Merge branch 'master' into T1312-MG-Cache-vertex-reads-on-disk

This commit is contained in:
gvolfing
2023-10-11 10:19:14 +02:00
170 changed files with 7142 additions and 3062 deletions

View File

@@ -20,6 +20,8 @@ add_subdirectory(auth)
add_subdirectory(audit)
add_subdirectory(dbms)
add_subdirectory(flags)
add_subdirectory(distributed)
add_subdirectory(replication)
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)

View File

@@ -208,7 +208,7 @@ class Session {
Version version_;
virtual std::string GetDatabaseName() const = 0;
virtual std::string GetCurrentDB() const = 0;
std::string UUID() const { return session_uuid_; }
private:

View File

@@ -208,7 +208,7 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
spdlog::debug("[Run - {}] '{}'", session.GetDatabaseName(), query.ValueString());
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
try {
// Interpret can throw.
@@ -272,7 +272,7 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
return HandleFailure(session, e);
}
spdlog::debug("[Run - {}] '{}'", session.GetDatabaseName(), query.ValueString());
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
try {
// Interpret can throw.

View File

@@ -60,6 +60,11 @@ class Database {
return storage_->Access(override_isolation_level);
}
std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
return storage_->UniqueAccess(override_isolation_level);
}
/**
* @brief Unique storage identified (name)
*

View File

@@ -0,0 +1,4 @@
add_library(mg-distributed)
add_library(mg::distributed ALIAS mg-distributed)
target_include_directories(mg-distributed PUBLIC include )
target_sources(mg-distributed PRIVATE lamport_clock.cpp)

View File

@@ -0,0 +1,61 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include <compare>
#include <cstdint>
#include <numeric>
namespace memgraph::distributed {
// forward declare, for strong timestamps
template <typename Tag>
struct LamportClock;
template <typename Tag>
struct timestamp {
friend std::strong_ordering operator<=>(timestamp const &, timestamp const &) = default;
private:
friend struct LamportClock<Tag>;
explicit timestamp(uint64_t value) : value_{value} {}
uint64_t value_;
};
constexpr struct internal_t {
} internal;
constexpr struct send_t {
} send;
constexpr struct receive_t {
} receive;
template <typename Tag>
struct LamportClock {
using timestamp_t = timestamp<Tag>;
auto get_timestamp(internal_t) -> timestamp_t { return timestamp_t{++internal}; };
auto get_timestamp(send_t) -> timestamp_t { return timestamp_t{++internal}; };
auto get_timestamp(receive_t, timestamp_t received_timestamp) -> timestamp_t {
while (true) {
auto local_current = internal.load(std::memory_order_acquire);
auto next = std::max(received_timestamp.value_, local_current) + 1;
bool res = internal.compare_exchange_weak(local_current, next, std::memory_order_acq_rel);
if (res) return timestamp_t{next};
}
};
private:
std::atomic<uint64_t> internal = 0;
};
} // namespace memgraph::distributed

View File

@@ -0,0 +1,11 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "distributed/lamport_clock.hpp"

View File

@@ -114,9 +114,9 @@ std::string SessionHL::GetDefaultDB() {
}
#endif
std::string SessionHL::GetDatabaseName() const {
if (!interpreter_.db_acc_) return "";
const auto *db = interpreter_.db_acc_->get();
std::string SessionHL::GetCurrentDB() const {
if (!interpreter_.current_db_.db_acc_) return "";
const auto *db = interpreter_.current_db_.db_acc_->get();
return db->id();
}
@@ -127,18 +127,23 @@ std::optional<std::string> SessionHL::GetServerNameForInit() {
bool SessionHL::Authenticate(const std::string &username, const std::string &password) {
bool res = true;
interpreter_.ResetUser();
{
auto locked_auth = auth_->Lock();
if (locked_auth->HasUsers()) {
user_ = locked_auth->Authenticate(username, password);
res = user_.has_value();
if (user_.has_value()) {
interpreter_.SetUser(user_->username());
} else {
res = false;
}
}
}
#ifdef MG_ENTERPRISE
// Start off with the default database
interpreter_.SetCurrentDB(GetDefaultDB());
interpreter_.SetCurrentDB(GetDefaultDB(), false);
#endif
implicit_db_.emplace(GetDatabaseName());
implicit_db_.emplace(GetCurrentDB());
return res;
}
@@ -159,7 +164,7 @@ std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Pull(Sess
std::optional<int> n,
std::optional<int> qid) {
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
auto *db = interpreter_.db_acc_->get();
auto *db = interpreter_.current_db_.db_acc_->get();
try {
TypedValueResultStream<TEncoder> stream(encoder, db->storage());
return DecodeSummary(interpreter_.Pull(&stream, n, qid));
@@ -184,14 +189,14 @@ std::pair<std::vector<std::string>, std::optional<int>> SessionHL::Interpret(
#ifdef MG_ENTERPRISE
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
auto *db = interpreter_.db_acc_->get();
auto *db = interpreter_.current_db_.db_acc_->get();
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
memgraph::storage::PropertyValue(params_pv), db->id());
}
#endif
try {
auto result = interpreter_.Prepare(query, params_pv, username, ToQueryExtras(extra), UUID());
auto result = interpreter_.Prepare(query, params_pv, ToQueryExtras(extra));
const std::string db_name = result.db ? *result.db : "";
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges, db_name)) {
interpreter_.Abort();
@@ -230,7 +235,7 @@ void SessionHL::Configure(const std::map<std::string, memgraph::communication::b
throw memgraph::communication::bolt::ClientError("Malformed database name.");
}
db = db_info.ValueString();
const auto &current = GetDatabaseName();
const auto &current = GetCurrentDB();
update = db != current;
if (!in_explicit_db_) implicit_db_.emplace(current); // Still not in an explicit database, save for recovery
in_explicit_db_ = true;
@@ -241,14 +246,14 @@ void SessionHL::Configure(const std::map<std::string, memgraph::communication::b
} else {
db = GetDefaultDB();
}
update = db != GetDatabaseName();
update = db != GetCurrentDB();
in_explicit_db_ = false;
}
// Check if the underlying database needs to be updated
if (update) {
MultiDatabaseAuth(user_, db);
interpreter_.SetCurrentDB(db);
interpreter_.SetCurrentDB(db, in_explicit_db_);
}
#endif
}
@@ -288,7 +293,7 @@ SessionHL::~SessionHL() {
std::map<std::string, memgraph::communication::bolt::Value> SessionHL::DecodeSummary(
const std::map<std::string, memgraph::query::TypedValue> &summary) {
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
auto *db = interpreter_.db_acc_->get();
auto *db = interpreter_.current_db_.db_acc_->get();
std::map<std::string, memgraph::communication::bolt::Value> decoded_summary;
for (const auto &kv : summary) {
auto maybe_value = ToBoltValue(kv.second, *db->storage(), memgraph::storage::View::NEW);

View File

@@ -68,7 +68,7 @@ class SessionHL final : public memgraph::communication::bolt::Session<memgraph::
std::optional<std::string> GetServerNameForInit() override;
std::string GetDatabaseName() const override;
std::string GetCurrentDB() const override;
private:
std::map<std::string, memgraph::communication::bolt::Value> DecodeSummary(

View File

@@ -4,4 +4,5 @@ find_package(ZLIB REQUIRED)
# STATIC library used to store key-value pairs
add_library(mg-kvstore STATIC kvstore.cpp)
add_library(mg::kvstore ALIAS mg-kvstore)
target_link_libraries(mg-kvstore stdc++fs mg-utils rocksdb BZip2::BZip2 ZLIB::ZLIB gflags)

View File

@@ -360,7 +360,7 @@ int main(int argc, char **argv) {
auto db_acc_opt = db_gatekeeper.access();
MG_ASSERT(db_acc_opt, "Failed to access the main database");
auto &db_acc = *db_acc_opt;
memgraph::query::InterpreterContext interpreter_context_(interp_config, nullptr, auth_handler.get(),
memgraph::query::InterpreterContext interpreter_context_(interp_config, &db_gatekeeper, auth_handler.get(),
auth_checker.get());
#endif
MG_ASSERT(db_acc, "Failed to access the main database");

View File

@@ -38,7 +38,8 @@ set(mg_query_sources
graph.cpp
db_accessor.cpp
auth_query_handler.cpp
interpreter_context.cpp)
interpreter_context.cpp
)
add_library(mg-query STATIC ${mg_query_sources})
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)

View File

@@ -19,7 +19,7 @@ struct InterpreterConfig {
bool allow_load_csv{true};
} query;
// The same as \ref memgraph::storage::replication::ReplicationClientConfig
// The same as \ref memgraph::replication::ReplicationClientConfig
std::chrono::seconds replication_replica_check_frequency{1};
std::string default_kafka_bootstrap_servers;

View File

@@ -459,6 +459,8 @@ class DbAccessor final {
}
storage::Result<std::optional<VertexAccessor>> RemoveVertex(VertexAccessor *vertex_accessor) {
accessor_->PrefetchOutEdges(vertex_accessor->impl_);
accessor_->PrefetchInEdges(vertex_accessor->impl_);
auto res = accessor_->DeleteVertex(&vertex_accessor->impl_);
if (res.HasError()) {
return res.GetError();
@@ -533,7 +535,7 @@ class DbAccessor final {
void AdvanceCommand() { accessor_->AdvanceCommand(); }
utils::BasicResult<storage::StorageDataManipulationError, void> Commit() { return accessor_->Commit(); }
utils::BasicResult<storage::StorageManipulationError, void> Commit() { return accessor_->Commit(); }
void Abort() { accessor_->Abort(); }
@@ -554,20 +556,12 @@ class DbAccessor final {
return accessor_->GetIndexStats(label, property);
}
std::vector<std::pair<storage::LabelId, storage::PropertyId>> ClearLabelPropertyIndexStats() {
return accessor_->ClearLabelPropertyIndexStats();
}
std::vector<storage::LabelId> ClearLabelIndexStats() { return accessor_->ClearLabelIndexStats(); }
std::vector<std::pair<storage::LabelId, storage::PropertyId>> DeleteLabelPropertyIndexStats(
const std::span<std::string> labels) {
return accessor_->DeleteLabelPropertyIndexStats(labels);
const storage::LabelId &label) {
return accessor_->DeleteLabelPropertyIndexStats(label);
}
std::vector<storage::LabelId> DeleteLabelIndexStats(const std::span<std::string> labels) {
return accessor_->DeleteLabelIndexStats(labels);
}
bool DeleteLabelIndexStats(const storage::LabelId &label) { return accessor_->DeleteLabelIndexStats(label); }
void SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats) {
accessor_->SetIndexStats(label, stats);
@@ -602,6 +596,44 @@ class DbAccessor final {
storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
const std::string &id() const { return accessor_->id(); }
utils::BasicResult<storage::StorageIndexDefinitionError, void> CreateIndex(storage::LabelId label) {
return accessor_->CreateIndex(label);
}
utils::BasicResult<storage::StorageIndexDefinitionError, void> CreateIndex(storage::LabelId label,
storage::PropertyId property) {
return accessor_->CreateIndex(label, property);
}
utils::BasicResult<storage::StorageIndexDefinitionError, void> DropIndex(storage::LabelId label) {
return accessor_->DropIndex(label);
}
utils::BasicResult<storage::StorageIndexDefinitionError, void> DropIndex(storage::LabelId label,
storage::PropertyId property) {
return accessor_->DropIndex(label, property);
}
utils::BasicResult<storage::StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
storage::LabelId label, storage::PropertyId property) {
return accessor_->CreateExistenceConstraint(label, property);
}
utils::BasicResult<storage::StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
storage::LabelId label, storage::PropertyId property) {
return accessor_->DropExistenceConstraint(label, property);
}
utils::BasicResult<storage::StorageUniqueConstraintDefinitionError, storage::UniqueConstraints::CreationStatus>
CreateUniqueConstraint(storage::LabelId label, const std::set<storage::PropertyId> &properties) {
return accessor_->CreateUniqueConstraint(label, properties);
}
storage::UniqueConstraints::DeletionStatus DropUniqueConstraint(storage::LabelId label,
const std::set<storage::PropertyId> &properties) {
return accessor_->DropUniqueConstraint(label, properties);
}
};
class SubgraphDbAccessor final {

View File

@@ -223,7 +223,11 @@ constexpr utils::TypeInfo query::Unwind::kType{utils::TypeId::AST_UNWIND, "Unwin
constexpr utils::TypeInfo query::AuthQuery::kType{utils::TypeId::AST_AUTH_QUERY, "AuthQuery", &query::Query::kType};
constexpr utils::TypeInfo query::InfoQuery::kType{utils::TypeId::AST_INFO_QUERY, "InfoQuery", &query::Query::kType};
constexpr utils::TypeInfo query::DatabaseInfoQuery::kType{utils::TypeId::AST_DATABASE_INFO_QUERY, "DatabaseInfoQuery",
&query::Query::kType};
constexpr utils::TypeInfo query::SystemInfoQuery::kType{utils::TypeId::AST_SYSTEM_INFO_QUERY, "SystemInfoQuery",
&query::Query::kType};
constexpr utils::TypeInfo query::Constraint::kType{utils::TypeId::AST_CONSTRAINT, "Constraint", nullptr};

View File

@@ -2897,19 +2897,37 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {AuthQuery::Privilege::
AuthQuery::Privilege::MULTI_DATABASE_EDIT,
AuthQuery::Privilege::MULTI_DATABASE_USE};
class InfoQuery : public memgraph::query::Query {
class DatabaseInfoQuery : public memgraph::query::Query {
public:
static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
enum class InfoType { STORAGE, INDEX, CONSTRAINT, BUILD };
enum class InfoType { INDEX, CONSTRAINT };
DEFVISITABLE(QueryVisitor<void>);
memgraph::query::InfoQuery::InfoType info_type_;
memgraph::query::DatabaseInfoQuery::InfoType info_type_;
InfoQuery *Clone(AstStorage *storage) const override {
InfoQuery *object = storage->Create<InfoQuery>();
DatabaseInfoQuery *Clone(AstStorage *storage) const override {
DatabaseInfoQuery *object = storage->Create<DatabaseInfoQuery>();
object->info_type_ = info_type_;
return object;
}
};
class SystemInfoQuery : public memgraph::query::Query {
public:
static const utils::TypeInfo kType;
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
enum class InfoType { STORAGE, BUILD };
DEFVISITABLE(QueryVisitor<void>);
memgraph::query::SystemInfoQuery::InfoType info_type_;
SystemInfoQuery *Clone(AstStorage *storage) const override {
SystemInfoQuery *object = storage->Create<SystemInfoQuery>();
object->info_type_ = info_type_;
return object;
}

View File

@@ -82,7 +82,8 @@ class AuthQuery;
class ExplainQuery;
class ProfileQuery;
class IndexQuery;
class InfoQuery;
class DatabaseInfoQuery;
class SystemInfoQuery;
class ConstraintQuery;
class RegexMatch;
class DumpQuery;
@@ -140,10 +141,10 @@ class ExpressionVisitor
template <class TResult>
class QueryVisitor
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery,
ShowConfigQuery, TransactionQueueQuery, StorageModeQuery, AnalyzeGraphQuery,
MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery> {};
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, DatabaseInfoQuery,
SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery,
SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery, StorageModeQuery,
AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery> {};
} // namespace memgraph::query

View File

@@ -112,25 +112,36 @@ antlrcpp::Any CypherMainVisitor::visitProfileQuery(MemgraphCypher::ProfileQueryC
return profile_query;
}
antlrcpp::Any CypherMainVisitor::visitInfoQuery(MemgraphCypher::InfoQueryContext *ctx) {
MG_ASSERT(ctx->children.size() == 2, "InfoQuery should have exactly two children!");
auto *info_query = storage_->Create<InfoQuery>();
antlrcpp::Any CypherMainVisitor::visitDatabaseInfoQuery(MemgraphCypher::DatabaseInfoQueryContext *ctx) {
MG_ASSERT(ctx->children.size() == 2, "DatabaseInfoQuery should have exactly two children!");
auto *info_query = storage_->Create<DatabaseInfoQuery>();
query_ = info_query;
if (ctx->indexInfo()) {
info_query->info_type_ = DatabaseInfoQuery::InfoType::INDEX;
return info_query;
}
if (ctx->constraintInfo()) {
info_query->info_type_ = DatabaseInfoQuery::InfoType::CONSTRAINT;
return info_query;
}
// Should never get here
throw utils::NotYetImplemented("Database info query: '{}'", ctx->getText());
}
antlrcpp::Any CypherMainVisitor::visitSystemInfoQuery(MemgraphCypher::SystemInfoQueryContext *ctx) {
MG_ASSERT(ctx->children.size() == 2, "SystemInfoQuery should have exactly two children!");
auto *info_query = storage_->Create<SystemInfoQuery>();
query_ = info_query;
if (ctx->storageInfo()) {
info_query->info_type_ = InfoQuery::InfoType::STORAGE;
info_query->info_type_ = SystemInfoQuery::InfoType::STORAGE;
return info_query;
} else if (ctx->indexInfo()) {
info_query->info_type_ = InfoQuery::InfoType::INDEX;
return info_query;
} else if (ctx->constraintInfo()) {
info_query->info_type_ = InfoQuery::InfoType::CONSTRAINT;
return info_query;
} else if (ctx->buildInfo()) {
info_query->info_type_ = InfoQuery::InfoType::BUILD;
return info_query;
} else {
throw utils::NotYetImplemented("Info query: '{}'", ctx->getText());
}
if (ctx->buildInfo()) {
info_query->info_type_ = SystemInfoQuery::InfoType::BUILD;
return info_query;
}
// Should never get here
throw utils::NotYetImplemented("System info query: '{}'", ctx->getText());
}
antlrcpp::Any CypherMainVisitor::visitConstraintQuery(MemgraphCypher::ConstraintQueryContext *ctx) {

View File

@@ -157,9 +157,14 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
antlrcpp::Any visitProfileQuery(MemgraphCypher::ProfileQueryContext *ctx) override;
/**
* @return InfoQuery*
* @return DatabaseInfoQuery*
*/
antlrcpp::Any visitInfoQuery(MemgraphCypher::InfoQueryContext *ctx) override;
antlrcpp::Any visitDatabaseInfoQuery(MemgraphCypher::DatabaseInfoQueryContext *ctx) override;
/**
* @return SystemInfoQuery*
*/
antlrcpp::Any visitSystemInfoQuery(MemgraphCypher::SystemInfoQueryContext *ctx) override;
/**
* @return Constraint

View File

@@ -27,7 +27,8 @@ query : cypherQuery
| indexQuery
| explainQuery
| profileQuery
| infoQuery
| databaseInfoQuery
| systemInfoQuery
| constraintQuery
;
@@ -48,7 +49,9 @@ constraintInfo : CONSTRAINT INFO ;
buildInfo : BUILD INFO ;
infoQuery : SHOW ( storageInfo | indexInfo | constraintInfo | buildInfo) ;
databaseInfoQuery : SHOW ( indexInfo | constraintInfo ) ;
systemInfoQuery : SHOW ( storageInfo | buildInfo ) ;
explainQuery : EXPLAIN cypherQuery ;

View File

@@ -128,7 +128,8 @@ query : cypherQuery
| indexQuery
| explainQuery
| profileQuery
| infoQuery
| databaseInfoQuery
| systemInfoQuery
| constraintQuery
| authQuery
| dumpQuery

View File

@@ -35,18 +35,14 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
void Visit(ProfileQuery &query) override { query.cypher_query_->Accept(dynamic_cast<QueryVisitor &>(*this)); }
void Visit(InfoQuery &info_query) override {
void Visit(DatabaseInfoQuery &info_query) override {
switch (info_query.info_type_) {
case InfoQuery::InfoType::INDEX:
case DatabaseInfoQuery::InfoType::INDEX:
// TODO: This should be INDEX | STATS, but we don't have support for
// *or* with privileges.
AddPrivilege(AuthQuery::Privilege::INDEX);
break;
case InfoQuery::InfoType::STORAGE:
case InfoQuery::InfoType::BUILD:
AddPrivilege(AuthQuery::Privilege::STATS);
break;
case InfoQuery::InfoType::CONSTRAINT:
case DatabaseInfoQuery::InfoType::CONSTRAINT:
// TODO: This should be CONSTRAINT | STATS, but we don't have support
// for *or* with privileges.
AddPrivilege(AuthQuery::Privilege::CONSTRAINT);
@@ -54,6 +50,15 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
}
}
void Visit(SystemInfoQuery &info_query) override {
switch (info_query.info_type_) {
case SystemInfoQuery::InfoType::STORAGE:
case SystemInfoQuery::InfoType::BUILD:
AddPrivilege(AuthQuery::Privilege::STATS);
break;
}
}
void Visit(ConstraintQuery &constraint_query) override { AddPrivilege(AuthQuery::Privilege::CONSTRAINT); }
void Visit(CypherQuery &query) override {

View File

@@ -518,6 +518,10 @@ bool SymbolGenerator::PreVisit(Exists &exists) {
throw utils::NotYetImplemented("WITH can not be used with exists, but only during matching!");
}
if (scope.in_return) {
throw utils::NotYetImplemented("RETURN can not be used with exists, but only during matching!");
}
scope.in_exists = true;
const auto &symbol = CreateAnonymousSymbol();

File diff suppressed because it is too large Load Diff

View File

@@ -95,7 +95,7 @@ class ReplicationQueryHandler {
const std::chrono::seconds replica_check_frequency) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DropReplica(const std::string &replica_name) = 0;
virtual void DropReplica(std::string_view replica_name) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual std::vector<Replica> ShowReplicas() const = 0;
@@ -139,6 +139,34 @@ struct QueryExtras {
std::optional<int64_t> tx_timeout;
};
struct CurrentDB {
CurrentDB() = default; // TODO: remove, we should always have an implicit default obtainable from somewhere
// ATM: it is provided by the DatabaseAccess
// future: should be a name + ptr to dbms_handler, lazy fetch when needed
explicit CurrentDB(memgraph::dbms::DatabaseAccess db_acc) : db_acc_{std::move(db_acc)} {}
CurrentDB(CurrentDB const &) = delete;
CurrentDB &operator=(CurrentDB const &) = delete;
void SetupDatabaseTransaction(std::optional<storage::IsolationLevel> override_isolation_level, bool could_commit,
bool unique = false);
void CleanupDBTransaction(bool abort);
void SetCurrentDB(memgraph::dbms::DatabaseAccess new_db, bool in_explicit_db) {
// do we lock here?
db_acc_ = std::move(new_db);
in_explicit_db_ = in_explicit_db;
}
// TODO: don't provide explicitly via constructor, instead have a lazy way of getting the current/default
// DatabaseAccess
// hence, explict bolt "use DB" in metadata wouldn't necessarily get access unless query required it.
std::optional<memgraph::dbms::DatabaseAccess> db_acc_; // Current db (TODO: expand to support multiple)
std::unique_ptr<storage::Storage::Accessor> db_transactional_accessor_;
std::optional<DbAccessor> execution_db_accessor_;
std::optional<TriggerContextCollector> trigger_context_collector_;
bool in_explicit_db_{false};
};
class Interpreter final {
public:
Interpreter(InterpreterContext *interpreter_context);
@@ -158,16 +186,14 @@ class Interpreter final {
std::optional<std::string> username_;
bool in_explicit_transaction_{false};
bool in_explicit_db_{false};
CurrentDB current_db_;
bool expect_rollback_{false};
std::shared_ptr<utils::AsyncTimer> explicit_transaction_timer_{};
std::shared_ptr<utils::AsyncTimer> current_timeout_timer_{};
std::optional<std::map<std::string, storage::PropertyValue>> metadata_{}; //!< User defined transaction metadata
std::optional<memgraph::dbms::DatabaseAccess> db_acc_; // Current db (TODO: expand to support multiple)
#ifdef MG_ENTERPRISE
void SetCurrentDB(std::string_view db_name);
void SetCurrentDB(memgraph::dbms::DatabaseAccess new_db);
void SetCurrentDB(std::string_view db_name, bool explicit_db);
void OnChangeCB(auto cb) { on_change_.emplace(cb); }
#endif
@@ -179,9 +205,9 @@ class Interpreter final {
*
* @throw query::QueryException
*/
PrepareResult Prepare(const std::string &query, const std::map<std::string, storage::PropertyValue> &params,
const std::string *username, QueryExtras const &extras = {},
const std::string &session_uuid = {});
Interpreter::PrepareResult Prepare(const std::string &query,
const std::map<std::string, storage::PropertyValue> &params,
QueryExtras const &extras);
/**
* Execute the last prepared query and stream *all* of the results into the
@@ -243,27 +269,30 @@ class Interpreter final {
*/
void Abort();
std::atomic<TransactionStatus> transaction_status_{TransactionStatus::IDLE};
std::atomic<TransactionStatus> transaction_status_{TransactionStatus::IDLE}; // Tie to current_transaction_
std::optional<uint64_t> current_transaction_;
void ResetUser();
void SetUser(std::string_view username);
private:
struct QueryExecution {
std::optional<PreparedQuery> prepared_query;
std::variant<utils::MonotonicBufferResource, utils::PoolResource> execution_memory;
utils::ResourceWithOutOfMemoryException execution_memory_with_exception;
std::optional<PreparedQuery> prepared_query;
std::map<std::string, TypedValue> summary;
std::vector<Notification> notifications;
explicit QueryExecution(utils::MonotonicBufferResource monotonic_memory)
: execution_memory(std::move(monotonic_memory)) {
std::visit(
[&](auto &memory_resource) {
execution_memory_with_exception = utils::ResourceWithOutOfMemoryException(&memory_resource);
},
execution_memory);
};
static auto Create(std::variant<utils::MonotonicBufferResource, utils::PoolResource> memory_resource,
std::optional<PreparedQuery> prepared_query = std::nullopt) -> std::unique_ptr<QueryExecution> {
return std::make_unique<QueryExecution>(std::move(memory_resource), std::move(prepared_query));
}
explicit QueryExecution(utils::PoolResource pool_resource) : execution_memory(std::move(pool_resource)) {
explicit QueryExecution(std::variant<utils::MonotonicBufferResource, utils::PoolResource> memory_resource,
std::optional<PreparedQuery> prepared_query)
: execution_memory(std::move(memory_resource)), prepared_query{std::move(prepared_query)} {
std::visit(
[&](auto &memory_resource) {
execution_memory_with_exception = utils::ResourceWithOutOfMemoryException(&memory_resource);
@@ -311,12 +340,6 @@ class Interpreter final {
InterpreterContext *interpreter_context_;
// This cannot be std::optional because we need to move this accessor later on into a lambda capture
// which is assigned to std::function. std::function requires every object to be copyable, so we
// move this unique_ptr into a shrared_ptr.
std::unique_ptr<storage::Storage::Accessor> db_accessor_;
std::optional<DbAccessor> execution_db_accessor_;
std::optional<TriggerContextCollector> trigger_context_collector_;
std::optional<FrameChangeCollector> frame_change_collector_;
std::optional<storage::IsolationLevel> interpreter_isolation_level;
@@ -334,22 +357,8 @@ class Interpreter final {
}
std::optional<std::function<void(std::string_view)>> on_change_{};
};
class TransactionQueueQueryHandler {
public:
TransactionQueueQueryHandler() = default;
virtual ~TransactionQueueQueryHandler() = default;
TransactionQueueQueryHandler(const TransactionQueueQueryHandler &) = default;
TransactionQueueQueryHandler &operator=(const TransactionQueueQueryHandler &) = default;
TransactionQueueQueryHandler(TransactionQueueQueryHandler &&) = default;
TransactionQueueQueryHandler &operator=(TransactionQueueQueryHandler &&) = default;
static std::vector<std::vector<TypedValue>> ShowTransactions(
const std::unordered_set<Interpreter *> &interpreters, const std::optional<std::string> &username,
bool hasTransactionManagementPrivilege, std::optional<memgraph::dbms::DatabaseAccess> &filter_db_acc);
void SetupInterpreterTransaction(const QueryExtras &extras);
void SetupDatabaseTransaction(bool couldCommit, bool unique = false);
};
template <typename TStream>
@@ -411,7 +420,7 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std:
// The only cases in which we have nothing to do are those where
// we're either in an explicit transaction or the query is such that
// a transaction wasn't started on a call to `Prepare()`.
MG_ASSERT(in_explicit_transaction_ || !db_accessor_);
MG_ASSERT(in_explicit_transaction_ || !current_db_.db_transactional_accessor_);
break;
}
// As the transaction is done we can clear all the executions

View File

@@ -13,15 +13,15 @@
#include "query/interpreter.hpp"
namespace memgraph::query {
std::vector<std::vector<TypedValue>> InterpreterContext::KillTransactions(
std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions(
std::vector<std::string> maybe_kill_transaction_ids, const std::optional<std::string> &username,
bool hasTransactionManagementPrivilege, Interpreter &calling_interpreter) {
std::function<bool(std::string const &)> privilege_checker) {
auto not_found_midpoint = maybe_kill_transaction_ids.end();
// Multiple simultaneous TERMINATE TRANSACTIONS aren't allowed
// TERMINATE and SHOW TRANSACTIONS are mutually exclusive
interpreters.WithLock([&not_found_midpoint, &maybe_kill_transaction_ids, username, hasTransactionManagementPrivilege,
filter_db_acc = &calling_interpreter.db_acc_](const auto &interpreters) {
interpreters.WithLock([&not_found_midpoint, &maybe_kill_transaction_ids, username,
privilege_checker = std::move(privilege_checker)](const auto &interpreters) {
for (Interpreter *interpreter : interpreters) {
TransactionStatus alive_status = TransactionStatus::ACTIVE;
// if it is just checking kill, commit and abort should wait for the end of the check
@@ -38,7 +38,6 @@ std::vector<std::vector<TypedValue>> InterpreterContext::KillTransactions(
interpreter->transaction_status_.store(TransactionStatus::ACTIVE, std::memory_order_release);
}
});
if (interpreter->db_acc_ != *filter_db_acc) continue;
std::optional<uint64_t> intr_trans = interpreter->GetTransactionId();
if (!intr_trans.has_value()) continue;
@@ -49,7 +48,11 @@ std::vector<std::vector<TypedValue>> InterpreterContext::KillTransactions(
// update the maybe_kill_transaction_ids (partitioning not found + killed)
--not_found_midpoint;
std::iter_swap(it, not_found_midpoint);
if (interpreter->username_ == username || hasTransactionManagementPrivilege) {
auto get_interpreter_db_name = [&]() -> std::string const & {
static std::string all;
return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all;
};
if (interpreter->username_ == username || privilege_checker(get_interpreter_db_name())) {
killed = true; // Note: this is used by the above `clean_status` (OnScopeExit)
spdlog::warn("Transaction {} successfully killed", transaction_id);
} else {

View File

@@ -11,6 +11,8 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_set>
@@ -34,6 +36,8 @@ class Database;
namespace memgraph::query {
constexpr uint64_t kInterpreterTransactionInitialId = 1ULL << 63U;
class AuthQueryHandler;
class AuthChecker;
class Interpreter;
@@ -72,14 +76,20 @@ struct InterpreterContext {
// TODO: Have a way to read the current database
memgraph::utils::Synchronized<std::unordered_set<Interpreter *>, memgraph::utils::SpinLock> interpreters;
struct {
auto next() -> uint64_t { return transaction_id++; }
private:
std::atomic<uint64_t> transaction_id = kInterpreterTransactionInitialId;
} id_handler;
/// Function that is used to tell all active interpreters that they should stop
/// their ongoing execution.
void Shutdown() { is_shutting_down.store(true, std::memory_order_release); }
std::vector<std::vector<TypedValue>> KillTransactions(std::vector<std::string> maybe_kill_transaction_ids,
const std::optional<std::string> &username,
bool hasTransactionManagementPrivilege,
Interpreter &calling_interpreter);
std::vector<std::vector<TypedValue>> TerminateTransactions(
std::vector<std::string> maybe_kill_transaction_ids, const std::optional<std::string> &username,
std::function<bool(std::string const &)> privilege_checker);
};
} // namespace memgraph::query

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -21,7 +21,7 @@
namespace memgraph::query::plan {
PRE_VISIT(CreateNode, RWType::W, true)
PRE_VISIT(CreateExpand, RWType::R, true)
PRE_VISIT(CreateExpand, RWType::R, true) // ?? RWType::RW
PRE_VISIT(Delete, RWType::W, true)
PRE_VISIT(SetProperty, RWType::W, true)

View File

@@ -488,6 +488,10 @@ class RuleBasedPlanner {
last_op = std::make_unique<ScanAll>(std::move(last_op), node1_symbol, view);
new_symbols.emplace_back(node1_symbol);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
} else if (named_paths.size() == 1U) {
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);
last_op = impl::GenNamedPaths(std::move(last_op), bound_symbols, named_paths);
last_op = GenFilters(std::move(last_op), bound_symbols, filters, storage, symbol_table);

View File

@@ -1940,7 +1940,7 @@ void mgp_vertex_destroy(mgp_vertex *v) { DeleteRawMgpObject(v); }
mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
// NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
static_assert(noexcept(*result = *v1 == *v2 ? 1 : 0));
static_assert(noexcept(*v1 == *v2));
*result = *v1 == *v2 ? 1 : 0;
return mgp_error::MGP_ERROR_NO_ERROR;
}
@@ -2313,7 +2313,7 @@ void mgp_edge_destroy(mgp_edge *e) { DeleteRawMgpObject(e); }
mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
// NOLINTNEXTLINE(clang-diagnostic-unevaluated-expression)
static_assert(noexcept(*result = *e1 == *e2 ? 1 : 0));
static_assert(noexcept(*e1 == *e2));
*result = *e1 == *e2 ? 1 : 0;
return mgp_error::MGP_ERROR_NO_ERROR;
}

View File

@@ -497,7 +497,7 @@ Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std
#ifdef MG_ENTERPRISE
interpreter->OnChangeCB([](auto) { return false; }); // Disable database change
#endif
auto accessor = interpreter->db_acc_->get()->Access();
auto accessor = interpreter->current_db_.db_acc_->get()->Access();
// register new interpreter into interpreter_context
interpreter_context->interpreters->insert(interpreter.get());
utils::OnScopeExit interpreter_cleanup{
@@ -527,7 +527,7 @@ Streams::StreamsMap::iterator Streams::CreateConsumer(StreamsMap &map, const std
std::string query{query_value.ValueString()};
spdlog::trace("Executing query '{}' in stream '{}'", query, stream_name);
auto prepare_result =
interpreter->Prepare(query, params_prop.IsNull() ? empty_parameters : params_prop.ValueMap(), nullptr);
interpreter->Prepare(query, params_prop.IsNull() ? empty_parameters : params_prop.ValueMap(), {});
if (!interpreter_context->auth_checker->IsUserAuthorized(owner, prepare_result.privileges, "")) {
throw StreamsException{
"Couldn't execute query '{}' for stream '{}' because the owner is not authorized to execute the "

View File

@@ -0,0 +1,24 @@
add_library(mg-replication STATIC)
add_library(mg::replication ALIAS mg-replication)
target_sources(mg-replication
PUBLIC
include/replication/state.hpp
include/replication/epoch.hpp
include/replication/config.hpp
include/replication/mode.hpp
include/replication/role.hpp
include/replication/status.hpp
PRIVATE
state.cpp
epoch.cpp
config.cpp
status.cpp
)
target_include_directories(mg-replication PUBLIC include)
find_package(fmt REQUIRED)
target_link_libraries(mg-replication
PUBLIC mg::utils mg::kvstore lib::json
PRIVATE fmt::fmt
)

View File

@@ -0,0 +1,11 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication/config.hpp"

12
src/replication/epoch.cpp Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication/epoch.hpp"

View File

@@ -12,12 +12,17 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <optional>
#include <string>
#include "replication/mode.hpp"
#include "storage/v2/replication/enums.hpp"
namespace memgraph::replication {
inline constexpr uint16_t kDefaultReplicationPort = 10000;
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
inline constexpr auto *kReservedReplicationRoleName{"__replication_role"};
namespace memgraph::storage::replication {
struct ReplicationClientConfig {
std::string name;
ReplicationMode mode;
@@ -51,4 +56,4 @@ struct ReplicationServerConfig {
std::optional<SSL> ssl;
};
} // namespace memgraph::storage::replication
} // namespace memgraph::replication

View File

@@ -0,0 +1,49 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <string>
#include <utility>
#include "utils/uuid.hpp"
namespace memgraph::replication {
struct ReplicationEpoch {
ReplicationEpoch() : id_(memgraph::utils::GenerateUUID()) {}
ReplicationEpoch(ReplicationEpoch const &) = delete;
ReplicationEpoch(ReplicationEpoch &&) = delete;
ReplicationEpoch &operator=(ReplicationEpoch const &) = delete;
ReplicationEpoch &operator=(ReplicationEpoch &&) = delete;
auto id() const -> std::string_view { return id_; }
auto NewEpoch() -> std::string { return std::exchange(id_, memgraph::utils::GenerateUUID()); }
auto SetEpoch(std::string new_epoch) -> std::string { return std::exchange(id_, std::move(new_epoch)); }
private:
// UUID to distinguish different main instance runs for replication process
// on SAME storage.
// Multiple instances can have same storage UUID and be MAIN at the same time.
// We cannot compare commit timestamps of those instances if one of them
// becomes the replica of the other so we use epoch_id_ as additional
// discriminating property.
// Example of this:
// We have 2 instances of the same storage, S1 and S2.
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
// At the moment when S1 commited a transaction with timestamp 20, and S2
// a different transaction with timestamp 15, we change S2's role to REPLICA
// and register it on S1.
// Without using the epoch_id, we don't know that S1 and S2 have completely
// different transactions, we think that the S2 is behind only by 5 commits.
std::string id_;
};
} // namespace memgraph::replication

View File

@@ -0,0 +1,16 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
namespace memgraph::replication {
enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
}

View File

@@ -0,0 +1,18 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
namespace memgraph::replication {
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
}

View File

@@ -0,0 +1,69 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include <cstdint>
#include <variant>
#include <vector>
#include "kvstore/kvstore.hpp"
#include "replication/config.hpp"
#include "replication/epoch.hpp"
#include "replication/mode.hpp"
#include "replication/role.hpp"
#include "utils/result.hpp"
namespace memgraph::replication {
enum class RolePersisted : uint8_t { UNKNOWN_OR_NO, YES };
struct ReplicationState {
ReplicationState(std::optional<std::filesystem::path> durability_dir);
ReplicationState(ReplicationState const &) = delete;
ReplicationState(ReplicationState &&) = delete;
ReplicationState &operator=(ReplicationState const &) = delete;
ReplicationState &operator=(ReplicationState &&) = delete;
void SetRole(ReplicationRole role) { return replication_role_.store(role); }
auto GetRole() const -> ReplicationRole { return replication_role_.load(); }
bool IsMain() const { return replication_role_ == ReplicationRole::MAIN; }
bool IsReplica() const { return replication_role_ == ReplicationRole::REPLICA; }
auto GetEpoch() const -> const ReplicationEpoch & { return epoch_; }
auto GetEpoch() -> ReplicationEpoch & { return epoch_; }
enum class FetchReplicationError : uint8_t {
NOTHING_FETCHED,
PARSE_ERROR,
};
using ReplicationDataReplica = ReplicationServerConfig;
using ReplicationDataMain = std::vector<ReplicationClientConfig>;
using ReplicationData = std::variant<ReplicationDataMain, ReplicationDataReplica>;
using FetchReplicationResult = utils::BasicResult<FetchReplicationError, ReplicationData>;
auto FetchReplicationData() -> FetchReplicationResult;
bool ShouldPersist() const { return nullptr != durability_; }
bool TryPersistRoleMain();
bool TryPersistRoleReplica(const ReplicationServerConfig &config);
bool TryPersistUnregisterReplica(std::string_view &name);
bool TryPersistRegisteredReplica(const ReplicationClientConfig &config);
private:
ReplicationEpoch epoch_;
std::atomic<ReplicationRole> replication_role_{ReplicationRole::MAIN};
std::unique_ptr<kvstore::KVStore> durability_;
std::atomic<RolePersisted> role_persisted = RolePersisted::UNKNOWN_OR_NO;
};
} // namespace memgraph::replication

View File

@@ -12,21 +12,16 @@
#pragma once
#include <chrono>
#include <compare>
#include <cstdint>
#include <optional>
#include <string>
#include <json/json.hpp>
#include "json/json.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
namespace memgraph::storage::replication {
inline constexpr auto *kReservedReplicationRoleName{"__replication_role"};
inline constexpr uint16_t kDefaultReplicationPort = 10000;
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
#include "replication/config.hpp"
#include "replication/role.hpp"
namespace memgraph::replication {
struct ReplicationStatus {
std::string name;
std::string ip_address;
@@ -40,6 +35,5 @@ struct ReplicationStatus {
};
nlohmann::json ReplicationStatusToJSON(ReplicationStatus &&status);
std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data);
} // namespace memgraph::storage::replication
} // namespace memgraph::replication

144
src/replication/state.cpp Normal file
View File

@@ -0,0 +1,144 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication/state.hpp"
#include "replication/status.hpp" //TODO: don't use status for durability
#include "utils/file.hpp"
constexpr auto kReplicationDirectory = std::string_view{"replication"};
namespace memgraph::replication {
ReplicationState::ReplicationState(std::optional<std::filesystem::path> durability_dir) {
if (!durability_dir) return;
auto repl_dir = *std::move(durability_dir);
repl_dir /= kReplicationDirectory;
utils::EnsureDirOrDie(repl_dir);
durability_ = std::make_unique<kvstore::KVStore>(std::move(repl_dir));
}
bool ReplicationState::TryPersistRoleReplica(const ReplicationServerConfig &config) {
if (!ShouldPersist()) return true;
// Only thing that matters here is the role saved as REPLICA and the listening port
auto data = ReplicationStatusToJSON(ReplicationStatus{.name = kReservedReplicationRoleName,
.ip_address = config.ip_address,
.port = config.port,
.sync_mode = ReplicationMode::SYNC,
.replica_check_frequency = std::chrono::seconds(0),
.ssl = std::nullopt,
.role = ReplicationRole::REPLICA});
if (durability_->Put(kReservedReplicationRoleName, data.dump())) {
role_persisted = RolePersisted::YES;
return true;
}
spdlog::error("Error when saving REPLICA replication role in settings.");
return false;
}
bool ReplicationState::TryPersistRoleMain() {
if (!ShouldPersist()) return true;
// Only thing that matters here is the role saved as MAIN
auto data = ReplicationStatusToJSON(ReplicationStatus{.name = kReservedReplicationRoleName,
.ip_address = "",
.port = 0,
.sync_mode = ReplicationMode::SYNC,
.replica_check_frequency = std::chrono::seconds(0),
.ssl = std::nullopt,
.role = ReplicationRole::MAIN});
if (durability_->Put(kReservedReplicationRoleName, data.dump())) {
role_persisted = RolePersisted::YES;
return true;
}
spdlog::error("Error when saving MAIN replication role in settings.");
return false;
}
bool ReplicationState::TryPersistUnregisterReplica(std::string_view &name) {
if (!ShouldPersist()) return true;
if (durability_->Delete(name)) return true;
spdlog::error("Error when removing replica {} from settings.", name);
return false;
}
auto ReplicationState::FetchReplicationData() -> FetchReplicationResult {
if (!ShouldPersist()) return FetchReplicationError::NOTHING_FETCHED;
const auto replication_data = durability_->Get(kReservedReplicationRoleName);
if (!replication_data.has_value()) {
return FetchReplicationError::NOTHING_FETCHED;
}
const auto maybe_replication_status = JSONToReplicationStatus(nlohmann::json::parse(*replication_data));
if (!maybe_replication_status.has_value()) {
return FetchReplicationError::PARSE_ERROR;
}
// To get here this must be the case
role_persisted = memgraph::replication::RolePersisted::YES;
const auto replication_status = *maybe_replication_status;
auto role = replication_status.role.value_or(ReplicationRole::MAIN);
switch (role) {
case ReplicationRole::REPLICA: {
return {ReplicationServerConfig{
.ip_address = kDefaultReplicationServerIp,
.port = replication_status.port,
}};
}
case ReplicationRole::MAIN: {
auto res = ReplicationState::ReplicationDataMain{};
res.reserve(durability_->Size() - 1);
for (const auto &[replica_name, replica_data] : *durability_) {
if (replica_name == kReservedReplicationRoleName) {
continue;
}
const auto maybe_replica_status = JSONToReplicationStatus(nlohmann::json::parse(replica_data));
if (!maybe_replica_status.has_value()) {
return FetchReplicationError::PARSE_ERROR;
}
auto replica_status = *maybe_replica_status;
if (replica_status.name != replica_name) {
return FetchReplicationError::PARSE_ERROR;
}
res.emplace_back(ReplicationClientConfig{
.name = replica_status.name,
.mode = replica_status.sync_mode,
.ip_address = replica_status.ip_address,
.port = replica_status.port,
.replica_check_frequency = replica_status.replica_check_frequency,
.ssl = replica_status.ssl,
});
}
return {std::move(res)};
}
}
}
bool ReplicationState::TryPersistRegisteredReplica(const ReplicationClientConfig &config) {
if (!ShouldPersist()) return true;
// If any replicas are persisted then Role must be persisted
if (role_persisted != RolePersisted::YES) {
DMG_ASSERT(IsMain(), "MAIN is expected");
if (!TryPersistRoleMain()) return false;
}
auto data = ReplicationStatusToJSON(ReplicationStatus{.name = config.name,
.ip_address = config.ip_address,
.port = config.port,
.sync_mode = config.mode,
.replica_check_frequency = config.replica_check_frequency,
.ssl = config.ssl,
.role = ReplicationRole::REPLICA});
if (durability_->Put(config.name, data.dump())) return true;
spdlog::error("Error when saving replica {} in settings.", config.name);
return false;
}
} // namespace memgraph::replication

View File

@@ -8,24 +8,21 @@
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication/status.hpp"
#include "storage/v2/replication/replication_persistence_helper.hpp"
#include "storage/v2/replication/enums.hpp"
#include "fmt/format.h"
#include "utils/logging.hpp"
namespace {
inline constexpr auto *kReplicaName = "replica_name";
inline constexpr auto *kIpAddress = "replica_ip_address";
inline constexpr auto *kPort = "replica_port";
inline constexpr auto *kSyncMode = "replica_sync_mode";
inline constexpr auto *kCheckFrequency = "replica_check_frequency";
inline constexpr auto *kSSLKeyFile = "replica_ssl_key_file";
inline constexpr auto *kSSLCertFile = "replica_ssl_cert_file";
inline constexpr auto *kReplicationRole = "replication_role";
} // namespace
constexpr auto *kReplicaName = "replica_name";
constexpr auto *kIpAddress = "replica_ip_address";
constexpr auto *kPort = "replica_port";
constexpr auto *kSyncMode = "replica_sync_mode";
constexpr auto *kCheckFrequency = "replica_check_frequency";
constexpr auto *kSSLKeyFile = "replica_ssl_key_file";
constexpr auto *kSSLCertFile = "replica_ssl_cert_file";
constexpr auto *kReplicationRole = "replication_role";
namespace memgraph::storage::replication {
namespace memgraph::replication {
nlohmann::json ReplicationStatusToJSON(ReplicationStatus &&status) {
auto data = nlohmann::json::object();
@@ -51,7 +48,6 @@ nlohmann::json ReplicationStatusToJSON(ReplicationStatus &&status) {
return data;
}
std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data) {
ReplicationStatus replica_status;
@@ -73,13 +69,13 @@ std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data)
MG_ASSERT(key_file.is_null() == cert_file.is_null());
if (!key_file.is_null()) {
replica_status.ssl = replication::ReplicationClientConfig::SSL{};
replica_status.ssl = ReplicationClientConfig::SSL{};
data.at(kSSLKeyFile).get_to(replica_status.ssl->key_file);
data.at(kSSLCertFile).get_to(replica_status.ssl->cert_file);
}
if (data.find(kReplicationRole) != data.end()) {
replica_status.role = replication::ReplicationRole::MAIN;
replica_status.role = ReplicationRole::MAIN;
data.at(kReplicationRole).get_to(replica_status.role.value());
}
} catch (const nlohmann::json::type_error &exception) {
@@ -92,4 +88,4 @@ std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data)
return replica_status;
}
} // namespace memgraph::storage::replication
} // namespace memgraph::replication

View File

@@ -36,13 +36,13 @@ add_library(mg-storage-v2 STATIC
replication/replication_server.cpp
replication/serialization.cpp
replication/slk.cpp
replication/replication_persistence_helper.cpp
replication/rpc.cpp
replication/replication.cpp
replication/replication_storage_state.cpp
replication/replication_handler.cpp
inmemory/replication/replication_server.cpp
inmemory/replication/replication_client.cpp
)
target_link_libraries(mg-storage-v2 Threads::Threads mg-utils gflags absl::flat_hash_map mg-rpc mg-slk)
target_link_libraries(mg-storage-v2 mg::replication Threads::Threads mg-utils gflags absl::flat_hash_map mg-rpc mg-slk)
# Until we get LTO there is an advantage to do some unity builds
set_target_properties(mg-storage-v2

View File

@@ -320,8 +320,9 @@ DiskStorage::~DiskStorage() {
kvstore_->options_.comparator = nullptr;
}
DiskStorage::DiskAccessor::DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode)
: Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) {
DiskStorage::DiskAccessor::DiskAccessor(auto tag, DiskStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: Accessor(tag, storage, isolation_level, storage_mode), config_(storage->config_.items) {
rocksdb::WriteOptions write_options;
auto txOptions = rocksdb::TransactionOptions{.set_snapshot = true};
disk_transaction_ = storage->kvstore_->db_->BeginTransaction(write_options, txOptions);
@@ -1091,7 +1092,7 @@ void DiskStorage::SetEdgeImportMode(EdgeImportMode edge_import_status) {
}
EdgeImportMode DiskStorage::GetEdgeImportMode() const {
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
std::shared_lock storage_guard_(main_lock_);
return edge_import_status_;
}
@@ -1445,24 +1446,24 @@ bool DiskStorage::DiskAccessor::DeleteEdgeFromDisk(const std::string &edge) {
return true;
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void>
[[nodiscard]] utils::BasicResult<StorageManipulationError, void>
DiskStorage::DiskAccessor::CheckVertexConstraintsBeforeCommit(
const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage) const {
if (auto existence_constraint_validation_result = storage_->constraints_.existence_constraints_->Validate(vertex);
existence_constraint_validation_result.has_value()) {
return StorageDataManipulationError{existence_constraint_validation_result.value()};
return StorageManipulationError{existence_constraint_validation_result.value()};
}
auto *disk_unique_constraints =
static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
if (auto unique_constraint_validation_result = disk_unique_constraints->Validate(vertex, unique_storage);
unique_constraint_validation_result.has_value()) {
return StorageDataManipulationError{unique_constraint_validation_result.value()};
return StorageManipulationError{unique_constraint_validation_result.value()};
}
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushVertices(
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::FlushVertices(
const auto &vertex_acc, std::vector<std::vector<PropertyValue>> &unique_storage) {
auto *disk_unique_constraints =
static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
@@ -1485,26 +1486,25 @@ DiskStorage::DiskAccessor::CheckVertexConstraintsBeforeCommit(
/// NOTE: this deletion has to come before writing, otherwise RocksDB thinks that all entries are deleted
if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) {
if (!DeleteVertexFromDisk(maybe_old_disk_key.value())) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
}
if (!WriteVertexToDisk(vertex)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
if (!disk_unique_constraints->SyncVertexToUniqueConstraintsStorage(vertex, *commit_timestamp_) ||
!disk_label_index->SyncVertexToLabelIndexStorage(vertex, *commit_timestamp_) ||
!disk_label_property_index->SyncVertexToLabelPropertyIndexStorage(vertex, *commit_timestamp_)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
}
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void>
DiskStorage::DiskAccessor::ClearDanglingVertices() {
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::ClearDanglingVertices() {
auto *disk_unique_constraints =
static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
auto *disk_label_index = static_cast<DiskLabelIndex *>(storage_->indices_.label_index_.get());
@@ -1516,12 +1516,12 @@ DiskStorage::DiskAccessor::ClearDanglingVertices() {
!disk_label_index->DeleteVerticesWithRemovedIndexingLabel(transaction_.start_timestamp, *commit_timestamp_) ||
!disk_label_property_index->DeleteVerticesWithRemovedIndexingLabel(transaction_.start_timestamp,
*commit_timestamp_)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushIndexCache() {
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::FlushIndexCache() {
std::vector<std::vector<PropertyValue>> unique_storage;
auto flush_index = [&](auto &index_storage) -> utils::BasicResult<StorageDataManipulationError, void> {
@@ -1552,7 +1552,7 @@ DiskStorage::DiskAccessor::ClearDanglingVertices() {
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushDeletedVertices() {
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::FlushDeletedVertices() {
auto *disk_unique_constraints =
static_cast<DiskUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
auto *disk_label_index = static_cast<DiskLabelIndex *>(storage_->indices_.label_index_.get());
@@ -1564,23 +1564,23 @@ DiskStorage::DiskAccessor::ClearDanglingVertices() {
!disk_unique_constraints->ClearDeletedVertex(vertex_gid, *commit_timestamp_) ||
!disk_label_index->ClearDeletedVertex(vertex_gid, *commit_timestamp_) ||
!disk_label_property_index->ClearDeletedVertex(vertex_gid, *commit_timestamp_)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
}
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushDeletedEdges() {
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::FlushDeletedEdges() {
for (const auto &edge_to_delete : edges_to_delete_) {
if (!DeleteEdgeFromDisk(edge_to_delete)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
}
return {};
}
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::FlushModifiedEdges(
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::FlushModifiedEdges(
const auto &edge_acc) {
for (const auto &modified_edge : transaction_.modified_edges_) {
const storage::Gid &gid = modified_edge.first;
@@ -1592,21 +1592,21 @@ DiskStorage::DiskAccessor::ClearDanglingVertices() {
/// If the object was created then flush it, otherwise since properties on edges are false
/// edge wasn't modified for sure.
if (action == Delta::Action::DELETE_OBJECT && !WriteEdgeToDisk(ser_edge_key, "")) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
} else {
// If the delta is DELETE_OBJECT, the edge is just created so there is nothing to delete.
// If the edge was deserialized, only properties can be modified -> key stays the same as when deserialized
// so we can delete it.
if (action == Delta::Action::DELETE_DESERIALIZED_OBJECT && !DeleteEdgeFromDisk(ser_edge_key)) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
const auto &edge = edge_acc.find(gid);
MG_ASSERT(edge != edge_acc.end(),
"Database in invalid state, commit not possible! Please restart your DB and start the import again.");
if (!WriteEdgeToDisk(ser_edge_key, utils::SerializeProperties(edge->properties))) {
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
}
}
@@ -1661,7 +1661,7 @@ DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label,
}
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor::Commit(
utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@@ -1669,10 +1669,84 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
auto *disk_storage = static_cast<DiskStorage *>(storage_);
bool edge_import_mode_active = disk_storage->edge_import_status_ == EdgeImportMode::ACTIVE;
if (transaction_.deltas.use().empty() ||
(!edge_import_mode_active &&
std::all_of(transaction_.deltas.use().begin(), transaction_.deltas.use().end(),
[](const Delta &delta) { return delta.action == Delta::Action::DELETE_DESERIALIZED_OBJECT; }))) {
if (!transaction_.md_deltas.empty()) {
// This is usually done by the MVCC, but it does not handle the metadata deltas
transaction_.EnsureCommitTimestampExists();
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp));
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
for (const auto &md_delta : transaction_.md_deltas) {
switch (md_delta.action) {
case MetadataDelta::Action::LABEL_INDEX_CREATE: {
if (!disk_storage->PersistLabelIndexCreation(md_delta.label)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_CREATE: {
const auto &info = md_delta.label_property;
if (!disk_storage->PersistLabelPropertyIndexAndExistenceConstraintCreation(info.label, info.property,
label_property_index_str)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::LABEL_INDEX_DROP: {
if (!disk_storage->PersistLabelIndexDeletion(md_delta.label)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_DROP: {
const auto &info = md_delta.label_property;
if (!disk_storage->PersistLabelPropertyIndexAndExistenceConstraintDeletion(info.label, info.property,
label_property_index_str)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::LABEL_INDEX_STATS_SET: {
throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage.");
} break;
case MetadataDelta::Action::LABEL_INDEX_STATS_CLEAR: {
throw utils::NotYetImplemented("ClearIndexStats(stats) is not implemented for DiskStorage.");
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_STATS_SET: {
throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage.");
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_STATS_CLEAR: {
throw utils::NotYetImplemented("ClearIndexStats(stats) is not implemented for DiskStorage.");
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_property;
if (!disk_storage->PersistLabelPropertyIndexAndExistenceConstraintCreation(info.label, info.property,
existence_constraints_str)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_DROP: {
const auto &info = md_delta.label_property;
if (!disk_storage->PersistLabelPropertyIndexAndExistenceConstraintDeletion(info.label, info.property,
existence_constraints_str)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::UNIQUE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_properties;
if (!disk_storage->PersistUniqueConstraintCreation(info.label, info.properties)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
case MetadataDelta::Action::UNIQUE_CONSTRAINT_DROP: {
const auto &info = md_delta.label_properties;
if (!disk_storage->PersistUniqueConstraintDeletion(info.label, info.properties)) {
return StorageManipulationError{PersistenceError{}};
}
} break;
}
}
} else if (transaction_.deltas.use().empty() ||
(!edge_import_mode_active &&
std::all_of(transaction_.deltas.use().begin(), transaction_.deltas.use().end(), [](const Delta &delta) {
return delta.action == Delta::Action::DELETE_DESERIALIZED_OBJECT;
}))) {
} else {
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp));
@@ -1726,7 +1800,7 @@ utils::BasicResult<StorageDataManipulationError, void> DiskStorage::DiskAccessor
disk_transaction_ = nullptr;
if (!commitStatus.ok()) {
spdlog::error("rocksdb: Commit failed with status {}", commitStatus.ToString());
return StorageDataManipulationError{SerializationError{}};
return StorageManipulationError{SerializationError{}};
}
spdlog::trace("rocksdb: Commit successful");
@@ -1871,156 +1945,122 @@ void DiskStorage::DiskAccessor::FinalizeTransaction() {
}
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::CreateIndex(
LabelId label, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *disk_label_index = static_cast<DiskLabelIndex *>(indices_.label_index_.get());
if (!disk_label_index->CreateIndex(label, SerializeVerticesForLabelIndex(label))) {
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get());
if (!disk_label_index->CreateIndex(label, on_disk->SerializeVerticesForLabelIndex(label))) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
if (!PersistLabelIndexCreation(label)) {
return StorageIndexDefinitionError{IndexPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_create, label);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelIndices);
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::CreateIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *disk_label_property_index = static_cast<DiskLabelPropertyIndex *>(indices_.label_property_index_.get());
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(LabelId label,
PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_property_index =
static_cast<DiskLabelPropertyIndex *>(on_disk->indices_.label_property_index_.get());
if (!disk_label_property_index->CreateIndex(label, property,
SerializeVerticesForLabelPropertyIndex(label, property))) {
on_disk->SerializeVerticesForLabelPropertyIndex(label, property))) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
if (!PersistLabelPropertyIndexAndExistenceConstraintCreation(label, property, label_property_index_str)) {
return StorageIndexDefinitionError{IndexPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_create, label, property);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelPropertyIndices);
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DropIndex(
LabelId label, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_index_->DropIndex(label)) {
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::DropIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get());
if (!disk_label_index->DropIndex(label)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
if (!PersistLabelIndexDeletion(label)) {
return StorageIndexDefinitionError{IndexPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_drop, label);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelIndices);
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DropIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_property_index_->DropIndex(label, property)) {
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::DropIndex(LabelId label,
PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_property_index =
static_cast<DiskLabelPropertyIndex *>(on_disk->indices_.label_property_index_.get());
if (!disk_label_property_index->DropIndex(label, property)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
if (!PersistLabelPropertyIndexAndExistenceConstraintDeletion(label, property, label_property_index_str)) {
return StorageIndexDefinitionError{IndexPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_drop, label, property);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelPropertyIndices);
return {};
}
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> DiskStorage::CreateExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (constraints_.existence_constraints_->ConstraintExists(label, property)) {
utils::BasicResult<StorageExistenceConstraintDefinitionError, void>
DiskStorage::DiskAccessor::CreateExistenceConstraint(LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create existence constraint requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *existence_constraints = on_disk->constraints_.existence_constraints_.get();
if (existence_constraints->ConstraintExists(label, property)) {
return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}};
}
if (auto check = CheckExistingVerticesBeforeCreatingExistenceConstraint(label, property); check.has_value()) {
if (auto check = on_disk->CheckExistingVerticesBeforeCreatingExistenceConstraint(label, property);
check.has_value()) {
return StorageExistenceConstraintDefinitionError{check.value()};
}
constraints_.existence_constraints_->InsertConstraint(label, property);
if (!PersistLabelPropertyIndexAndExistenceConstraintCreation(label, property, existence_constraints_str)) {
return StorageExistenceConstraintDefinitionError{ConstraintsPersistenceError{}};
}
existence_constraints->InsertConstraint(label, property);
transaction_.md_deltas.emplace_back(MetadataDelta::existence_constraint_create, label, property);
return {};
}
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DiskStorage::DropExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> /*desired_commit_timestamp*/) {
if (!constraints_.existence_constraints_->DropConstraint(label, property)) {
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DiskStorage::DiskAccessor::DropExistenceConstraint(
LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Drop existence constraint requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *existence_constraints = on_disk->constraints_.existence_constraints_.get();
if (!existence_constraints->DropConstraint(label, property)) {
return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}};
}
if (!PersistLabelPropertyIndexAndExistenceConstraintDeletion(label, property, existence_constraints_str)) {
return StorageExistenceConstraintDroppingError{ConstraintsPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::existence_constraint_drop, label, property);
return {};
}
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
DiskStorage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *disk_unique_constraints = static_cast<DiskUniqueConstraints *>(constraints_.unique_constraints_.get());
DiskStorage::DiskAccessor::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Create unique constraint requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_unique_constraints = static_cast<DiskUniqueConstraints *>(on_disk->constraints_.unique_constraints_.get());
if (auto constraint_check = disk_unique_constraints->CheckIfConstraintCanBeCreated(label, properties);
constraint_check != UniqueConstraints::CreationStatus::SUCCESS) {
return constraint_check;
}
auto check = CheckExistingVerticesBeforeCreatingUniqueConstraint(label, properties);
auto check = on_disk->CheckExistingVerticesBeforeCreatingUniqueConstraint(label, properties);
if (check.HasError()) {
return StorageUniqueConstraintDefinitionError{check.GetError()};
}
if (!disk_unique_constraints->InsertConstraint(label, properties, check.GetValue())) {
return StorageUniqueConstraintDefinitionError{ConstraintDefinitionError{}};
}
if (!PersistUniqueConstraintCreation(label, properties)) {
return StorageUniqueConstraintDefinitionError{ConstraintsPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::unique_constraint_create, label, properties);
return UniqueConstraints::CreationStatus::SUCCESS;
}
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus>
DiskStorage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> /*desired_commit_timestamp*/) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = constraints_.unique_constraints_->DropConstraint(label, properties);
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Drop unique constraint requires a unique access to the storage!");
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_unique_constraints = static_cast<DiskUniqueConstraints *>(on_disk->constraints_.unique_constraints_.get());
if (auto ret = disk_unique_constraints->DropConstraint(label, properties);
ret != UniqueConstraints::DeletionStatus::SUCCESS) {
return ret;
}
if (!PersistUniqueConstraintDeletion(label, properties)) {
return StorageUniqueConstraintDroppingError{ConstraintsPersistenceError{}};
}
transaction_.md_deltas.emplace_back(MetadataDelta::unique_constraint_create, label, properties);
return UniqueConstraints::DeletionStatus::SUCCESS;
}
@@ -2050,4 +2090,32 @@ uint64_t DiskStorage::CommitTimestamp(const std::optional<uint64_t> desired_comm
return *desired_commit_timestamp;
}
std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level) {
auto isolation_level = override_isolation_level.value_or(isolation_level_);
if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
}
return std::unique_ptr<DiskAccessor>(
new DiskAccessor{Storage::Accessor::shared_access, this, isolation_level, storage_mode_});
}
std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level) {
auto isolation_level = override_isolation_level.value_or(isolation_level_);
if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
}
return std::unique_ptr<DiskAccessor>(
new DiskAccessor{Storage::Accessor::unique_access, this, isolation_level, storage_mode_});
}
IndicesInfo DiskStorage::DiskAccessor::ListAllIndices() const {
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get());
auto *disk_label_property_index =
static_cast<DiskLabelPropertyIndex *>(on_disk->indices_.label_property_index_.get());
return {disk_label_index->ListIndices(), disk_label_property_index->ListIndices()};
}
ConstraintsInfo DiskStorage::DiskAccessor::ListAllConstraints() const {
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return {disk_storage->constraints_.existence_constraints_->ListConstraints(),
disk_storage->constraints_.unique_constraints_->ListConstraints()};
}
} // namespace memgraph::storage

View File

@@ -44,7 +44,7 @@ class DiskStorage final : public Storage {
private:
friend class DiskStorage;
explicit DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
explicit DiskAccessor(auto tag, DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
/// TODO: const methods?
void LoadVerticesToMainMemoryCache();
@@ -145,20 +145,12 @@ class DiskStorage final : public Storage {
return {};
}
std::vector<LabelId> ClearLabelIndexStats() override {
throw utils::NotYetImplemented("ClearIndexStats() is not implemented for DiskStorage.");
}
std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() override {
throw utils::NotYetImplemented("ClearIndexStats() is not implemented for DiskStorage.");
}
std::vector<LabelId> DeleteLabelIndexStats(std::span<std::string> /*labels*/) override {
bool DeleteLabelIndexStats(const storage::LabelId & /*labels*/) override {
throw utils::NotYetImplemented("DeleteIndexStatsForLabels(labels) is not implemented for DiskStorage.");
}
std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats(
const std::span<std::string> /*labels*/) override {
const storage::LabelId & /*labels*/) override {
throw utils::NotYetImplemented("DeleteIndexStatsForLabels(labels) is not implemented for DiskStorage.");
}
@@ -194,18 +186,12 @@ class DiskStorage final : public Storage {
return disk_storage->indices_.label_property_index_->IndexExists(label, property);
}
IndicesInfo ListAllIndices() const override {
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return disk_storage->ListAllIndices();
}
IndicesInfo ListAllIndices() const override;
ConstraintsInfo ListAllConstraints() const override {
auto *disk_storage = static_cast<DiskStorage *>(storage_);
return disk_storage->ListAllConstraints();
}
ConstraintsInfo ListAllConstraints() const override;
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageDataManipulationError, void> Commit(
utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) override;
void UpdateObjectsCountOnAbort();
@@ -227,6 +213,26 @@ class DiskStorage final : public Storage {
std::optional<storage::EdgeAccessor> DeserializeEdge(const rocksdb::Slice &key, const rocksdb::Slice &value,
const rocksdb::Slice &ts);
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) override;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) override;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) override;
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property) override;
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property) override;
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) override;
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label,
const std::set<PropertyId> &properties) override;
private:
VertexAccessor CreateVertexFromDisk(utils::SkipList<Vertex>::Accessor &accessor, storage::Gid gid,
std::vector<LabelId> &&label_ids, PropertyStore &&properties, Delta *delta);
@@ -242,20 +248,20 @@ class DiskStorage final : public Storage {
/// At the time of calling, the commit_timestamp_ must already exist.
/// After this method, the vertex and edge caches are cleared.
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushIndexCache();
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushIndexCache();
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushDeletedVertices();
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushDeletedVertices();
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushDeletedEdges();
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushDeletedEdges();
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushVertices(
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushVertices(
const auto &vertex_acc, std::vector<std::vector<PropertyValue>> &unique_storage);
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> FlushModifiedEdges(const auto &edge_acc);
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushModifiedEdges(const auto &edge_acc);
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> ClearDanglingVertices();
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> ClearDanglingVertices();
[[nodiscard]] utils::BasicResult<StorageDataManipulationError, void> CheckVertexConstraintsBeforeCommit(
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> CheckVertexConstraintsBeforeCommit(
const Vertex &vertex, std::vector<std::vector<PropertyValue>> &unique_storage) const;
bool WriteVertexToDisk(const Vertex &vertex);
@@ -290,42 +296,14 @@ class DiskStorage final : public Storage {
std::vector<std::pair<std::string, std::string>> vertices_to_delete_;
rocksdb::Transaction *disk_transaction_;
bool scanned_all_vertices_ = false;
};
}; // Accessor
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override {
auto isolation_level = override_isolation_level.value_or(isolation_level_);
if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
}
return std::unique_ptr<DiskAccessor>(new DiskAccessor{this, isolation_level, storage_mode_});
}
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) override;
RocksDBStorage *GetRocksDBStorage() const { return kvstore_.get(); }
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override;
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override;
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
void SetEdgeImportMode(EdgeImportMode edge_import_status);
@@ -379,21 +357,23 @@ class DiskStorage final : public Storage {
StorageInfo GetInfo() const override;
void FreeMemory(std::unique_lock<utils::RWLock> /*lock*/) override {}
void FreeMemory(std::unique_lock<utils::ResourceLock> /*lock*/) override {}
void EstablishNewEpoch() override { throw utils::BasicException("Disk storage mode does not support replication."); }
void PrepareForNewEpoch(std::string prev_epoch) override {
throw utils::BasicException("Disk storage mode does not support replication.");
}
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
EdgeImportMode edge_import_status_{EdgeImportMode::INACTIVE};
std::unique_ptr<EdgeImportModeCache> edge_import_mode_cache_{nullptr};
auto CreateReplicationClient(replication::ReplicationClientConfig const &config)
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
-> std::unique_ptr<ReplicationClient> override {
throw utils::BasicException("Disk storage mode does not support replication.");
}
auto CreateReplicationServer(const replication::ReplicationServerConfig &config)
auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> override {
throw utils::BasicException("Disk storage mode does not support replication.");
}

View File

@@ -24,6 +24,7 @@
#include <utility>
#include <vector>
#include "replication/epoch.hpp"
#include "storage/v2/durability/paths.hpp"
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/durability/wal.hpp"
@@ -133,8 +134,8 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
spdlog::info("Recreating indices from metadata.");
// Recover label indices.
spdlog::info("Recreating {} label indices from metadata.", indices_constraints.indices.label.size());
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(indices->label_index_.get());
for (const auto &item : indices_constraints.indices.label) {
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(indices->label_index_.get());
if (!mem_label_index->CreateIndex(item, vertices->access(), parallel_exec_info))
throw RecoveryFailure("The label index must be created here!");
@@ -142,6 +143,15 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
}
spdlog::info("Label indices are recreated.");
spdlog::info("Recreating index statistics from metadata.");
// Recover label indices statistics.
spdlog::info("Recreating {} label index statistics from metadata.", indices_constraints.indices.label_stats.size());
for (const auto &item : indices_constraints.indices.label_stats) {
mem_label_index->SetIndexStats(item.first, item.second);
spdlog::info("A label index statistics is recreated from metadata.");
}
spdlog::info("Label indices statistics are recreated.");
// Recover label+property indices.
spdlog::info("Recreating {} label+property indices from metadata.",
indices_constraints.indices.label_property.size());
@@ -152,6 +162,19 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
spdlog::info("A label+property index is recreated from metadata.");
}
spdlog::info("Label+property indices are recreated.");
// Recover label+property indices statistics.
spdlog::info("Recreating {} label+property indices statistics from metadata.",
indices_constraints.indices.label_property_stats.size());
for (const auto &item : indices_constraints.indices.label_property_stats) {
const auto label_id = item.first;
const auto property_id = item.second.first;
const auto &stats = item.second.second;
mem_label_property_index->SetIndexStats({label_id, property_id}, stats);
spdlog::info("A label+property index statistics is recreated from metadata.");
}
spdlog::info("Label+property indices statistics are recreated.");
spdlog::info("Indices are recreated.");
spdlog::info("Recreating constraints from metadata.");
@@ -188,7 +211,7 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
std::string *epoch_id,
memgraph::replication::ReplicationEpoch &epoch,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,
@@ -241,7 +264,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
recovery_info = recovered_snapshot->recovery_info;
indices_constraints = std::move(recovered_snapshot->indices_constraints);
snapshot_timestamp = recovered_snapshot->snapshot_info.start_timestamp;
*epoch_id = std::move(recovered_snapshot->snapshot_info.epoch_id);
epoch.SetEpoch(std::move(recovered_snapshot->snapshot_info.epoch_id));
if (!utils::DirExists(wal_directory)) {
const auto par_exec_info = config.durability.allow_parallel_index_creation
@@ -286,7 +309,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
// UUID used for durability is the UUID of the last WAL file.
// Same for the epoch id.
*uuid = std::move(wal_files.back().uuid);
*epoch_id = std::move(wal_files.back().epoch_id);
epoch.SetEpoch(std::move(wal_files.back().epoch_id));
}
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
@@ -342,7 +365,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
}
previous_seq_num = wal_file.seq_num;
if (wal_file.epoch_id != *epoch_id) {
if (wal_file.epoch_id != epoch.id()) {
// This way we skip WALs finalized only because of role change.
// We can also set the last timestamp to 0 if last loaded timestamp
// is nullopt as this can only happen if the WAL file with seq = 0
@@ -350,7 +373,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
if (last_loaded_timestamp) {
epoch_history->emplace_back(wal_file.epoch_id, *last_loaded_timestamp);
}
*epoch_id = std::move(wal_file.epoch_id);
epoch.SetEpoch(std::move(wal_file.epoch_id));
}
try {
auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,

View File

@@ -18,6 +18,7 @@
#include <string>
#include <variant>
#include "replication/epoch.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/durability/metadata.hpp"
@@ -109,7 +110,7 @@ void RecoverIndicesAndConstraints(
/// @throw std::bad_alloc
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
std::string *epoch_id,
memgraph::replication::ReplicationEpoch &epoch,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -56,6 +56,10 @@ enum class Marker : uint8_t {
DELTA_EXISTENCE_CONSTRAINT_DROP = 0x5e,
DELTA_UNIQUE_CONSTRAINT_CREATE = 0x5f,
DELTA_UNIQUE_CONSTRAINT_DROP = 0x60,
DELTA_LABEL_INDEX_STATS_SET = 0x61,
DELTA_LABEL_INDEX_STATS_CLEAR = 0x62,
DELTA_LABEL_PROPERTY_INDEX_STATS_SET = 0x63,
DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR = 0x64,
VALUE_FALSE = 0x00,
VALUE_TRUE = 0xff,
@@ -93,6 +97,10 @@ static const Marker kMarkersAll[] = {
Marker::DELTA_TRANSACTION_END,
Marker::DELTA_LABEL_INDEX_CREATE,
Marker::DELTA_LABEL_INDEX_DROP,
Marker::DELTA_LABEL_INDEX_STATS_SET,
Marker::DELTA_LABEL_INDEX_STATS_CLEAR,
Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET,
Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR,
Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE,
Marker::DELTA_LABEL_PROPERTY_INDEX_DROP,
Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE,

View File

@@ -19,6 +19,8 @@
#include "storage/v2/durability/exceptions.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
namespace memgraph::storage::durability {
@@ -39,6 +41,8 @@ struct RecoveredIndicesAndConstraints {
struct {
std::vector<LabelId> label;
std::vector<std::pair<LabelId, PropertyId>> label_property;
std::vector<std::pair<LabelId, LabelIndexStats>> label_stats;
std::vector<std::pair<LabelId, std::pair<PropertyId, LabelPropertyIndexStats>>> label_property_stats;
} indices;
struct {
@@ -74,4 +78,19 @@ void RemoveRecoveredIndexConstraint(std::vector<TObj> *list, TObj obj, const cha
}
}
// Helper function used to remove indices stats from the recovered
// indices/constraints object.
// @note multiple stats can be pushed one after the other; when removing, remove from the back
// @throw RecoveryFailure
template <typename TObj, typename K>
void RemoveRecoveredIndexStats(std::vector<TObj> *list, K label, const char *error_message) {
for (auto it = list->rbegin(); it != list->rend(); ++it) {
if (it->first == label) {
list->erase(std::next(it).base()); // erase using a reverse iterator
return;
}
}
throw RecoveryFailure(error_message);
}
} // namespace memgraph::storage::durability

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,7 +22,6 @@ static const std::string kSnapshotDirectory{"snapshots"};
static const std::string kWalDirectory{"wal"};
static const std::string kBackupDirectory{".backup"};
static const std::string kLockFile{".lock"};
static const std::string kReplicationDirectory{"replication"};
// This is the prefix used for Snapshot and WAL filenames. It is a timestamp
// format that equals to: YYYYmmddHHMMSSffffff

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -344,6 +344,10 @@ std::optional<PropertyValue> Decoder::ReadPropertyValue() {
case Marker::DELTA_TRANSACTION_END:
case Marker::DELTA_LABEL_INDEX_CREATE:
case Marker::DELTA_LABEL_INDEX_DROP:
case Marker::DELTA_LABEL_INDEX_STATS_SET:
case Marker::DELTA_LABEL_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET:
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
@@ -443,6 +447,10 @@ bool Decoder::SkipPropertyValue() {
case Marker::DELTA_TRANSACTION_END:
case Marker::DELTA_LABEL_INDEX_CREATE:
case Marker::DELTA_LABEL_INDEX_DROP:
case Marker::DELTA_LABEL_INDEX_STATS_SET:
case Marker::DELTA_LABEL_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET:
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:

View File

@@ -23,6 +23,10 @@
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/edge_ref.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "storage/v2/inmemory/label_index.hpp"
#include "storage/v2/inmemory/label_property_index.hpp"
#include "storage/v2/mvcc.hpp"
#include "storage/v2/vertex.hpp"
#include "storage/v2/vertex_accessor.hpp"
@@ -1077,10 +1081,11 @@ RecoveredSnapshot LoadSnapshotVersion14(const std::filesystem::path &path, utils
return {info, ret, std::move(indices_constraints)};
}
RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, const Config &config) {
RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count,
const Config &config) {
RecoveryInfo recovery_info;
RecoveredIndicesAndConstraints indices_constraints;
@@ -1089,9 +1094,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!");
if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version));
if (*version == 14U) {
return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config.items);
}
if (*version != 15U) throw RecoveryFailure(fmt::format("Expected snapshot version is 15, but got {}", *version));
// Cleanup of loaded data in case of failure.
bool success = false;
@@ -1364,11 +1367,355 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
return {info, recovery_info, std::move(indices_constraints)};
}
RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, const Config &config) {
RecoveryInfo recovery_info;
RecoveredIndicesAndConstraints indices_constraints;
Decoder snapshot;
const auto version = snapshot.Initialize(path, kSnapshotMagic);
if (!version) throw RecoveryFailure("Couldn't read snapshot magic and/or version!");
if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version));
if (*version == 14U) {
return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config.items);
}
if (*version == 15U) {
return LoadSnapshotVersion15(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config);
}
// Cleanup of loaded data in case of failure.
bool success = false;
utils::OnScopeExit cleanup([&] {
if (!success) {
edges->clear();
vertices->clear();
epoch_history->clear();
}
});
// Read snapshot info.
const auto info = ReadSnapshotInfo(path);
spdlog::info("Recovering {} vertices and {} edges.", info.vertices_count, info.edges_count);
// Check for edges.
bool snapshot_has_edges = info.offset_edges != 0;
// Recover mapper.
std::unordered_map<uint64_t, uint64_t> snapshot_id_map;
{
spdlog::info("Recovering mapper metadata.");
if (!snapshot.SetPosition(info.offset_mapper)) throw RecoveryFailure("Couldn't read data from snapshot!");
auto marker = snapshot.ReadMarker();
if (!marker || *marker != Marker::SECTION_MAPPER) throw RecoveryFailure("Invalid snapshot data!");
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
for (uint64_t i = 0; i < *size; ++i) {
auto id = snapshot.ReadUint();
if (!id) throw RecoveryFailure("Invalid snapshot data!");
auto name = snapshot.ReadString();
if (!name) throw RecoveryFailure("Invalid snapshot data!");
auto my_id = name_id_mapper->NameToId(*name);
snapshot_id_map.emplace(*id, my_id);
SPDLOG_TRACE("Mapping \"{}\"from snapshot id {} to actual id {}.", *name, *id, my_id);
}
}
auto get_label_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
auto it = snapshot_id_map.find(snapshot_id);
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
return LabelId::FromUint(it->second);
};
auto get_property_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
auto it = snapshot_id_map.find(snapshot_id);
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
return PropertyId::FromUint(it->second);
};
auto get_edge_type_from_id = [&snapshot_id_map](uint64_t snapshot_id) {
auto it = snapshot_id_map.find(snapshot_id);
if (it == snapshot_id_map.end()) throw RecoveryFailure("Invalid snapshot data!");
return EdgeTypeId::FromUint(it->second);
};
// Reset current edge count.
edge_count->store(0, std::memory_order_release);
{
spdlog::info("Recovering edges.");
// Recover edges.
if (snapshot_has_edges) {
// We don't need to check whether we store properties on edge or not, because `LoadPartialEdges` will always
// iterate over the edges in the snapshot (if they exist) and the current configuration of properties on edge only
// affect what it does:
// 1. If properties are allowed on edges, then it loads the edges.
// 2. If properties are not allowed on edges, then it checks that none of the edges have any properties.
if (!snapshot.SetPosition(info.offset_edge_batches)) {
throw RecoveryFailure("Couldn't read data from snapshot!");
}
const auto edge_batches = ReadBatchInfos(snapshot);
RecoverOnMultipleThreads(
config.durability.recovery_thread_count,
[path, edges, items = config.items, &get_property_from_id](const size_t /*batch_index*/,
const BatchInfo &batch) {
LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id);
},
edge_batches);
}
spdlog::info("Edges are recovered.");
// Recover vertices (labels and properties).
spdlog::info("Recovering vertices.", info.vertices_count);
uint64_t last_vertex_gid{0};
if (!snapshot.SetPosition(info.offset_vertex_batches)) {
throw RecoveryFailure("Couldn't read data from snapshot!");
}
const auto vertex_batches = ReadBatchInfos(snapshot);
RecoverOnMultipleThreads(
config.durability.recovery_thread_count,
[path, vertices, &vertex_batches, &get_label_from_id, &get_property_from_id, &last_vertex_gid](
const size_t batch_index, const BatchInfo &batch) {
const auto last_vertex_gid_in_batch =
LoadPartialVertices(path, *vertices, batch.offset, batch.count, get_label_from_id, get_property_from_id);
if (batch_index == vertex_batches.size() - 1) {
last_vertex_gid = last_vertex_gid_in_batch;
}
},
vertex_batches);
spdlog::info("Vertices are recovered.");
// Recover vertices (in/out edges).
spdlog::info("Recover connectivity.");
recovery_info.vertex_batches.reserve(vertex_batches.size());
for (const auto batch : vertex_batches) {
recovery_info.vertex_batches.emplace_back(std::make_pair(Gid::FromUint(0), batch.count));
}
std::atomic<uint64_t> highest_edge_gid{0};
RecoverOnMultipleThreads(
config.durability.recovery_thread_count,
[path, vertices, edges, edge_count, items = config.items, snapshot_has_edges, &get_edge_type_from_id,
&highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) {
const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items,
snapshot_has_edges, get_edge_type_from_id);
edge_count->fetch_add(result.edge_count);
auto known_highest_edge_gid = highest_edge_gid.load();
while (known_highest_edge_gid < result.highest_edge_id) {
highest_edge_gid.compare_exchange_weak(known_highest_edge_gid, result.highest_edge_id);
}
recovery_info.vertex_batches[batch_index].first = result.first_vertex_gid;
},
vertex_batches);
spdlog::info("Connectivity is recovered.");
// Set initial values for edge/vertex ID generators.
recovery_info.next_edge_id = highest_edge_gid + 1;
recovery_info.next_vertex_id = last_vertex_gid + 1;
}
// Recover indices.
{
spdlog::info("Recovering metadata of indices.");
if (!snapshot.SetPosition(info.offset_indices)) throw RecoveryFailure("Couldn't read data from snapshot!");
auto marker = snapshot.ReadMarker();
if (!marker || *marker != Marker::SECTION_INDICES) throw RecoveryFailure("Invalid snapshot data!");
// Recover label indices.
{
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} label indices.", *size);
for (uint64_t i = 0; i < *size; ++i) {
const auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
AddRecoveredIndexConstraint(&indices_constraints.indices.label, get_label_from_id(*label),
"The label index already exists!");
SPDLOG_TRACE("Recovered metadata of label index for :{}", name_id_mapper->IdToName(snapshot_id_map.at(*label)));
}
spdlog::info("Metadata of label indices are recovered.");
}
// Recover label indices statistics.
{
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} label indices statistics.", *size);
for (uint64_t i = 0; i < *size; ++i) {
const auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
const auto count = snapshot.ReadUint();
if (!count) throw RecoveryFailure("Invalid snapshot data!");
const auto avg_degree = snapshot.ReadDouble();
if (!avg_degree) throw RecoveryFailure("Invalid snapshot data!");
const auto label_id = get_label_from_id(*label);
indices_constraints.indices.label_stats.emplace_back(label_id, LabelIndexStats{*count, *avg_degree});
SPDLOG_TRACE("Recovered metadata of label index statistics for :{}",
name_id_mapper->IdToName(snapshot_id_map.at(*label)));
}
spdlog::info("Metadata of label indices are recovered.");
}
// Recover label+property indices.
{
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} label+property indices.", *size);
for (uint64_t i = 0; i < *size; ++i) {
const auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
const auto property = snapshot.ReadUint();
if (!property) throw RecoveryFailure("Invalid snapshot data!");
AddRecoveredIndexConstraint(&indices_constraints.indices.label_property,
{get_label_from_id(*label), get_property_from_id(*property)},
"The label+property index already exists!");
SPDLOG_TRACE("Recovered metadata of label+property index for :{}({})",
name_id_mapper->IdToName(snapshot_id_map.at(*label)),
name_id_mapper->IdToName(snapshot_id_map.at(*property)));
}
spdlog::info("Metadata of label+property indices are recovered.");
}
// Recover label+property indices statistics.
{
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} label+property indices statistics.", *size);
for (uint64_t i = 0; i < *size; ++i) {
const auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
const auto property = snapshot.ReadUint();
if (!property) throw RecoveryFailure("Invalid snapshot data!");
const auto count = snapshot.ReadUint();
if (!count) throw RecoveryFailure("Invalid snapshot data!");
const auto distinct_values_count = snapshot.ReadUint();
if (!distinct_values_count) throw RecoveryFailure("Invalid snapshot data!");
const auto statistic = snapshot.ReadDouble();
if (!statistic) throw RecoveryFailure("Invalid snapshot data!");
const auto avg_group_size = snapshot.ReadDouble();
if (!avg_group_size) throw RecoveryFailure("Invalid snapshot data!");
const auto avg_degree = snapshot.ReadDouble();
if (!avg_degree) throw RecoveryFailure("Invalid snapshot data!");
const auto label_id = get_label_from_id(*label);
const auto property_id = get_property_from_id(*property);
indices_constraints.indices.label_property_stats.emplace_back(
label_id, std::make_pair(property_id, LabelPropertyIndexStats{*count, *distinct_values_count, *statistic,
*avg_group_size, *avg_degree}));
SPDLOG_TRACE("Recovered metadata of label+property index statistics for :{}({})",
name_id_mapper->IdToName(snapshot_id_map.at(*label)),
name_id_mapper->IdToName(snapshot_id_map.at(*property)));
}
spdlog::info("Metadata of label+property indices are recovered.");
}
spdlog::info("Metadata of indices are recovered.");
}
// Recover constraints.
{
spdlog::info("Recovering metadata of constraints.");
if (!snapshot.SetPosition(info.offset_constraints)) throw RecoveryFailure("Couldn't read data from snapshot!");
auto marker = snapshot.ReadMarker();
if (!marker || *marker != Marker::SECTION_CONSTRAINTS) throw RecoveryFailure("Invalid snapshot data!");
// Recover existence constraints.
{
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} existence constraints.", *size);
for (uint64_t i = 0; i < *size; ++i) {
auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
auto property = snapshot.ReadUint();
if (!property) throw RecoveryFailure("Invalid snapshot data!");
AddRecoveredIndexConstraint(&indices_constraints.constraints.existence,
{get_label_from_id(*label), get_property_from_id(*property)},
"The existence constraint already exists!");
SPDLOG_TRACE("Recovered metadata of existence constraint for :{}({})",
name_id_mapper->IdToName(snapshot_id_map.at(*label)),
name_id_mapper->IdToName(snapshot_id_map.at(*property)));
}
spdlog::info("Metadata of existence constraints are recovered.");
}
// Recover unique constraints.
// Snapshot version should be checked since unique constraints were
// implemented in later versions of snapshot.
if (*version >= kUniqueConstraintVersion) {
auto size = snapshot.ReadUint();
if (!size) throw RecoveryFailure("Invalid snapshot data!");
spdlog::info("Recovering metadata of {} unique constraints.", *size);
for (uint64_t i = 0; i < *size; ++i) {
auto label = snapshot.ReadUint();
if (!label) throw RecoveryFailure("Invalid snapshot data!");
auto properties_count = snapshot.ReadUint();
if (!properties_count) throw RecoveryFailure("Invalid snapshot data!");
std::set<PropertyId> properties;
for (uint64_t j = 0; j < *properties_count; ++j) {
auto property = snapshot.ReadUint();
if (!property) throw RecoveryFailure("Invalid snapshot data!");
properties.insert(get_property_from_id(*property));
}
AddRecoveredIndexConstraint(&indices_constraints.constraints.unique, {get_label_from_id(*label), properties},
"The unique constraint already exists!");
SPDLOG_TRACE("Recovered metadata of unique constraints for :{}",
name_id_mapper->IdToName(snapshot_id_map.at(*label)));
}
spdlog::info("Metadata of unique constraints are recovered.");
}
spdlog::info("Metadata of constraints are recovered.");
}
spdlog::info("Recovering metadata.");
// Recover epoch history
{
if (!snapshot.SetPosition(info.offset_epoch_history)) throw RecoveryFailure("Couldn't read data from snapshot!");
const auto marker = snapshot.ReadMarker();
if (!marker || *marker != Marker::SECTION_EPOCH_HISTORY) throw RecoveryFailure("Invalid snapshot data!");
const auto history_size = snapshot.ReadUint();
if (!history_size) {
throw RecoveryFailure("Invalid snapshot data!");
}
for (int i = 0; i < *history_size; ++i) {
auto maybe_epoch_id = snapshot.ReadString();
if (!maybe_epoch_id) {
throw RecoveryFailure("Invalid snapshot data!");
}
const auto maybe_last_commit_timestamp = snapshot.ReadUint();
if (!maybe_last_commit_timestamp) {
throw RecoveryFailure("Invalid snapshot data!");
}
epoch_history->emplace_back(std::move(*maybe_epoch_id), *maybe_last_commit_timestamp);
}
}
spdlog::info("Metadata recovered.");
// Recover timestamp.
recovery_info.next_timestamp = info.start_timestamp + 1;
// Set success flag (to disable cleanup).
success = true;
return {info, recovery_info, std::move(indices_constraints)};
}
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
Indices *indices, Constraints *constraints, const Config &config, const std::string &uuid,
const std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
const memgraph::replication::ReplicationEpoch &epoch,
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
utils::FileRetainer *file_retainer) {
// Ensure that the storage directory exists.
utils::EnsureDirOrDie(snapshot_directory);
@@ -1577,6 +1924,31 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
}
}
// Write label indices statistics.
{
// NOTE: On-disk does not support snapshots
auto *inmem_index = static_cast<InMemoryLabelIndex *>(indices->label_index_.get());
auto label = inmem_index->ListIndices();
const auto size_pos = snapshot.GetPosition();
snapshot.WriteUint(0); // Just a place holder
unsigned i = 0;
for (const auto &item : label) {
auto stats = inmem_index->GetIndexStats(item);
if (stats) {
snapshot.WriteUint(item.AsUint());
snapshot.WriteUint(stats->count);
snapshot.WriteDouble(stats->avg_degree);
++i;
}
}
if (i != 0) {
const auto last_pos = snapshot.GetPosition();
snapshot.SetPosition(size_pos);
snapshot.WriteUint(i); // Write real size
snapshot.SetPosition(last_pos);
}
}
// Write label+property indices.
{
auto label_property = indices->label_property_index_->ListIndices();
@@ -1586,6 +1958,35 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
write_mapping(item.second);
}
}
// Write label+property indices statistics.
{
// NOTE: On-disk does not support snapshots
auto *inmem_index = static_cast<InMemoryLabelPropertyIndex *>(indices->label_property_index_.get());
auto label = inmem_index->ListIndices();
const auto size_pos = snapshot.GetPosition();
snapshot.WriteUint(0); // Just a place holder
unsigned i = 0;
for (const auto &item : label) {
auto stats = inmem_index->GetIndexStats(item);
if (stats) {
snapshot.WriteUint(item.first.AsUint());
snapshot.WriteUint(item.second.AsUint());
snapshot.WriteUint(stats->count);
snapshot.WriteUint(stats->distinct_values_count);
snapshot.WriteDouble(stats->statistic);
snapshot.WriteDouble(stats->avg_group_size);
snapshot.WriteDouble(stats->avg_degree);
++i;
}
}
if (i != 0) {
const auto last_pos = snapshot.GetPosition();
snapshot.SetPosition(size_pos);
snapshot.WriteUint(i); // Write real size
snapshot.SetPosition(last_pos);
}
}
}
// Write constraints.
@@ -1644,7 +2045,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
offset_metadata = snapshot.GetPosition();
snapshot.WriteMarker(Marker::SECTION_METADATA);
snapshot.WriteString(uuid);
snapshot.WriteString(epoch_id);
snapshot.WriteString(epoch.id());
snapshot.WriteUint(transaction->start_timestamp);
snapshot.WriteUint(edges_count);
snapshot.WriteUint(vertices_count);

View File

@@ -15,6 +15,7 @@
#include <filesystem>
#include <string>
#include "replication/epoch.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/durability/metadata.hpp"
@@ -71,7 +72,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
Indices *indices, Constraints *constraints, const Config &config, const std::string &uuid,
std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
const memgraph::replication::ReplicationEpoch &epoch,
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
utils::FileRetainer *file_retainer);
} // namespace memgraph::storage::durability

View File

@@ -14,11 +14,15 @@
namespace memgraph::storage::durability {
/// Enum used to indicate a global database operation that isn't transactional.
enum class StorageGlobalOperation {
enum class StorageMetadataOperation {
LABEL_INDEX_CREATE,
LABEL_INDEX_DROP,
LABEL_INDEX_STATS_SET,
LABEL_INDEX_STATS_CLEAR,
LABEL_PROPERTY_INDEX_CREATE,
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,

View File

@@ -20,7 +20,7 @@ namespace memgraph::storage::durability {
// The current version of snapshot and WAL encoding / decoding.
// IMPORTANT: Please bump this version for every snapshot and/or WAL format
// change!!!
const uint64_t kVersion{15};
const uint64_t kVersion{16};
const uint64_t kOldestSupportedVersion{14};
const uint64_t kUniqueConstraintVersion{13};

View File

@@ -13,9 +13,11 @@
#include "storage/v2/delta.hpp"
#include "storage/v2/durability/exceptions.hpp"
#include "storage/v2/durability/metadata.hpp"
#include "storage/v2/durability/paths.hpp"
#include "storage/v2/durability/version.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/file_locker.hpp"
#include "utils/logging.hpp"
@@ -75,23 +77,31 @@ namespace memgraph::storage::durability {
namespace {
Marker OperationToMarker(StorageGlobalOperation operation) {
Marker OperationToMarker(StorageMetadataOperation operation) {
switch (operation) {
case StorageGlobalOperation::LABEL_INDEX_CREATE:
case StorageMetadataOperation::LABEL_INDEX_CREATE:
return Marker::DELTA_LABEL_INDEX_CREATE;
case StorageGlobalOperation::LABEL_INDEX_DROP:
case StorageMetadataOperation::LABEL_INDEX_DROP:
return Marker::DELTA_LABEL_INDEX_DROP;
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE:
case StorageMetadataOperation::LABEL_INDEX_STATS_SET:
return Marker::DELTA_LABEL_INDEX_STATS_SET;
case StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR:
return Marker::DELTA_LABEL_INDEX_STATS_CLEAR;
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE:
return Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE;
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP:
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP:
return Marker::DELTA_LABEL_PROPERTY_INDEX_DROP;
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE:
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_SET:
return Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET;
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR:
return Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR;
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
return Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE;
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP:
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP:
return Marker::DELTA_EXISTENCE_CONSTRAINT_DROP;
case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE:
case StorageMetadataOperation::UNIQUE_CONSTRAINT_CREATE:
return Marker::DELTA_UNIQUE_CONSTRAINT_CREATE;
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP:
case StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP:
return Marker::DELTA_UNIQUE_CONSTRAINT_DROP;
}
}
@@ -150,10 +160,18 @@ WalDeltaData::Type MarkerToWalDeltaDataType(Marker marker) {
return WalDeltaData::Type::LABEL_INDEX_CREATE;
case Marker::DELTA_LABEL_INDEX_DROP:
return WalDeltaData::Type::LABEL_INDEX_DROP;
case Marker::DELTA_LABEL_INDEX_STATS_SET:
return WalDeltaData::Type::LABEL_INDEX_STATS_SET;
case Marker::DELTA_LABEL_INDEX_STATS_CLEAR:
return WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR;
case Marker::DELTA_LABEL_PROPERTY_INDEX_CREATE:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE;
case Marker::DELTA_LABEL_PROPERTY_INDEX_DROP:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP;
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_SET:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET;
case Marker::DELTA_LABEL_PROPERTY_INDEX_STATS_CLEAR:
return WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR;
case Marker::DELTA_EXISTENCE_CONSTRAINT_CREATE:
return WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE;
case Marker::DELTA_EXISTENCE_CONSTRAINT_DROP:
@@ -263,7 +281,11 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
case WalDeltaData::Type::TRANSACTION_END:
break;
case WalDeltaData::Type::LABEL_INDEX_CREATE:
case WalDeltaData::Type::LABEL_INDEX_DROP: {
case WalDeltaData::Type::LABEL_INDEX_DROP:
case WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR: /* Special case, this clear is done on all label/property
pairs that contain the defined label */
{
if constexpr (read_data) {
auto label = decoder->ReadString();
if (!label) throw RecoveryFailure("Invalid WAL data!");
@@ -273,6 +295,18 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
}
break;
}
case WalDeltaData::Type::LABEL_INDEX_STATS_SET: {
if constexpr (read_data) {
auto label = decoder->ReadString();
if (!label) throw RecoveryFailure("Invalid WAL data!");
delta.operation_label_stats.label = std::move(*label);
auto stats = decoder->ReadString();
if (!stats) throw RecoveryFailure("Invalid WAL data!");
delta.operation_label_stats.stats = std::move(*stats);
} else {
if (!decoder->SkipString() || !decoder->SkipString()) throw RecoveryFailure("Invalid WAL data!");
}
} break;
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
@@ -289,6 +323,23 @@ WalDeltaData ReadSkipWalDeltaData(BaseDecoder *decoder) {
}
break;
}
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
if constexpr (read_data) {
auto label = decoder->ReadString();
if (!label) throw RecoveryFailure("Invalid WAL data!");
delta.operation_label_property_stats.label = std::move(*label);
auto property = decoder->ReadString();
if (!property) throw RecoveryFailure("Invalid WAL data!");
delta.operation_label_property_stats.property = std::move(*property);
auto stats = decoder->ReadString();
if (!stats) throw RecoveryFailure("Invalid WAL data!");
delta.operation_label_property_stats.stats = std::move(*stats);
} else {
if (!decoder->SkipString() || !decoder->SkipString() || !decoder->SkipString())
throw RecoveryFailure("Invalid WAL data!");
}
break;
}
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
if constexpr (read_data) {
@@ -371,11 +422,11 @@ WalInfo ReadWalInfo(const std::filesystem::path &path) {
// Read deltas.
info.num_deltas = 0;
auto validate_delta = [&wal]() -> std::optional<std::pair<uint64_t, bool>> {
auto validate_delta = [&wal, version = *version]() -> std::optional<std::pair<uint64_t, bool>> {
try {
auto timestamp = ReadWalDeltaHeader(&wal);
auto type = SkipWalDeltaData(&wal);
return {{timestamp, IsWalDeltaDataTypeTransactionEnd(type)}};
return {{timestamp, IsWalDeltaDataTypeTransactionEnd(type, version)}};
} catch (const RecoveryFailure &) {
return std::nullopt;
}
@@ -445,14 +496,28 @@ bool operator==(const WalDeltaData &a, const WalDeltaData &b) {
case WalDeltaData::Type::LABEL_INDEX_CREATE:
case WalDeltaData::Type::LABEL_INDEX_DROP:
case WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR: /* Special case, label property index stats clear just
passes the label and all label/property pairs with the
label get cleared */
return a.operation_label.label == b.operation_label.label;
case WalDeltaData::Type::LABEL_INDEX_STATS_SET:
return a.operation_label_stats.label == b.operation_label_stats.label &&
a.operation_label_stats.stats == b.operation_label_stats.stats;
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
return a.operation_label_property.label == b.operation_label_property.label &&
a.operation_label_property.property == b.operation_label_property.property;
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET:
return a.operation_label_property_stats.label == b.operation_label_property_stats.label &&
a.operation_label_property_stats.property == b.operation_label_property_stats.property &&
a.operation_label_property_stats.stats == b.operation_label_property_stats.stats;
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
return a.operation_label_properties.label == b.operation_label_properties.label &&
@@ -585,30 +650,50 @@ void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp) {
encoder->WriteMarker(Marker::DELTA_TRANSACTION_END);
}
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp) {
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageMetadataOperation operation,
LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats, uint64_t timestamp) {
encoder->WriteMarker(Marker::SECTION_DELTA);
encoder->WriteUint(timestamp);
switch (operation) {
case StorageGlobalOperation::LABEL_INDEX_CREATE:
case StorageGlobalOperation::LABEL_INDEX_DROP: {
case StorageMetadataOperation::LABEL_INDEX_CREATE:
case StorageMetadataOperation::LABEL_INDEX_DROP:
case StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR:
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR: /* Special case, this clear is done on all
label/property pairs that contain the defined
label */
{
MG_ASSERT(properties.empty(), "Invalid function call!");
encoder->WriteMarker(OperationToMarker(operation));
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
break;
}
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE:
case StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP:
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE:
case StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP: {
case StorageMetadataOperation::LABEL_INDEX_STATS_SET: {
MG_ASSERT(properties.empty(), "Invalid function call!");
encoder->WriteMarker(OperationToMarker(operation));
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
encoder->WriteString(ToJson(stats));
break;
}
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE:
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP:
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE:
case StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP: {
MG_ASSERT(properties.size() == 1, "Invalid function call!");
encoder->WriteMarker(OperationToMarker(operation));
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
encoder->WriteString(name_id_mapper->IdToName((*properties.begin()).AsUint()));
break;
}
case StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE:
case StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP: {
case StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_SET: {
encoder->WriteMarker(OperationToMarker(operation));
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
encoder->WriteString(name_id_mapper->IdToName((*properties.begin()).AsUint()));
encoder->WriteString(ToJson(property_stats));
break;
}
case StorageMetadataOperation::UNIQUE_CONSTRAINT_CREATE:
case StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP: {
MG_ASSERT(!properties.empty(), "Invalid function call!");
encoder->WriteMarker(OperationToMarker(operation));
encoder->WriteString(name_id_mapper->IdToName(label.AsUint()));
@@ -802,6 +887,21 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst
"The label index doesn't exist!");
break;
}
case WalDeltaData::Type::LABEL_INDEX_STATS_SET: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_stats.label));
LabelIndexStats stats{};
if (!FromJson(delta.operation_label_stats.stats, stats)) {
throw RecoveryFailure("Failed to read statistics!");
}
indices_constraints->indices.label_stats.emplace_back(label_id, stats);
break;
}
case WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label));
RemoveRecoveredIndexStats(&indices_constraints->indices.label_stats, label_id,
"The label index stats doesn't exist!");
break;
}
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
@@ -816,6 +916,23 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst
"The label property index doesn't exist!");
break;
}
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
auto &info = delta.operation_label_property_stats;
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(info.label));
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(info.property));
LabelPropertyIndexStats stats{};
if (!FromJson(info.stats, stats)) {
throw RecoveryFailure("Failed to read statistics!");
}
indices_constraints->indices.label_property_stats.emplace_back(label_id, std::make_pair(property_id, stats));
break;
}
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label));
RemoveRecoveredIndexStats(&indices_constraints->indices.label_property_stats, label_id,
"The label index stats doesn't exist!");
break;
}
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label));
auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property));
@@ -964,9 +1081,10 @@ void WalFile::AppendTransactionEnd(uint64_t timestamp) {
UpdateStats(timestamp);
}
void WalFile::AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
void WalFile::AppendOperation(StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties,
const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats,
uint64_t timestamp) {
EncodeOperation(&wal_, name_id_mapper_, operation, label, properties, timestamp);
EncodeOperation(&wal_, name_id_mapper_, operation, label, properties, stats, property_stats, timestamp);
UpdateStats(timestamp);
}

View File

@@ -21,8 +21,11 @@
#include "storage/v2/durability/metadata.hpp"
#include "storage/v2/durability/serialization.hpp"
#include "storage/v2/durability/storage_global_operation.hpp"
#include "storage/v2/durability/version.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "storage/v2/name_id_mapper.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/vertex.hpp"
@@ -58,8 +61,12 @@ struct WalDeltaData {
TRANSACTION_END,
LABEL_INDEX_CREATE,
LABEL_INDEX_DROP,
LABEL_INDEX_STATS_SET,
LABEL_INDEX_STATS_CLEAR,
LABEL_PROPERTY_INDEX_CREATE,
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
@@ -103,12 +110,23 @@ struct WalDeltaData {
std::string label;
std::set<std::string> properties;
} operation_label_properties;
struct {
std::string label;
std::string stats;
} operation_label_stats;
struct {
std::string label;
std::string property;
std::string stats;
} operation_label_property_stats;
};
bool operator==(const WalDeltaData &a, const WalDeltaData &b);
bool operator!=(const WalDeltaData &a, const WalDeltaData &b);
constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
constexpr bool IsWalDeltaDataTypeTransactionEndVersion15(const WalDeltaData::Type type) {
switch (type) {
// These delta actions are all found inside transactions so they don't
// indicate a transaction end.
@@ -131,16 +149,28 @@ constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type) {
// 'transaction'.
case WalDeltaData::Type::LABEL_INDEX_CREATE:
case WalDeltaData::Type::LABEL_INDEX_DROP:
case WalDeltaData::Type::LABEL_INDEX_STATS_SET:
case WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET:
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE:
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE:
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP:
return true;
return true; // TODO: Still true?
}
}
constexpr bool IsWalDeltaDataTypeTransactionEnd(const WalDeltaData::Type type, const uint64_t version = kVersion) {
if (version < 16U) {
return IsWalDeltaDataTypeTransactionEndVersion15(type);
}
// All deltas are now handled in a transactional scope
return type == WalDeltaData::Type::TRANSACTION_END;
}
/// Function used to read information about the WAL file.
/// @throw RecoveryFailure
WalInfo ReadWalInfo(const std::filesystem::path &path);
@@ -174,8 +204,9 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta
void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp);
/// Function used to encode non-transactional operation.
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties, uint64_t timestamp);
void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, StorageMetadataOperation operation,
LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats, uint64_t timestamp);
/// Function used to load the WAL data into the storage.
/// @throw RecoveryFailure
@@ -204,8 +235,8 @@ class WalFile {
void AppendTransactionEnd(uint64_t timestamp);
void AppendOperation(StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
uint64_t timestamp);
void AppendOperation(StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties,
const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t timestamp);
void Sync();

View File

@@ -0,0 +1,35 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <fmt/core.h>
#include "utils/simple_json.hpp"
namespace memgraph::storage {
struct LabelIndexStats {
uint64_t count;
double avg_degree;
};
static inline std::string ToJson(const LabelIndexStats &in) {
return fmt::format(R"({{"count":{}, "avg_degree":{}}})", in.count, in.avg_degree);
}
static inline bool FromJson(const std::string &json, LabelIndexStats &out) {
bool res = true;
res &= utils::GetJsonValue(json, "count", out.count);
res &= utils::GetJsonValue(json, "avg_degree", out.avg_degree);
return res;
}
} // namespace memgraph::storage

View File

@@ -0,0 +1,40 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <fmt/core.h>
#include "utils/simple_json.hpp"
namespace memgraph::storage {
struct LabelPropertyIndexStats {
uint64_t count, distinct_values_count;
double statistic, avg_group_size, avg_degree;
};
static inline std::string ToJson(const LabelPropertyIndexStats &in) {
return fmt::format(
R"({{"count":{}, "distinct_values_count":{}, "statistic":{}, "avg_group_size":{} "avg_degree":{}}})", in.count,
in.distinct_values_count, in.statistic, in.avg_group_size, in.avg_degree);
}
static inline bool FromJson(const std::string &json, LabelPropertyIndexStats &out) {
bool res = true;
res &= utils::GetJsonValue(json, "count", out.count);
res &= utils::GetJsonValue(json, "distinct_values_count", out.distinct_values_count);
res &= utils::GetJsonValue(json, "statistic", out.statistic);
res &= utils::GetJsonValue(json, "avg_group_size", out.avg_group_size);
res &= utils::GetJsonValue(json, "avg_degree", out.avg_degree);
return res;
}
} // namespace memgraph::storage

View File

@@ -159,11 +159,13 @@ InMemoryLabelIndex::Iterable InMemoryLabelIndex::Vertices(LabelId label, View vi
}
void InMemoryLabelIndex::SetIndexStats(const storage::LabelId &label, const storage::LabelIndexStats &stats) {
stats_[label] = stats;
auto locked_stats = stats_.Lock();
locked_stats->insert_or_assign(label, stats);
}
std::optional<LabelIndexStats> InMemoryLabelIndex::GetIndexStats(const storage::LabelId &label) const {
if (auto it = stats_.find(label); it != stats_.end()) {
auto locked_stats = stats_.ReadLock();
if (auto it = locked_stats->find(label); it != locked_stats->end()) {
return it->second;
}
return {};
@@ -171,25 +173,24 @@ std::optional<LabelIndexStats> InMemoryLabelIndex::GetIndexStats(const storage::
std::vector<LabelId> InMemoryLabelIndex::ClearIndexStats() {
std::vector<LabelId> deleted_indexes;
deleted_indexes.reserve(stats_.size());
std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes),
auto locked_stats = stats_.Lock();
deleted_indexes.reserve(locked_stats->size());
std::transform(locked_stats->begin(), locked_stats->end(), std::back_inserter(deleted_indexes),
[](const auto &elem) { return elem.first; });
stats_.clear();
locked_stats->clear();
return deleted_indexes;
}
std::vector<LabelId> InMemoryLabelIndex::DeleteIndexStats(const storage::LabelId &label) {
std::vector<LabelId> deleted_indexes;
for (auto it = stats_.cbegin(); it != stats_.cend();) {
// stats_ is a map with label as the key, so only one can exist at a time
bool InMemoryLabelIndex::DeleteIndexStats(const storage::LabelId &label) {
auto locked_stats = stats_.Lock();
for (auto it = locked_stats->cbegin(); it != locked_stats->cend(); ++it) {
if (it->first == label) {
deleted_indexes.push_back(it->first);
it = stats_.erase(it);
} else {
++it;
locked_stats->erase(it);
return true;
}
}
return deleted_indexes;
return false;
}
} // namespace memgraph::storage

View File

@@ -13,15 +13,12 @@
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/indices/label_index.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/rw_lock.hpp"
namespace memgraph::storage {
struct LabelIndexStats {
uint64_t count;
double avg_degree;
};
using ParallelizedIndexCreationInfo =
std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>;
@@ -108,11 +105,11 @@ class InMemoryLabelIndex : public storage::LabelIndex {
std::vector<LabelId> ClearIndexStats();
std::vector<LabelId> DeleteIndexStats(const storage::LabelId &label);
bool DeleteIndexStats(const storage::LabelId &label);
private:
std::map<LabelId, utils::SkipList<Entry>> index_;
std::map<LabelId, storage::LabelIndexStats> stats_;
utils::Synchronized<std::map<LabelId, storage::LabelIndexStats>, utils::ReadPrioritizedRWLock> stats_;
};
} // namespace memgraph::storage

View File

@@ -70,7 +70,7 @@ bool InMemoryLabelPropertyIndex::CreateIndex(LabelId label, PropertyId property,
auto [it, emplaced] =
index_.emplace(std::piecewise_construct, std::forward_as_tuple(label, property), std::forward_as_tuple());
indices_by_property_[property].insert({label, &index_.at({label, property})});
indices_by_property_[property].insert({label, &it->second});
if (!emplaced) {
// Index already exists.
@@ -386,20 +386,23 @@ uint64_t InMemoryLabelPropertyIndex::ApproximateVertexCount(
std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::ClearIndexStats() {
std::vector<std::pair<LabelId, PropertyId>> deleted_indexes;
deleted_indexes.reserve(stats_.size());
std::transform(stats_.begin(), stats_.end(), std::back_inserter(deleted_indexes),
auto locked_stats = stats_.Lock();
deleted_indexes.reserve(locked_stats->size());
std::transform(locked_stats->begin(), locked_stats->end(), std::back_inserter(deleted_indexes),
[](const auto &elem) { return elem.first; });
stats_.clear();
locked_stats->clear();
return deleted_indexes;
}
// stats_ is a map where the key is a pair of label and property, so for one label many pairs can be deleted
std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::DeleteIndexStats(
const storage::LabelId &label) {
std::vector<std::pair<LabelId, PropertyId>> deleted_indexes;
for (auto it = stats_.cbegin(); it != stats_.cend();) {
auto locked_stats = stats_.Lock();
for (auto it = locked_stats->cbegin(); it != locked_stats->cend();) {
if (it->first.first == label) {
deleted_indexes.push_back(it->first);
it = stats_.erase(it);
it = locked_stats->erase(it);
} else {
++it;
}
@@ -409,12 +412,14 @@ std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::DeleteIn
void InMemoryLabelPropertyIndex::SetIndexStats(const std::pair<storage::LabelId, storage::PropertyId> &key,
const LabelPropertyIndexStats &stats) {
stats_[key] = stats;
auto locked_stats = stats_.Lock();
locked_stats->insert_or_assign(key, stats);
}
std::optional<LabelPropertyIndexStats> InMemoryLabelPropertyIndex::GetIndexStats(
const std::pair<storage::LabelId, storage::PropertyId> &key) const {
if (auto it = stats_.find(key); it != stats_.end()) {
auto locked_stats = stats_.ReadLock();
if (auto it = locked_stats->find(key); it != locked_stats->end()) {
return it->second;
}
return {};

View File

@@ -13,14 +13,11 @@
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/indices/label_property_index.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "utils/rw_lock.hpp"
namespace memgraph::storage {
struct LabelPropertyIndexStats {
uint64_t count, distinct_values_count;
double statistic, avg_group_size, avg_degree;
};
/// TODO: andi. Too many copies, extract at one place
using ParallelizedIndexCreationInfo =
std::pair<std::vector<std::pair<Gid, uint64_t>> /*vertex_recovery_info*/, uint64_t /*thread_count*/>;
@@ -138,7 +135,9 @@ class InMemoryLabelPropertyIndex : public storage::LabelPropertyIndex {
private:
std::map<std::pair<LabelId, PropertyId>, utils::SkipList<Entry>> index_;
std::unordered_map<PropertyId, std::unordered_map<LabelId, utils::SkipList<Entry> *>> indices_by_property_;
std::map<std::pair<LabelId, PropertyId>, storage::LabelPropertyIndexStats> stats_;
utils::Synchronized<std::map<std::pair<LabelId, PropertyId>, storage::LabelPropertyIndexStats>,
utils::ReadPrioritizedRWLock>
stats_;
};
} // namespace memgraph::storage

View File

@@ -103,8 +103,9 @@ uint64_t ReplicateCurrentWal(CurrentWalHandler &stream, durability::WalFile cons
////// ReplicationClient //////
InMemoryReplicationClient::InMemoryReplicationClient(InMemoryStorage *storage,
const replication::ReplicationClientConfig &config)
: ReplicationClient{storage, config} {}
const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *epoch)
: ReplicationClient{storage, config, epoch} {}
void InMemoryReplicationClient::RecoverReplica(uint64_t replica_commit) {
spdlog::debug("Starting replica recover");

View File

@@ -18,7 +18,8 @@ class InMemoryStorage;
class InMemoryReplicationClient : public ReplicationClient {
public:
InMemoryReplicationClient(InMemoryStorage *storage, const replication::ReplicationClientConfig &config);
InMemoryReplicationClient(InMemoryStorage *storage, const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *epoch);
protected:
void RecoverReplica(uint64_t replica_commit) override;

View File

@@ -13,6 +13,7 @@
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/durability/version.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
@@ -33,8 +34,9 @@ std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder
} // namespace
InMemoryReplicationServer::InMemoryReplicationServer(InMemoryStorage *storage,
const replication::ReplicationServerConfig &config)
: ReplicationServer{config}, storage_(storage) {
const memgraph::replication::ReplicationServerConfig &config,
memgraph::replication::ReplicationEpoch *repl_epoch)
: ReplicationServer{config}, storage_(storage), repl_epoch_{repl_epoch} {
rpc_server_.Register<replication::HeartbeatRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received HeartbeatRpc");
this->HeartbeatHandler(req_reader, res_builder);
@@ -65,8 +67,8 @@ InMemoryReplicationServer::InMemoryReplicationServer(InMemoryStorage *storage,
void InMemoryReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::HeartbeatReq req;
slk::Load(&req, req_reader);
replication::HeartbeatRes res{true, storage_->replication_state_.last_commit_timestamp_.load(),
storage_->replication_state_.GetEpoch().id};
replication::HeartbeatRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load(),
std::string{repl_epoch_->id()}};
slk::Save(res, res_builder);
}
@@ -79,13 +81,14 @@ void InMemoryReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk
auto maybe_epoch_id = decoder.ReadString();
MG_ASSERT(maybe_epoch_id, "Invalid replication message");
if (*maybe_epoch_id != storage_->replication_state_.GetEpoch().id) {
storage_->replication_state_.AppendEpoch(*maybe_epoch_id);
auto &repl_storage_state = storage_->repl_storage_state_;
if (*maybe_epoch_id != repl_epoch_->id()) {
auto prev_epoch = repl_epoch_->SetEpoch(*maybe_epoch_id);
repl_storage_state.AddEpochToHistoryForce(prev_epoch);
}
if (storage_->wal_file_) {
if (req.seq_num > storage_->wal_file_->SequenceNumber() ||
*maybe_epoch_id != storage_->replication_state_.GetEpoch().id) {
if (req.seq_num > storage_->wal_file_->SequenceNumber() || *maybe_epoch_id != repl_epoch_->id()) {
storage_->wal_file_->FinalizeWal();
storage_->wal_file_.reset();
storage_->wal_seq_num_ = req.seq_num;
@@ -98,23 +101,25 @@ void InMemoryReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk
storage_->wal_seq_num_ = req.seq_num;
}
if (req.previous_commit_timestamp != storage_->replication_state_.last_commit_timestamp_.load()) {
if (req.previous_commit_timestamp != repl_storage_state.last_commit_timestamp_.load()) {
// Empty the stream
bool transaction_complete = false;
while (!transaction_complete) {
SPDLOG_INFO("Skipping delta");
const auto [timestamp, delta] = ReadDelta(&decoder);
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(
delta.type, durability::kVersion); // TODO: Check if we are always using the latest version when replicating
}
replication::AppendDeltasRes res{false, storage_->replication_state_.last_commit_timestamp_.load()};
replication::AppendDeltasRes res{false, repl_storage_state.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
return;
}
ReadAndApplyDelta(storage_, &decoder);
ReadAndApplyDelta(storage_, &decoder,
durability::kVersion); // TODO: Check if we are always using the latest version when replicating
replication::AppendDeltasRes res{true, storage_->replication_state_.last_commit_timestamp_.load()};
replication::AppendDeltasRes res{true, repl_storage_state.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!");
}
@@ -144,17 +149,14 @@ void InMemoryReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Bu
std::make_unique<InMemoryLabelPropertyIndex>(&storage_->indices_, storage_->config_);
try {
spdlog::debug("Loading snapshot");
auto &epoch =
storage_->replication_state_
.GetEpoch(); // This needs to be a non-const ref since we are updating it in LoadSnapshot TODO fix
auto recovered_snapshot = durability::LoadSnapshot(
*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, &storage_->replication_state_.history,
*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, &storage_->repl_storage_state_.history,
storage_->name_id_mapper_.get(), &storage_->edge_count_, storage_->config_);
spdlog::debug("Snapshot loaded successfully");
// If this step is present it should always be the first step of
// the recovery so we use the UUID we read from snasphost
storage_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid);
epoch.id = std::move(recovered_snapshot.snapshot_info.epoch_id);
repl_epoch_->SetEpoch(std::move(recovered_snapshot.snapshot_info.epoch_id));
const auto &recovery_info = recovered_snapshot.recovery_info;
storage_->vertex_id_ = recovery_info.next_vertex_id;
storage_->edge_id_ = recovery_info.next_edge_id;
@@ -168,7 +170,7 @@ void InMemoryReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Bu
}
storage_guard.unlock();
replication::SnapshotRes res{true, storage_->replication_state_.last_commit_timestamp_.load()};
replication::SnapshotRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
spdlog::trace("Deleting old snapshot files due to snapshot recovery.");
@@ -206,10 +208,10 @@ void InMemoryReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Bu
utils::EnsureDirOrDie(storage_->wal_directory_);
for (auto i = 0; i < wal_file_number; ++i) {
LoadWal(storage_, &decoder);
LoadWal(storage_, *repl_epoch_, &decoder);
}
replication::WalFilesRes res{true, storage_->replication_state_.last_commit_timestamp_.load()};
replication::WalFilesRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!");
}
@@ -222,14 +224,15 @@ void InMemoryReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::
utils::EnsureDirOrDie(storage_->wal_directory_);
LoadWal(storage_, &decoder);
LoadWal(storage_, *repl_epoch_, &decoder);
replication::CurrentWalRes res{true, storage_->replication_state_.last_commit_timestamp_.load()};
replication::CurrentWalRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!");
}
void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, replication::Decoder *decoder) {
void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, memgraph::replication::ReplicationEpoch &epoch,
replication::Decoder *decoder) {
const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory;
utils::EnsureDir(temp_wal_directory);
auto maybe_wal_path = decoder->ReadFile(temp_wal_directory);
@@ -241,8 +244,9 @@ void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, replication::D
storage->uuid_ = wal_info.uuid;
}
if (wal_info.epoch_id != storage->replication_state_.GetEpoch().id) {
storage->replication_state_.AppendEpoch(wal_info.epoch_id);
if (wal_info.epoch_id != epoch.id()) {
auto prev_epoch = epoch.SetEpoch(wal_info.epoch_id);
storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
}
if (storage->wal_file_) {
@@ -264,7 +268,7 @@ void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, replication::D
wal.SetPosition(wal_info.offset_deltas);
for (size_t i = 0; i < wal_info.num_deltas;) {
i += ReadAndApplyDelta(storage, &wal);
i += ReadAndApplyDelta(storage, &wal, *version);
}
spdlog::debug("Replication from current WAL successful!");
@@ -277,18 +281,27 @@ void InMemoryReplicationServer::TimestampHandler(slk::Reader *req_reader, slk::B
replication::TimestampReq req;
slk::Load(&req, req_reader);
replication::TimestampRes res{true, storage_->replication_state_.last_commit_timestamp_.load()};
replication::TimestampRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
}
uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder) {
uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder,
const uint64_t version) {
auto edge_acc = storage->edges_.access();
auto vertex_acc = storage->vertices_.access();
constexpr bool kUniqueAccess = true;
std::optional<std::pair<uint64_t, InMemoryStorage::ReplicationAccessor>> commit_timestamp_and_accessor;
auto get_transaction = [storage, &commit_timestamp_and_accessor](uint64_t commit_timestamp) {
auto get_transaction = [storage, &commit_timestamp_and_accessor](uint64_t commit_timestamp,
bool unique = !kUniqueAccess) {
if (!commit_timestamp_and_accessor) {
auto acc = storage->Access(std::nullopt);
std::unique_ptr<Storage::Accessor> acc = nullptr;
if (unique) {
acc = storage->UniqueAccess(std::nullopt);
} else {
acc = storage->Access(std::nullopt);
}
auto inmem_acc = std::unique_ptr<InMemoryStorage::InMemoryAccessor>(
static_cast<InMemoryStorage::InMemoryAccessor *>(acc.release()));
commit_timestamp_and_accessor.emplace(commit_timestamp, std::move(*inmem_acc));
@@ -299,7 +312,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
};
uint64_t applied_deltas = 0;
auto max_commit_timestamp = storage->replication_state_.last_commit_timestamp_.load();
auto max_commit_timestamp = storage->repl_storage_state_.last_commit_timestamp_.load();
for (bool transaction_complete = false; !transaction_complete; ++applied_deltas) {
const auto [timestamp, delta] = ReadDelta(decoder);
@@ -307,7 +320,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
max_commit_timestamp = timestamp;
}
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type);
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type, version);
if (timestamp < storage->timestamp_) {
continue;
@@ -466,7 +479,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
case durability::WalDeltaData::Type::TRANSACTION_END: {
spdlog::trace(" Transaction end");
if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
throw utils::BasicException("Invalid data!");
throw utils::BasicException("Invalid commit data!");
auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first);
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
commit_timestamp_and_accessor = std::nullopt;
@@ -476,25 +489,45 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: {
spdlog::trace(" Create label index on :{}", delta.operation_label.label);
// Need to send the timestamp
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (storage->CreateIndex(storage->NameToLabel(delta.operation_label.label), timestamp).HasError())
auto *transaction = get_transaction(timestamp, kUniqueAccess);
if (transaction->CreateIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_DROP: {
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (storage->DropIndex(storage->NameToLabel(delta.operation_label.label), timestamp).HasError())
auto *transaction = get_transaction(timestamp, kUniqueAccess);
if (transaction->DropIndex(storage->NameToLabel(delta.operation_label.label)).HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_STATS_SET: {
spdlog::trace(" Set label index statistics on :{}", delta.operation_label_stats.label);
// Need to send the timestamp
auto *transaction = get_transaction(timestamp);
const auto label = storage->NameToLabel(delta.operation_label_stats.label);
LabelIndexStats stats{};
if (!FromJson(delta.operation_label_stats.stats, stats)) {
throw utils::BasicException("Failed to read statistics!");
}
transaction->SetIndexStats(label, stats);
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR: {
const auto &info = delta.operation_label;
spdlog::trace(" Clear label index statistics on :{}", info.label);
// Need to send the timestamp
auto *transaction = get_transaction(timestamp);
transaction->DeleteLabelIndexStats(storage->NameToLabel(info.label));
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (storage
auto *transaction = get_transaction(timestamp, kUniqueAccess);
if (transaction
->CreateIndex(storage->NameToLabel(delta.operation_label_property.label),
storage->NameToProperty(delta.operation_label_property.property), timestamp)
storage->NameToProperty(delta.operation_label_property.property))
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
@@ -502,31 +535,53 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (storage
auto *transaction = get_transaction(timestamp, kUniqueAccess);
if (transaction
->DropIndex(storage->NameToLabel(delta.operation_label_property.label),
storage->NameToProperty(delta.operation_label_property.property), timestamp)
storage->NameToProperty(delta.operation_label_property.property))
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
const auto &info = delta.operation_label_property_stats;
spdlog::trace(" Set label-property index statistics on :{}", info.label);
// Need to send the timestamp
auto *transaction = get_transaction(timestamp);
const auto label = storage->NameToLabel(info.label);
const auto property = storage->NameToProperty(info.property);
LabelPropertyIndexStats stats{};
if (!FromJson(info.stats, stats)) {
throw utils::BasicException("Failed to read statistics!");
}
transaction->SetIndexStats(label, property, stats);
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR: {
const auto &info = delta.operation_label;
spdlog::trace(" Clear label-property index statistics on :{}", info.label);
// Need to send the timestamp
auto *transaction = get_transaction(timestamp);
transaction->DeleteLabelPropertyIndexStats(storage->NameToLabel(info.label));
break;
}
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
auto ret = storage->CreateExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
storage->NameToProperty(delta.operation_label_property.property),
timestamp);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
auto ret =
transaction->CreateExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
storage->NameToProperty(delta.operation_label_property.property));
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (storage
auto *transaction = get_transaction(timestamp, kUniqueAccess);
if (transaction
->DropExistenceConstraint(storage->NameToLabel(delta.operation_label_property.label),
storage->NameToProperty(delta.operation_label_property.property), timestamp)
storage->NameToProperty(delta.operation_label_property.property))
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
@@ -535,13 +590,13 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
std::stringstream ss;
utils::PrintIterable(ss, delta.operation_label_properties.properties);
spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
std::set<PropertyId> properties;
for (const auto &prop : delta.operation_label_properties.properties) {
properties.emplace(storage->NameToProperty(prop));
}
auto ret = storage->CreateUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label),
properties, timestamp);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
auto ret = transaction->CreateUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label),
properties);
if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS)
throw utils::BasicException("Invalid transaction!");
break;
@@ -550,23 +605,24 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
std::stringstream ss;
utils::PrintIterable(ss, delta.operation_label_properties.properties);
spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
std::set<PropertyId> properties;
for (const auto &prop : delta.operation_label_properties.properties) {
properties.emplace(storage->NameToProperty(prop));
}
auto ret = storage->DropUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label),
properties, timestamp);
if (ret.HasError() || ret.GetValue() != UniqueConstraints::DeletionStatus::SUCCESS)
auto *transaction = get_transaction(timestamp, kUniqueAccess);
auto ret =
transaction->DropUniqueConstraint(storage->NameToLabel(delta.operation_label_properties.label), properties);
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
throw utils::BasicException("Invalid transaction!");
}
break;
}
}
}
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid data!");
if (commit_timestamp_and_accessor) throw utils::BasicException("Did not finish the transaction!");
storage->replication_state_.last_commit_timestamp_ = max_commit_timestamp;
storage->repl_storage_state_.last_commit_timestamp_ = max_commit_timestamp;
spdlog::debug("Applied {} deltas", applied_deltas);
return applied_deltas;

View File

@@ -11,6 +11,7 @@
#pragma once
#include "replication/epoch.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/replication/serialization.hpp"
@@ -20,7 +21,9 @@ class InMemoryStorage;
class InMemoryReplicationServer : public ReplicationServer {
public:
explicit InMemoryReplicationServer(InMemoryStorage *storage, const replication::ReplicationServerConfig &config);
explicit InMemoryReplicationServer(InMemoryStorage *storage,
const memgraph::replication::ReplicationServerConfig &config,
memgraph::replication::ReplicationEpoch *repl_epoch);
private:
// RPC handlers
@@ -36,11 +39,14 @@ class InMemoryReplicationServer : public ReplicationServer {
void TimestampHandler(slk::Reader *req_reader, slk::Builder *res_builder);
static void LoadWal(InMemoryStorage *storage, replication::Decoder *decoder);
static void LoadWal(InMemoryStorage *storage, memgraph::replication::ReplicationEpoch &epoch,
replication::Decoder *decoder);
static uint64_t ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder);
static uint64_t ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder, uint64_t version);
InMemoryStorage *storage_;
memgraph::replication::ReplicationEpoch *repl_epoch_;
};
} // namespace memgraph::storage

View File

@@ -12,11 +12,14 @@
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/metadata_delta.hpp"
/// REPLICATION ///
#include "storage/v2/inmemory/replication/replication_client.hpp"
#include "storage/v2/inmemory/replication/replication_server.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
#include "storage/v2/replication/replication_handler.hpp"
#include "utils/resource_lock.hpp"
namespace memgraph::storage {
@@ -56,17 +59,18 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
"process!",
config_.durability.storage_directory);
}
auto &repl_state = repl_state_;
if (config_.durability.recover_on_startup) {
auto &epoch = replication_state_.GetEpoch();
auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch.id,
&replication_state_.history, &vertices_, &edges_, &edge_count_,
auto &epoch = repl_state.GetEpoch();
auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, epoch,
&repl_storage_state_.history, &vertices_, &edges_, &edge_count_,
name_id_mapper_.get(), &indices_, &constraints_, config_, &wal_seq_num_);
if (info) {
vertex_id_ = info->next_vertex_id;
edge_id_ = info->next_edge_id;
timestamp_ = std::max(timestamp_, info->next_timestamp);
if (info->last_commit_timestamp) {
replication_state_.last_commit_timestamp_ = *info->last_commit_timestamp;
repl_storage_state_.last_commit_timestamp_ = *info->last_commit_timestamp;
}
}
} else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED ||
@@ -100,7 +104,8 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
}
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] {
if (auto maybe_error = this->CreateSnapshot({true}); maybe_error.HasError()) {
auto const &repl_state = repl_state_;
if (auto maybe_error = this->CreateSnapshot(repl_state, {true}); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(
@@ -129,19 +134,14 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
if (config_.durability.restore_replication_state_on_startup) {
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
RestoreReplicationRole();
if (replication_state_.GetRole() == replication::ReplicationRole::MAIN) {
RestoreReplicas();
}
ReplicationHandler{repl_state, *this}.RestoreReplication();
} else {
spdlog::warn(
"Replication configuration will NOT be stored. When the server restarts, replication state will be "
"forgotten.");
}
if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED &&
replication_state_.GetRole() == replication::ReplicationRole::MAIN) {
if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && repl_state.IsMain()) {
spdlog::warn(
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider "
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
@@ -156,8 +156,8 @@ InMemoryStorage::~InMemoryStorage() {
gc_runner_.Stop();
}
{
// Clear replication data
replication_state_.Reset();
// Stop replication (Stop all clients or stop the REPLICA server)
repl_storage_state_.Reset();
}
if (wal_file_) {
wal_file_->FinalizeWal();
@@ -167,7 +167,8 @@ InMemoryStorage::~InMemoryStorage() {
snapshot_runner_.Stop();
}
if (config_.durability.snapshot_on_exit) {
if (auto maybe_error = this->CreateSnapshot({false}); maybe_error.HasError()) {
auto const &repl_state = repl_state_;
if (auto maybe_error = this->CreateSnapshot(repl_state, {false}); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
@@ -187,9 +188,9 @@ InMemoryStorage::~InMemoryStorage() {
}
}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryStorage *storage, IsolationLevel isolation_level,
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: Accessor(storage, isolation_level, storage_mode), config_(storage->config_.items) {}
: Accessor(tag, storage, isolation_level, storage_mode), config_(storage->config_.items) {}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept
: Accessor(std::move(other)), config_(other.config_) {}
@@ -642,7 +643,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor *
}
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@@ -651,7 +652,50 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
if (transaction_.deltas.use().empty()) {
auto const &replState = mem_storage->repl_state_;
if (!transaction_.md_deltas.empty()) {
// This is usually done by the MVCC, but it does not handle the metadata deltas
transaction_.EnsureCommitTimestampExists();
// Save these so we can mark them used in the commit log.
uint64_t start_timestamp = transaction_.start_timestamp;
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp));
// Write transaction to WAL while holding the engine lock to make sure
// that committed transactions are sorted by the commit timestamp in the
// WAL files. We supply the new commit timestamp to the function so that
// it knows what will be the final commit timestamp. The WAL must be
// written before actually committing the transaction (before setting
// the commit timestamp) so that no other transaction can see the
// modifications before they are written to disk.
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas = mem_storage->AppendToWalDataDefinition(transaction_, *commit_timestamp_);
// Take committed_transactions lock while holding the engine lock to
// make sure that committed transactions are sorted by the commit
// timestamp in the list.
mem_storage->committed_transactions_.WithLock([&](auto & /*committed_transactions*/) {
// TODO: release lock, and update all deltas to have a local copy
// of the commit timestamp
MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!");
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
// Replica can only update the last commit timestamp with
// the commits received from main.
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_);
}
// Release engine lock because we don't have to hold it anymore
// and emplace back could take a long time.
engine_guard.unlock();
});
mem_storage->commit_log_->MarkFinished(start_timestamp);
}
} else if (transaction_.deltas.use().empty()) {
// We don't have to update the commit timestamp here because no one reads
// it.
mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp);
@@ -669,7 +713,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
auto validation_result = storage_->constraints_.existence_constraints_->Validate(*prev.vertex);
if (validation_result) {
Abort();
return StorageDataManipulationError{*validation_result};
return StorageManipulationError{*validation_result};
}
}
@@ -726,8 +770,7 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
// modifications before they are written to disk.
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (mem_storage->replication_state_.GetRole() == replication::ReplicationRole::MAIN ||
desired_commit_timestamp.has_value()) {
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
}
@@ -742,10 +785,9 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release);
// Replica can only update the last commit timestamp with
// the commits received from main.
if (mem_storage->replication_state_.GetRole() == replication::ReplicationRole::MAIN ||
desired_commit_timestamp.has_value()) {
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->replication_state_.last_commit_timestamp_.store(*commit_timestamp_);
mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_);
}
// Release engine lock because we don't have to hold it anymore
// and emplace back could take a long time.
@@ -758,14 +800,14 @@ utils::BasicResult<StorageDataManipulationError, void> InMemoryStorage::InMemory
if (unique_constraint_violation) {
Abort();
return StorageDataManipulationError{*unique_constraint_violation};
return StorageManipulationError{*unique_constraint_violation};
}
}
is_transaction_active_ = false;
if (!could_replicate_all_sync_replicas) {
return StorageDataManipulationError{ReplicationError{}};
return StorageManipulationError{ReplicationError{}};
}
return {};
@@ -951,189 +993,121 @@ void InMemoryStorage::InMemoryAccessor::FinalizeTransaction() {
}
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::CreateIndex(
LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(indices_.label_index_.get());
if (!mem_label_index->CreateIndex(label, vertices_.access(), std::nullopt)) {
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
if (!mem_label_index->CreateIndex(label, in_memory->vertices_.access(), std::nullopt)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
const auto success =
AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_create, label);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelIndices);
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::CreateIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *mem_label_property_index = static_cast<InMemoryLabelPropertyIndex *>(indices_.label_property_index_.get());
if (!mem_label_property_index->CreateIndex(label, property, vertices_.access(), std::nullopt)) {
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(
LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
if (!mem_label_property_index->CreateIndex(label, property, in_memory->vertices_.access(), std::nullopt)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_create, label, property);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveLabelPropertyIndices);
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::DropIndex(
LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_index_->DropIndex(label)) {
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex(LabelId label) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
if (!mem_label_index->DropIndex(label)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success =
AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_drop, label);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelIndices);
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::DropIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_property_index_->DropIndex(label, property)) {
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::DropIndex(
LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
if (!mem_label_property_index->DropIndex(label, property)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
// For a description why using `timestamp_` is correct, see
// `CreateIndex(LabelId label)`.
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_drop, label, property);
// We don't care if there is a replication error because on main node the change will go through
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveLabelPropertyIndices);
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
return {};
}
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> InMemoryStorage::CreateExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (constraints_.existence_constraints_->ConstraintExists(label, property)) {
utils::BasicResult<StorageExistenceConstraintDefinitionError, void>
InMemoryStorage::InMemoryAccessor::CreateExistenceConstraint(LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *existence_constraints = in_memory->constraints_.existence_constraints_.get();
if (existence_constraints->ConstraintExists(label, property)) {
return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}};
}
if (auto violation = ExistenceConstraints::ValidateVerticesOnConstraint(vertices_.access(), label, property);
if (auto violation =
ExistenceConstraints::ValidateVerticesOnConstraint(in_memory->vertices_.access(), label, property);
violation.has_value()) {
return StorageExistenceConstraintDefinitionError{violation.value()};
}
constraints_.existence_constraints_->InsertConstraint(label, property);
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
if (success) {
return {};
}
return StorageExistenceConstraintDefinitionError{ReplicationError{}};
existence_constraints->InsertConstraint(label, property);
transaction_.md_deltas.emplace_back(MetadataDelta::existence_constraint_create, label, property);
return {};
}
utils::BasicResult<StorageExistenceConstraintDroppingError, void> InMemoryStorage::DropExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!constraints_.existence_constraints_->DropConstraint(label, property)) {
utils::BasicResult<StorageExistenceConstraintDroppingError, void>
InMemoryStorage::InMemoryAccessor::DropExistenceConstraint(LabelId label, PropertyId property) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *existence_constraints = in_memory->constraints_.existence_constraints_.get();
if (!existence_constraints->DropConstraint(label, property)) {
return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
if (success) {
return {};
}
return StorageExistenceConstraintDroppingError{ReplicationError{}};
transaction_.md_deltas.emplace_back(MetadataDelta::existence_constraint_drop, label, property);
return {};
}
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
InMemoryStorage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get());
auto ret = mem_unique_constraints->CreateConstraint(label, properties, vertices_.access());
InMemoryStorage::InMemoryAccessor::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(in_memory->constraints_.unique_constraints_.get());
auto ret = mem_unique_constraints->CreateConstraint(label, properties, in_memory->vertices_.access());
if (ret.HasError()) {
return StorageUniqueConstraintDefinitionError{ret.GetError()};
}
if (ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) {
return ret.GetValue();
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label,
properties, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
if (success) {
return UniqueConstraints::CreationStatus::SUCCESS;
}
return StorageUniqueConstraintDefinitionError{ReplicationError{}};
transaction_.md_deltas.emplace_back(MetadataDelta::unique_constraint_create, label, properties);
return UniqueConstraints::CreationStatus::SUCCESS;
}
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus>
InMemoryStorage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = constraints_.unique_constraints_->DropConstraint(label, properties);
UniqueConstraints::DeletionStatus InMemoryStorage::InMemoryAccessor::DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(in_memory->constraints_.unique_constraints_.get());
auto ret = mem_unique_constraints->DropConstraint(label, properties);
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
return ret;
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label,
properties, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
replication_state_.last_commit_timestamp_ = commit_timestamp;
if (success) {
return UniqueConstraints::DeletionStatus::SUCCESS;
}
return StorageUniqueConstraintDroppingError{ReplicationError{}};
transaction_.md_deltas.emplace_back(MetadataDelta::unique_constraint_drop, label, properties);
return UniqueConstraints::DeletionStatus::SUCCESS;
}
VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(LabelId label, View view) {
@@ -1181,7 +1155,8 @@ Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, S
// of any query on replica to the last commited transaction
// which is timestamp_ as only commit of transaction with writes
// can change the value of it.
if (replication_state_.GetRole() == replication::ReplicationRole::REPLICA) {
auto const &replState = repl_state_;
if (replState.IsReplica()) {
start_timestamp = timestamp_;
} else {
start_timestamp = timestamp_++;
@@ -1191,7 +1166,7 @@ Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, S
}
template <bool force>
void InMemoryStorage::CollectGarbage(std::unique_lock<utils::RWLock> main_guard) {
void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_guard) {
// NOTE: You do not need to consider cleanup of deleted object that occurred in
// different storage modes within the same CollectGarbage call. This is because
// SetStorageMode will ensure CollectGarbage is called before any new transactions
@@ -1501,8 +1476,8 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::RWLock> main_guard)
}
// tell the linker he can find the CollectGarbage definitions here
template void InMemoryStorage::CollectGarbage<true>(std::unique_lock<utils::RWLock>);
template void InMemoryStorage::CollectGarbage<false>(std::unique_lock<utils::RWLock>);
template void InMemoryStorage::CollectGarbage<true>(std::unique_lock<utils::ResourceLock>);
template void InMemoryStorage::CollectGarbage<false>(std::unique_lock<utils::ResourceLock>);
StorageInfo InMemoryStorage::GetInfo() const {
auto vertex_count = vertices_.size();
@@ -1516,12 +1491,12 @@ StorageInfo InMemoryStorage::GetInfo() const {
utils::GetDirDiskUsage(config_.durability.storage_directory)};
}
bool InMemoryStorage::InitializeWalFile() {
bool InMemoryStorage::InitializeWalFile(memgraph::replication::ReplicationEpoch &epoch) {
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL)
return false;
if (!wal_file_) {
wal_file_.emplace(wal_directory_, uuid_, replication_state_.GetEpoch().id, config_.items, name_id_mapper_.get(),
wal_seq_num_++, &file_retainer_);
wal_file_.emplace(wal_directory_, uuid_, epoch.id(), config_.items, name_id_mapper_.get(), wal_seq_num_++,
&file_retainer_);
}
return true;
}
@@ -1546,14 +1521,15 @@ void InMemoryStorage::FinalizeWalFile() {
}
bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
return true;
}
// Traverse deltas and append them to the WAL file.
// A single transaction will always be contained in a single WAL file.
auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire);
replication_state_.InitializeTransaction(wal_file_->SequenceNumber());
repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber());
auto append_deltas = [&](auto callback) {
// Helper lambda that traverses the delta chain on order to find the first
@@ -1704,7 +1680,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
append_deltas([&](const Delta &delta, const auto &parent, uint64_t timestamp) {
wal_file_->AppendDelta(delta, parent, timestamp);
replication_state_.AppendDelta(delta, parent, timestamp);
repl_storage_state_.AppendDelta(delta, parent, timestamp);
});
// Add a delta that indicates that the transaction is fully written to the WAL
@@ -1712,36 +1688,134 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
wal_file_->AppendTransactionEnd(final_commit_timestamp);
FinalizeWalFile();
return replication_state_.FinalizeTransaction(final_commit_timestamp);
return repl_storage_state_.FinalizeTransaction(final_commit_timestamp);
}
bool InMemoryStorage::AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties,
uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) {
bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
return true;
}
wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp);
repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber());
for (const auto &md_delta : transaction.md_deltas) {
switch (md_delta.action) {
case MetadataDelta::Action::LABEL_INDEX_CREATE: {
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_CREATE, md_delta.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_CREATE: {
const auto &info = md_delta.label_property;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_CREATE, info.label,
{info.property}, final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_INDEX_DROP: {
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_DROP, md_delta.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_DROP: {
const auto &info = md_delta.label_property;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_DROP, info.label,
{info.property}, final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_INDEX_STATS_SET: {
const auto &info = md_delta.label_stats;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_STATS_SET, info.label, info.stats,
final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_INDEX_STATS_CLEAR: {
const auto &info = md_delta.label_stats;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_INDEX_STATS_CLEAR, info.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_STATS_SET: {
const auto &info = md_delta.label_property_stats;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_SET, info.label,
{info.property}, info.stats, final_commit_timestamp);
} break;
case MetadataDelta::Action::LABEL_PROPERTY_INDEX_STATS_CLEAR: /* Special case we clear all label/property
pairs with the defined label */
{
const auto &info = md_delta.label_stats;
AppendToWalDataDefinition(durability::StorageMetadataOperation::LABEL_PROPERTY_INDEX_STATS_CLEAR, info.label,
final_commit_timestamp);
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_property;
AppendToWalDataDefinition(durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_CREATE, info.label,
{info.property}, final_commit_timestamp);
} break;
case MetadataDelta::Action::EXISTENCE_CONSTRAINT_DROP: {
const auto &info = md_delta.label_property;
AppendToWalDataDefinition(durability::StorageMetadataOperation::EXISTENCE_CONSTRAINT_DROP, info.label,
{info.property}, final_commit_timestamp);
} break;
case MetadataDelta::Action::UNIQUE_CONSTRAINT_CREATE: {
const auto &info = md_delta.label_properties;
AppendToWalDataDefinition(durability::StorageMetadataOperation::UNIQUE_CONSTRAINT_CREATE, info.label,
info.properties, final_commit_timestamp);
} break;
case MetadataDelta::Action::UNIQUE_CONSTRAINT_DROP: {
const auto &info = md_delta.label_properties;
AppendToWalDataDefinition(durability::StorageMetadataOperation::UNIQUE_CONSTRAINT_DROP, info.label,
info.properties, final_commit_timestamp);
} break;
}
}
// Add a delta that indicates that the transaction is fully written to the WAL
wal_file_->AppendTransactionEnd(final_commit_timestamp);
FinalizeWalFile();
return replication_state_.AppendOperation(wal_file_->SequenceNumber(), operation, label, properties,
final_commit_timestamp);
return repl_storage_state_.FinalizeTransaction(final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, LabelIndexStats stats,
LabelPropertyIndexStats property_stats,
uint64_t final_commit_timestamp) {
wal_file_->AppendOperation(operation, label, properties, stats, property_stats, final_commit_timestamp);
repl_storage_state_.AppendOperation(operation, label, properties, stats, property_stats, final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties,
LabelPropertyIndexStats property_stats,
uint64_t final_commit_timestamp) {
return AppendToWalDataDefinition(operation, label, properties, {}, property_stats, final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
LabelIndexStats stats, uint64_t final_commit_timestamp) {
return AppendToWalDataDefinition(operation, label, {}, stats, {}, final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties,
uint64_t final_commit_timestamp) {
return AppendToWalDataDefinition(operation, label, properties, {}, final_commit_timestamp);
}
void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
uint64_t final_commit_timestamp) {
return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
}
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(
std::optional<bool> is_periodic) {
if (replication_state_.GetRole() != replication::ReplicationRole::MAIN) {
memgraph::replication::ReplicationState const &replicationState, std::optional<bool> is_periodic) {
if (replicationState.IsReplica()) {
return CreateSnapshotError::DisabledForReplica;
}
auto snapshot_creator = [this]() {
auto const &epoch = replicationState.GetEpoch();
auto snapshot_creator = [this, &epoch]() {
utils::Timer timer;
const auto &epoch = replication_state_.GetEpoch();
auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_);
// Create snapshot.
durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_,
config_.durability.snapshot_retention_count, &vertices_, &edges_, name_id_mapper_.get(),
&indices_, &constraints_, config_, uuid_, epoch.id, replication_state_.history,
&indices_, &constraints_, config_, uuid_, epoch, repl_storage_state_.history,
&file_retainer_);
// Finalize snapshot transaction.
commit_log_->MarkFinished(transaction.start_timestamp);
@@ -1756,7 +1830,7 @@ utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::Create
auto max_num_tries{10};
while (max_num_tries) {
if (should_try_shared) {
std::shared_lock<utils::RWLock> storage_guard(main_lock_);
std::shared_lock storage_guard(main_lock_);
if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
snapshot_creator();
return {};
@@ -1778,7 +1852,7 @@ utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::Create
return CreateSnapshotError::ReachedMaxNumTries;
}
void InMemoryStorage::FreeMemory(std::unique_lock<utils::RWLock> main_guard) {
void InMemoryStorage::FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) {
CollectGarbage<true>(std::move(main_guard));
// SkipList is already threadsafe
@@ -1797,14 +1871,13 @@ uint64_t InMemoryStorage::CommitTimestamp(const std::optional<uint64_t> desired_
return *desired_commit_timestamp;
}
void InMemoryStorage::EstablishNewEpoch() {
void InMemoryStorage::PrepareForNewEpoch(std::string prev_epoch) {
std::unique_lock engine_guard{engine_lock_};
if (wal_file_) {
wal_file_->FinalizeWal();
wal_file_.reset();
}
// TODO: Move out of storage (no need for the lock) <- missing commit_timestamp at a higher level
replication_state_.NewEpoch();
repl_storage_state_.AddEpochToHistory(std::move(prev_epoch));
}
utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::IsPathLocked() {
@@ -1832,14 +1905,66 @@ utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath()
return true;
}
auto InMemoryStorage::CreateReplicationClient(replication::ReplicationClientConfig const &config)
auto InMemoryStorage::CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
-> std::unique_ptr<ReplicationClient> {
return std::make_unique<InMemoryReplicationClient>(this, config);
auto &replState = this->repl_state_;
return std::make_unique<InMemoryReplicationClient>(this, config, &replState.GetEpoch());
}
std::unique_ptr<ReplicationServer> InMemoryStorage::CreateReplicationServer(
const replication::ReplicationServerConfig &config) {
return std::make_unique<InMemoryReplicationServer>(this, config);
const memgraph::replication::ReplicationServerConfig &config) {
auto &replState = this->repl_state_;
return std::make_unique<InMemoryReplicationServer>(this, config, &replState.GetEpoch());
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(std::optional<IsolationLevel> override_isolation_level) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{
Storage::Accessor::shared_access, this, override_isolation_level.value_or(isolation_level_), storage_mode_});
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(
std::optional<IsolationLevel> override_isolation_level) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{
Storage::Accessor::unique_access, this, override_isolation_level.value_or(isolation_level_), storage_mode_});
}
IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const {
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
auto *mem_label_property_index =
static_cast<InMemoryLabelPropertyIndex *>(in_memory->indices_.label_property_index_.get());
return {mem_label_index->ListIndices(), mem_label_property_index->ListIndices()};
}
ConstraintsInfo InMemoryStorage::InMemoryAccessor::ListAllConstraints() const {
const auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return {mem_storage->constraints_.existence_constraints_->ListConstraints(),
mem_storage->constraints_.unique_constraints_->ListConstraints()};
}
void InMemoryStorage::InMemoryAccessor::SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) {
SetIndexStatsForIndex(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), label, stats);
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_stats_set, label, stats);
}
void InMemoryStorage::InMemoryAccessor::SetIndexStats(const storage::LabelId &label,
const storage::PropertyId &property,
const LabelPropertyIndexStats &stats) {
SetIndexStatsForIndex(static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()),
std::make_pair(label, property), stats);
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_stats_set, label, property, stats);
}
bool InMemoryStorage::InMemoryAccessor::DeleteLabelIndexStats(const storage::LabelId &label) {
const auto res =
DeleteIndexStatsForIndex<bool>(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), label);
transaction_.md_deltas.emplace_back(MetadataDelta::label_index_stats_clear, label);
return res;
}
std::vector<std::pair<LabelId, PropertyId>> InMemoryStorage::InMemoryAccessor::DeleteLabelPropertyIndexStats(
const storage::LabelId &label) {
const auto &res = DeleteIndexStatsForIndex<std::vector<std::pair<LabelId, PropertyId>>>(
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()), label);
transaction_.md_deltas.emplace_back(MetadataDelta::label_property_index_stats_clear, label);
return res;
}
} // namespace memgraph::storage

View File

@@ -15,19 +15,20 @@
#include <cstdint>
#include <memory>
#include <utility>
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/inmemory/label_index.hpp"
#include "storage/v2/inmemory/label_property_index.hpp"
#include "storage/v2/storage.hpp"
/// REPLICATION ///
#include "storage/v2/replication/config.hpp"
#include "replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/replication.hpp"
#include "storage/v2/replication/replication_persistence_helper.hpp"
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
#include "storage/v2/transaction.hpp"
#include "utils/memory.hpp"
#include "utils/resource_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {
@@ -64,7 +65,8 @@ class InMemoryStorage final : public Storage {
private:
friend class InMemoryStorage;
explicit InMemoryAccessor(InMemoryStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
explicit InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode);
public:
InMemoryAccessor(const InMemoryAccessor &) = delete;
@@ -159,52 +161,19 @@ class InMemoryStorage final : public Storage {
index->SetIndexStats(key, stats);
}
void SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) override {
SetIndexStatsForIndex(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()), label, stats);
}
void SetIndexStats(const storage::LabelId &label, const LabelIndexStats &stats) override;
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
const LabelPropertyIndexStats &stats) override {
SetIndexStatsForIndex(static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()),
std::make_pair(label, property), stats);
}
const LabelPropertyIndexStats &stats) override;
template <typename TResult, typename TIndex>
std::vector<TResult> ClearIndexStatsForIndex(TIndex *index) const {
return index->ClearIndexStats();
TResult DeleteIndexStatsForIndex(TIndex *index, const storage::LabelId &label) {
return index->DeleteIndexStats(label);
}
std::vector<LabelId> ClearLabelIndexStats() override {
return ClearIndexStatsForIndex<LabelId>(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()));
}
std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats(const storage::LabelId &label) override;
std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() override {
return ClearIndexStatsForIndex<std::pair<LabelId, PropertyId>>(
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()));
}
template <typename TResult, typename TIndex>
std::vector<TResult> DeleteIndexStatsForIndex(TIndex *index, const std::span<std::string> labels) {
std::vector<TResult> deleted_indexes;
for (const auto &label : labels) {
std::vector<TResult> loc_results = index->DeleteIndexStats(NameToLabel(label));
deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()),
std::make_move_iterator(loc_results.end()));
}
return deleted_indexes;
}
std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats(
const std::span<std::string> labels) override {
return DeleteIndexStatsForIndex<std::pair<LabelId, PropertyId>>(
static_cast<InMemoryLabelPropertyIndex *>(storage_->indices_.label_property_index_.get()), labels);
}
std::vector<LabelId> DeleteLabelIndexStats(const std::span<std::string> labels) override {
return DeleteIndexStatsForIndex<LabelId>(static_cast<InMemoryLabelIndex *>(storage_->indices_.label_index_.get()),
labels);
}
bool DeleteLabelIndexStats(const storage::LabelId &label) override;
Result<std::optional<std::pair<std::vector<VertexAccessor>, std::vector<EdgeAccessor>>>> DetachDelete(
std::vector<VertexAccessor *> nodes, std::vector<EdgeAccessor *> edges, bool detach) override;
@@ -228,15 +197,9 @@ class InMemoryStorage final : public Storage {
return static_cast<InMemoryStorage *>(storage_)->indices_.label_property_index_->IndexExists(label, property);
}
IndicesInfo ListAllIndices() const override {
const auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return mem_storage->ListAllIndices();
}
IndicesInfo ListAllIndices() const override;
ConstraintsInfo ListAllConstraints() const override {
const auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
return mem_storage->ListAllConstraints();
}
ConstraintsInfo ListAllConstraints() const override;
/// Returns void if the transaction has been committed.
/// Returns `StorageDataManipulationError` if an error occures. Error can be:
@@ -245,7 +208,7 @@ class InMemoryStorage final : public Storage {
/// case the transaction is automatically aborted.
/// @throw std::bad_alloc
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageDataManipulationError, void> Commit(
utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) override;
/// @throw std::bad_alloc
@@ -253,6 +216,78 @@ class InMemoryStorage final : public Storage {
void FinalizeTransaction() override;
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `IndexDefinitionError`: the index already exists.
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) override;
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index already exists.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) override;
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) override;
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) override;
/// Returns void if the existence constraint has been created.
/// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint.
/// * `ConstraintDefinitionError`: the constraint already exists.
/// @throw std::bad_alloc
/// @throw std::length_error
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property) override;
/// Drop an existing existence constraint.
/// Returns void if the existence constraint has been dropped.
/// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintDefinitionError`: the constraint did not exists.
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property) override;
/// Create an unique constraint.
/// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there are already vertices violating the constraint.
/// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be:
/// * `SUCCESS` if the constraint was successfully created,
/// * `ALREADY_EXISTS` if the constraint already existed,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
/// @throw std::bad_alloc
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) override;
/// Removes an existing unique constraint.
/// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be:
/// * `SUCCESS` if constraint was successfully removed,
/// * `NOT_FOUND` if the specified constraint was not found,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label,
const std::set<PropertyId> &properties) override;
protected:
// TODO Better naming
/// @throw std::bad_alloc
@@ -280,101 +315,25 @@ class InMemoryStorage final : public Storage {
Transaction &GetTransaction() { return transaction_; }
};
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override {
return std::unique_ptr<InMemoryAccessor>(
new InMemoryAccessor{this, override_isolation_level.value_or(isolation_level_), storage_mode_});
}
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override;
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `IndexDefinitionError`: the index already exists.
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) override;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) override;
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index already exists.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) override;
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
/// Returns void if the existence constraint has been created.
/// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint.
/// * `ConstraintDefinitionError`: the constraint already exists.
/// @throw std::bad_alloc
/// @throw std::length_error
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
/// Drop an existing existence constraint.
/// Returns void if the existence constraint has been dropped.
/// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintDefinitionError`: the constraint did not exists.
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) override;
/// Create an unique constraint.
/// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there are already vertices violating the constraint.
/// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be:
/// * `SUCCESS` if the constraint was successfully created,
/// * `ALREADY_EXISTS` if the constraint already existed,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
/// @throw std::bad_alloc
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override;
/// Removes an existing unique constraint.
/// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be:
/// * `SUCCESS` if constraint was successfully removed,
/// * `NOT_FOUND` if the specified constraint was not found,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp) override;
void FreeMemory(std::unique_lock<utils::RWLock> main_guard) override;
void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) override;
utils::FileRetainer::FileLockerAccessor::ret_type IsPathLocked();
utils::FileRetainer::FileLockerAccessor::ret_type LockPath();
utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath();
utils::BasicResult<CreateSnapshotError> CreateSnapshot(std::optional<bool> is_periodic);
utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot(
memgraph::replication::ReplicationState const &replicationState, std::optional<bool> is_periodic);
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
auto CreateReplicationClient(replication::ReplicationClientConfig const &config)
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
-> std::unique_ptr<ReplicationClient> override;
auto CreateReplicationServer(const replication::ReplicationServerConfig &config)
auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> override;
private:
@@ -390,22 +349,37 @@ class InMemoryStorage final : public Storage {
/// @throw std::system_error
/// @throw std::bad_alloc
template <bool force>
void CollectGarbage(std::unique_lock<utils::RWLock> main_guard = {});
void CollectGarbage(std::unique_lock<utils::ResourceLock> main_guard = {});
bool InitializeWalFile();
bool InitializeWalFile(memgraph::replication::ReplicationEpoch &epoch);
void FinalizeWalFile();
StorageInfo GetInfo() const override;
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
[[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
[[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
[[nodiscard]] bool AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, LabelIndexStats stats,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, LabelPropertyIndexStats property_stats,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, LabelIndexStats stats,
LabelPropertyIndexStats property_stats, uint64_t final_commit_timestamp);
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
void EstablishNewEpoch() override;
void PrepareForNewEpoch(std::string prev_epoch) override;
// Main object storage
utils::SkipList<storage::Vertex> vertices_;

View File

@@ -0,0 +1,157 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include <cstdint>
#include <set>
#include "storage/v2/edge_ref.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/logging.hpp"
namespace memgraph::storage {
// NOTE: MetadataDeltas are not supported in a multi command transaction (single query only)
struct MetadataDelta {
enum class Action {
LABEL_INDEX_CREATE,
LABEL_INDEX_DROP,
LABEL_INDEX_STATS_SET,
LABEL_INDEX_STATS_CLEAR,
LABEL_PROPERTY_INDEX_CREATE,
LABEL_PROPERTY_INDEX_DROP,
LABEL_PROPERTY_INDEX_STATS_SET,
LABEL_PROPERTY_INDEX_STATS_CLEAR,
EXISTENCE_CONSTRAINT_CREATE,
EXISTENCE_CONSTRAINT_DROP,
UNIQUE_CONSTRAINT_CREATE,
UNIQUE_CONSTRAINT_DROP,
};
static constexpr struct LabelIndexCreate {
} label_index_create;
static constexpr struct LabelIndexDrop {
} label_index_drop;
static constexpr struct LabelIndexStatsSet {
} label_index_stats_set;
static constexpr struct LabelIndexStatsClear {
} label_index_stats_clear;
static constexpr struct LabelPropertyIndexCreate {
} label_property_index_create;
static constexpr struct LabelPropertyIndexDrop {
} label_property_index_drop;
static constexpr struct LabelPropertyIndexStatsSet {
} label_property_index_stats_set;
static constexpr struct LabelPropertyIndexStatsClear {
} label_property_index_stats_clear;
static constexpr struct ExistenceConstraintCreate {
} existence_constraint_create;
static constexpr struct ExistenceConstraintDrop {
} existence_constraint_drop;
static constexpr struct UniqueConstraintCreate {
} unique_constraint_create;
static constexpr struct UniqueConstraintDrop {
} unique_constraint_drop;
MetadataDelta(LabelIndexCreate /*tag*/, LabelId label) : action(Action::LABEL_INDEX_CREATE), label(label) {}
MetadataDelta(LabelIndexDrop /*tag*/, LabelId label) : action(Action::LABEL_INDEX_DROP), label(label) {}
MetadataDelta(LabelIndexStatsSet /*tag*/, LabelId label, LabelIndexStats stats)
: action(Action::LABEL_INDEX_STATS_SET), label_stats{label, stats} {}
MetadataDelta(LabelIndexStatsClear /*tag*/, LabelId label) : action(Action::LABEL_INDEX_STATS_CLEAR), label{label} {}
MetadataDelta(LabelPropertyIndexCreate /*tag*/, LabelId label, PropertyId property)
: action(Action::LABEL_PROPERTY_INDEX_CREATE), label_property{label, property} {}
MetadataDelta(LabelPropertyIndexDrop /*tag*/, LabelId label, PropertyId property)
: action(Action::LABEL_PROPERTY_INDEX_DROP), label_property{label, property} {}
MetadataDelta(LabelPropertyIndexStatsSet /*tag*/, LabelId label, PropertyId property, LabelPropertyIndexStats stats)
: action(Action::LABEL_PROPERTY_INDEX_STATS_SET), label_property_stats{label, property, stats} {}
MetadataDelta(LabelPropertyIndexStatsClear /*tag*/, LabelId label)
: action(Action::LABEL_PROPERTY_INDEX_STATS_CLEAR), label{label} {}
MetadataDelta(ExistenceConstraintCreate /*tag*/, LabelId label, PropertyId property)
: action(Action::EXISTENCE_CONSTRAINT_CREATE), label_property{label, property} {}
MetadataDelta(ExistenceConstraintDrop /*tag*/, LabelId label, PropertyId property)
: action(Action::EXISTENCE_CONSTRAINT_DROP), label_property{label, property} {}
MetadataDelta(UniqueConstraintCreate /*tag*/, LabelId label, std::set<PropertyId> properties)
: action(Action::UNIQUE_CONSTRAINT_CREATE), label_properties{label, std::move(properties)} {}
MetadataDelta(UniqueConstraintDrop /*tag*/, LabelId label, std::set<PropertyId> properties)
: action(Action::UNIQUE_CONSTRAINT_DROP), label_properties{label, std::move(properties)} {}
MetadataDelta(const MetadataDelta &) = delete;
MetadataDelta(MetadataDelta &&) = delete;
MetadataDelta &operator=(const MetadataDelta &) = delete;
MetadataDelta &operator=(MetadataDelta &&) = delete;
~MetadataDelta() {
switch (action) {
case Action::LABEL_INDEX_CREATE:
case Action::LABEL_INDEX_DROP:
case Action::LABEL_INDEX_STATS_SET:
case Action::LABEL_INDEX_STATS_CLEAR:
case Action::LABEL_PROPERTY_INDEX_CREATE:
case Action::LABEL_PROPERTY_INDEX_DROP:
case Action::LABEL_PROPERTY_INDEX_STATS_SET:
case Action::LABEL_PROPERTY_INDEX_STATS_CLEAR:
case Action::EXISTENCE_CONSTRAINT_CREATE:
case Action::EXISTENCE_CONSTRAINT_DROP:
break;
case Action::UNIQUE_CONSTRAINT_CREATE:
case Action::UNIQUE_CONSTRAINT_DROP:
label_properties.properties.~set<PropertyId>();
break;
}
}
Action action;
union {
LabelId label;
struct {
LabelId label;
PropertyId property;
} label_property;
struct {
LabelId label;
std::set<PropertyId> properties;
} label_properties;
struct {
LabelId label;
LabelIndexStats stats;
} label_stats;
struct {
LabelId label;
PropertyId property;
LabelPropertyIndexStats stats;
} label_property_stats;
};
};
static_assert(alignof(MetadataDelta) >= 8, "The Delta should be aligned to at least 8!");
} // namespace memgraph::storage

View File

@@ -13,11 +13,7 @@
#include <cstdint>
namespace memgraph::storage::replication {
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
enum class ReplicationMode : std::uint8_t { SYNC, ASYNC };
enum class ReplicaState : std::uint8_t { READY, REPLICATING, RECOVERY, INVALID };
enum class RegistrationMode : std::uint8_t { MUST_BE_INSTANTLY_VALID, CAN_BE_INVALID };
} // namespace memgraph::storage::replication

View File

@@ -17,38 +17,13 @@
#include <utility>
#include "io/network/endpoint.hpp"
#include "replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
#include "utils/uuid.hpp"
// TODO: move to replication namespace and unify
namespace memgraph::storage {
// TODO: Should be at MAIN instance level; shouldn't be connected to storage
struct ReplicationEpoch {
ReplicationEpoch() : id(utils::GenerateUUID()) {}
// UUID to distinguish different main instance runs for replication process
// on SAME storage.
// Multiple instances can have same storage UUID and be MAIN at the same time.
// We cannot compare commit timestamps of those instances if one of them
// becomes the replica of the other so we use epoch_id_ as additional
// discriminating property.
// Example of this:
// We have 2 instances of the same storage, S1 and S2.
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
// At the moment when S1 commited a transaction with timestamp 20, and S2
// a different transaction with timestamp 15, we change S2's role to REPLICA
// and register it on S1.
// Without using the epoch_id, we don't know that S1 and S2 have completely
// different transactions, we think that the S2 is behind only by 5 commits.
std::string id; // TODO: Move to replication level
// Generates a new epoch id, returning the old one
std::string NewEpoch() { return std::exchange(id, utils::GenerateUUID()); }
std::string SetEpoch(std::string new_epoch) { return std::exchange(id, std::move(new_epoch)); }
};
struct TimestampInfo {
uint64_t current_timestamp_of_replica;
uint64_t current_number_of_timestamp_behind_master;
@@ -56,7 +31,7 @@ struct TimestampInfo {
struct ReplicaInfo {
std::string name;
replication::ReplicationMode mode;
memgraph::replication::ReplicationMode mode;
io::network::Endpoint endpoint;
replication::ReplicaState state;
TimestampInfo timestamp_info;

View File

@@ -1,383 +0,0 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v2/replication/replication.hpp"
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/storage.hpp"
namespace memgraph::storage {
using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler;
namespace {
std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
using enum RegisterReplicaError;
switch (error) {
case NAME_EXISTS:
return "NAME_EXISTS";
case END_POINT_EXISTS:
return "END_POINT_EXISTS";
case CONNECTION_FAILED:
return "CONNECTION_FAILED";
case COULD_NOT_BE_PERSISTED:
return "COULD_NOT_BE_PERSISTED";
}
}
} // namespace
storage::ReplicationState::ReplicationState(bool restore, std::filesystem::path durability_dir) {
if (restore) {
utils::EnsureDirOrDie(durability_dir / durability::kReplicationDirectory);
durability_ = std::make_unique<kvstore::KVStore>(durability_dir / durability::kReplicationDirectory);
}
}
void storage::ReplicationState::Reset() {
replication_server_.reset();
replication_clients_.WithLock([&](auto &clients) { clients.clear(); });
}
bool storage::ReplicationState::SetMainReplicationRole(storage::Storage *storage) {
// We don't want to generate new epoch_id and do the
// cleanup if we're already a MAIN
if (GetRole() == replication::ReplicationRole::MAIN) {
return false;
}
// Main instance does not need replication server
// This should be always called first so we finalize everything
replication_server_.reset(nullptr);
storage->EstablishNewEpoch();
if (ShouldStoreAndRestoreReplicationState()) {
// Only thing that matters here is the role saved as MAIN
auto data = replication::ReplicationStatusToJSON(
replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName,
.ip_address = "",
.port = 0,
.sync_mode = replication::ReplicationMode::SYNC,
.replica_check_frequency = std::chrono::seconds(0),
.ssl = std::nullopt,
.role = replication::ReplicationRole::MAIN});
if (!durability_->Put(replication::kReservedReplicationRoleName, data.dump())) {
spdlog::error("Error when saving MAIN replication role in settings.");
return false;
}
}
SetRole(replication::ReplicationRole::MAIN);
return true;
}
bool storage::ReplicationState::AppendOperation(const uint64_t seq_num, durability::StorageGlobalOperation operation,
LabelId label, const std::set<PropertyId> &properties,
uint64_t final_commit_timestamp) {
bool finalized_on_all_replicas = true;
// TODO Should we return true if not MAIN?
if (GetRole() == replication::ReplicationRole::MAIN) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->StartTransactionReplication(seq_num);
client->IfStreamingTransaction(
[&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); });
const auto finalized = client->FinalizeTransactionReplication();
if (client->Mode() == replication::ReplicationMode::SYNC) {
finalized_on_all_replicas = finalized && finalized_on_all_replicas;
}
}
});
}
return finalized_on_all_replicas;
}
void storage::ReplicationState::InitializeTransaction(uint64_t seq_num) {
if (GetRole() == replication::ReplicationRole::MAIN) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->StartTransactionReplication(seq_num);
}
});
}
}
void storage::ReplicationState::AppendDelta(const Delta &delta, const Edge &parent, uint64_t timestamp) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendDelta(delta, parent, timestamp); });
}
});
}
void storage::ReplicationState::AppendDelta(const Delta &delta, const Vertex &parent, uint64_t timestamp) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendDelta(delta, parent, timestamp); });
}
});
}
bool storage::ReplicationState::FinalizeTransaction(uint64_t timestamp) {
bool finalized_on_all_replicas = true;
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(timestamp); });
const auto finalized = client->FinalizeTransactionReplication();
if (client->Mode() == replication::ReplicationMode::SYNC) {
finalized_on_all_replicas = finalized && finalized_on_all_replicas;
}
}
});
return finalized_on_all_replicas;
}
utils::BasicResult<RegisterReplicaError> ReplicationState::RegisterReplica(
const replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config,
Storage *storage) {
MG_ASSERT(GetRole() == replication::ReplicationRole::MAIN, "Only main instance can register a replica!");
auto name_check = [&config](auto &clients) {
auto name_matches = [&name = config.name](const auto &client) { return client->Name() == name; };
return std::any_of(clients.begin(), clients.end(), name_matches);
};
auto desired_endpoint = io::network::Endpoint{config.ip_address, config.port};
auto endpoint_check = [&](auto &clients) {
auto endpoint_matches = [&](const auto &client) { return client->Endpoint() == desired_endpoint; };
return std::any_of(clients.begin(), clients.end(), endpoint_matches);
};
auto task = [&](auto &clients) -> utils::BasicResult<RegisterReplicaError> {
if (name_check(clients)) {
return RegisterReplicaError::NAME_EXISTS;
}
if (endpoint_check(clients)) {
return RegisterReplicaError::END_POINT_EXISTS;
}
if (!TryPersistReplicaClient(config)) {
return RegisterReplicaError::COULD_NOT_BE_PERSISTED;
}
auto client = storage->CreateReplicationClient(config);
client->Start();
if (client->State() == replication::ReplicaState::INVALID) {
if (replication::RegistrationMode::CAN_BE_INVALID != registration_mode) {
return RegisterReplicaError::CONNECTION_FAILED;
}
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.", client->Name());
}
clients.push_back(std::move(client));
return {};
};
return replication_clients_.WithLock(task);
}
bool ReplicationState::TryPersistReplicaClient(const replication::ReplicationClientConfig &config) {
if (!ShouldStoreAndRestoreReplicationState()) return true;
auto data = replication::ReplicationStatusToJSON(
replication::ReplicationStatus{.name = config.name,
.ip_address = config.ip_address,
.port = config.port,
.sync_mode = config.mode,
.replica_check_frequency = config.replica_check_frequency,
.ssl = config.ssl,
.role = replication::ReplicationRole::REPLICA});
if (durability_->Put(config.name, data.dump())) return true;
spdlog::error("Error when saving replica {} in settings.", config.name);
return false;
}
bool ReplicationState::SetReplicaRole(const replication::ReplicationServerConfig &config, Storage *storage) {
// We don't want to restart the server if we're already a REPLICA
if (GetRole() == replication::ReplicationRole::REPLICA) {
return false;
}
replication_server_ = storage->CreateReplicationServer(config);
bool res = replication_server_->Start();
if (!res) {
spdlog::error("Unable to start the replication server.");
return false;
}
if (ShouldStoreAndRestoreReplicationState()) {
// Only thing that matters here is the role saved as REPLICA and the listening port
auto data = replication::ReplicationStatusToJSON(
replication::ReplicationStatus{.name = replication::kReservedReplicationRoleName,
.ip_address = config.ip_address,
.port = config.port,
.sync_mode = replication::ReplicationMode::SYNC,
.replica_check_frequency = std::chrono::seconds(0),
.ssl = std::nullopt,
.role = replication::ReplicationRole::REPLICA});
if (!durability_->Put(replication::kReservedReplicationRoleName, data.dump())) {
spdlog::error("Error when saving REPLICA replication role in settings.");
return false;
}
}
SetRole(replication::ReplicationRole::REPLICA);
return true;
}
bool ReplicationState::UnregisterReplica(std::string_view name) {
MG_ASSERT(GetRole() == replication::ReplicationRole::MAIN, "Only main instance can unregister a replica!");
if (ShouldStoreAndRestoreReplicationState()) {
if (!durability_->Delete(name)) {
spdlog::error("Error when removing replica {} from settings.", name);
return false;
}
}
return replication_clients_.WithLock([&](auto &clients) {
return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; });
});
}
std::optional<replication::ReplicaState> ReplicationState::GetReplicaState(const std::string_view name) {
return replication_clients_.WithLock([&](auto &clients) -> std::optional<replication::ReplicaState> {
const auto client_it =
std::find_if(clients.cbegin(), clients.cend(), [name](auto &client) { return client->Name() == name; });
if (client_it == clients.cend()) {
return std::nullopt;
}
return (*client_it)->State();
});
}
std::vector<ReplicaInfo> ReplicationState::ReplicasInfo() {
return replication_clients_.WithLock([](auto &clients) {
std::vector<ReplicaInfo> replica_info;
replica_info.reserve(clients.size());
std::transform(
clients.begin(), clients.end(), std::back_inserter(replica_info), [](const auto &client) -> ReplicaInfo {
return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo()};
});
return replica_info;
});
}
void ReplicationState::RestoreReplicationRole(Storage *storage) {
if (!ShouldStoreAndRestoreReplicationState()) {
return;
}
spdlog::info("Restoring replication role.");
uint16_t port = replication::kDefaultReplicationPort;
const auto replication_data = durability_->Get(replication::kReservedReplicationRoleName);
if (!replication_data.has_value()) {
spdlog::debug("Cannot find data needed for restore replication role in persisted metadata.");
return;
}
const auto maybe_replication_status = replication::JSONToReplicationStatus(nlohmann::json::parse(*replication_data));
if (!maybe_replication_status.has_value()) {
LOG_FATAL("Cannot parse previously saved configuration of replication role {}.",
replication::kReservedReplicationRoleName);
}
const auto replication_status = *maybe_replication_status;
if (!replication_status.role.has_value()) {
SetRole(replication::ReplicationRole::MAIN);
} else {
SetRole(*replication_status.role);
port = replication_status.port;
}
if (GetRole() == replication::ReplicationRole::REPLICA) {
replication_server_ = storage->CreateReplicationServer(replication::ReplicationServerConfig{
.ip_address = replication::kDefaultReplicationServerIp,
.port = port,
});
bool res = replication_server_->Start();
if (!res) {
LOG_FATAL("Unable to start the replication server.");
}
}
spdlog::info("Replication role restored to {}.",
GetRole() == replication::ReplicationRole::MAIN ? "MAIN" : "REPLICA");
}
void ReplicationState::RestoreReplicas(Storage *storage) {
if (!ShouldStoreAndRestoreReplicationState()) {
return;
}
spdlog::info("Restoring replicas.");
for (const auto &[replica_name, replica_data] : *durability_) {
spdlog::info("Restoring replica {}.", replica_name);
const auto maybe_replica_status = replication::JSONToReplicationStatus(nlohmann::json::parse(replica_data));
if (!maybe_replica_status.has_value()) {
LOG_FATAL("Cannot parse previously saved configuration of replica {}.", replica_name);
}
auto replica_status = *maybe_replica_status;
MG_ASSERT(replica_status.name == replica_name, "Expected replica name is '{}', but got '{}'", replica_status.name,
replica_name);
if (replica_name == replication::kReservedReplicationRoleName) {
continue;
}
auto ret = RegisterReplica(replication::RegistrationMode::CAN_BE_INVALID,
replication::ReplicationClientConfig{
.name = replica_status.name,
.mode = replica_status.sync_mode,
.ip_address = replica_status.ip_address,
.port = replica_status.port,
.replica_check_frequency = replica_status.replica_check_frequency,
.ssl = replica_status.ssl,
},
storage);
if (ret.HasError()) {
MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
LOG_FATAL("Failure when restoring replica {}: {}.", replica_name, RegisterReplicaErrorToString(ret.GetError()));
}
spdlog::info("Replica {} restored.", replica_name);
}
}
constexpr uint16_t kEpochHistoryRetention = 1000;
void ReplicationState::NewEpoch() {
// Generate new epoch id and save the last one to the history.
if (history.size() == kEpochHistoryRetention) {
history.pop_front();
}
auto prevEpoch = epoch_.NewEpoch();
history.emplace_back(std::move(prevEpoch), last_commit_timestamp_);
}
void ReplicationState::AppendEpoch(std::string new_epoch) {
auto prevEpoch = epoch_.SetEpoch(std::move(new_epoch));
history.emplace_back(std::move(prevEpoch), last_commit_timestamp_);
}
} // namespace memgraph::storage

View File

@@ -1,120 +0,0 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "kvstore/kvstore.hpp"
#include "storage/v2/delta.hpp"
#include "storage/v2/durability/storage_global_operation.hpp"
#include "storage/v2/transaction.hpp"
#include "utils/result.hpp"
/// REPLICATION ///
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/global.hpp"
#include "storage/v2/replication/replication_persistence_helper.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
// TODO use replication namespace
namespace memgraph::storage {
class Storage;
class ReplicationServer;
class ReplicationClient;
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
struct ReplicationState {
// TODO: This mirrors the logic in InMemoryConstructor; make it independent
ReplicationState(bool restore, std::filesystem::path durability_dir);
// Generic API
void Reset();
// TODO: Just check if server exists -> you are REPLICA
replication::ReplicationRole GetRole() const { return replication_role_.load(); }
bool SetMainReplicationRole(Storage *storage); // Set the instance to MAIN
// TODO: ReplicationServer/Client uses Storage* for RPC callbacks
bool SetReplicaRole(const replication::ReplicationServerConfig &config,
Storage *storage); // Sets the instance to REPLICA
// Generic restoration
void RestoreReplicationRole(Storage *storage);
// MAIN actually doing the replication
bool AppendOperation(uint64_t seq_num, durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
void InitializeTransaction(uint64_t seq_num);
void AppendDelta(const Delta &delta, const Vertex &parent, uint64_t timestamp);
void AppendDelta(const Delta &delta, const Edge &parent, uint64_t timestamp);
bool FinalizeTransaction(uint64_t timestamp);
// MAIN connecting to replicas
utils::BasicResult<RegisterReplicaError> RegisterReplica(const replication::RegistrationMode registration_mode,
const replication::ReplicationClientConfig &config,
Storage *storage);
bool UnregisterReplica(std::string_view name);
// MAIN reconnecting to replicas
void RestoreReplicas(Storage *storage);
// MAIN getting info from replicas
// TODO make into const (problem with SpinLock and WithReadLock)
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name);
std::vector<ReplicaInfo> ReplicasInfo();
const ReplicationEpoch &GetEpoch() const { return epoch_; }
ReplicationEpoch &GetEpoch() { return epoch_; }
// Questions:
// - storage durability <- databases/*name*/wal and snapshots (where this for epoch_id)
// - multi-tenant durability <- databases/.durability (there is a list of all active tenants)
// History of the previous epoch ids.
// Each value consists of the epoch id along the last commit belonging to that
// epoch.
std::deque<std::pair<std::string, uint64_t>> history;
// TODO: actually durability
std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId};
void NewEpoch();
void AppendEpoch(std::string new_epoch);
private:
bool TryPersistReplicaClient(const replication::ReplicationClientConfig &config);
bool ShouldStoreAndRestoreReplicationState() const { return nullptr != durability_; }
void SetRole(replication::ReplicationRole role) { return replication_role_.store(role); }
// NOTE: Server is not in MAIN it is in REPLICA
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
// We create ReplicationClient using unique_ptr so we can move
// newly created client into the vector.
// We cannot move the client directly because it contains ThreadPool
// which cannot be moved. Also, the move is necessary because
// we don't want to create the client directly inside the vector
// because that would require the lock on the list putting all
// commits (they iterate list of clients) to halt.
// This way we can initialize client in main thread which means
// that we can immediately notify the user if the initialization
// failed.
using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>;
ReplicationClientList replication_clients_;
std::atomic<replication::ReplicationRole> replication_role_{replication::ReplicationRole::MAIN};
std::unique_ptr<kvstore::KVStore> durability_;
ReplicationEpoch epoch_;
};
} // namespace memgraph::storage

View File

@@ -15,28 +15,25 @@
#include <type_traits>
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/storage.hpp"
#include "storage/v2/transaction.hpp"
#include "utils/file_locker.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace memgraph::storage {
static auto CreateClientContext(const replication::ReplicationClientConfig &config) -> communication::ClientContext {
static auto CreateClientContext(const memgraph::replication::ReplicationClientConfig &config)
-> communication::ClientContext {
return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file}
: communication::ClientContext{};
}
ReplicationClient::ReplicationClient(Storage *storage, replication::ReplicationClientConfig const &config)
ReplicationClient::ReplicationClient(Storage *storage, const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *epoch)
: name_{config.name},
rpc_context_{CreateClientContext(config)},
rpc_client_{io::network::Endpoint(config.ip_address, config.port), &rpc_context_},
replica_check_frequency_{config.replica_check_frequency},
mode_{config.mode},
storage_{storage} {}
storage_{storage},
repl_epoch_{epoch} {}
ReplicationClient::~ReplicationClient() {
auto endpoint = rpc_client_.Endpoint();
@@ -45,21 +42,19 @@ ReplicationClient::~ReplicationClient() {
}
uint64_t ReplicationClient::LastCommitTimestamp() const {
return storage_->replication_state_.last_commit_timestamp_.load();
return storage_->repl_storage_state_.last_commit_timestamp_.load();
}
void ReplicationClient::InitializeClient() {
uint64_t current_commit_timestamp{kTimestampInitialId};
const auto &main_epoch = storage_->replication_state_.GetEpoch();
auto stream{rpc_client_.Stream<replication::HeartbeatRpc>(storage_->replication_state_.last_commit_timestamp_,
main_epoch.id)};
auto stream{rpc_client_.Stream<replication::HeartbeatRpc>(storage_->repl_storage_state_.last_commit_timestamp_,
std::string{repl_epoch_->id()})};
const auto replica = stream.AwaitResponse();
std::optional<uint64_t> branching_point;
if (replica.epoch_id != main_epoch.id && replica.current_commit_timestamp != kTimestampInitialId) {
auto const &history = storage_->replication_state_.history;
if (replica.epoch_id != repl_epoch_->id() && replica.current_commit_timestamp != kTimestampInitialId) {
auto const &history = storage_->repl_storage_state_.history;
const auto epoch_info_iter = std::find_if(history.crbegin(), history.crend(), [&](const auto &main_epoch_info) {
return main_epoch_info.first == replica.epoch_id;
});
@@ -81,8 +76,8 @@ void ReplicationClient::InitializeClient() {
current_commit_timestamp = replica.current_commit_timestamp;
spdlog::trace("Current timestamp on replica {}: {}", name_, current_commit_timestamp);
spdlog::trace("Current timestamp on main: {}", storage_->replication_state_.last_commit_timestamp_.load());
if (current_commit_timestamp == storage_->replication_state_.last_commit_timestamp_.load()) {
spdlog::trace("Current timestamp on main: {}", storage_->repl_storage_state_.last_commit_timestamp_.load());
if (current_commit_timestamp == storage_->repl_storage_state_.last_commit_timestamp_.load()) {
spdlog::debug("Replica '{}' up to date", name_);
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::READY);
@@ -109,7 +104,7 @@ TimestampInfo ReplicationClient::GetTimestampInfo() {
replica_state_.store(replication::ReplicaState::INVALID);
HandleRpcFailure();
}
auto main_time_stamp = storage_->replication_state_.last_commit_timestamp_.load();
auto main_time_stamp = storage_->repl_storage_state_.last_commit_timestamp_.load();
info.current_timestamp_of_replica = response.current_commit_timestamp;
info.current_number_of_timestamp_behind_master = response.current_commit_timestamp - main_time_stamp;
} catch (const rpc::RpcFailedException &) {
@@ -172,7 +167,7 @@ void ReplicationClient::StartTransactionReplication(const uint64_t current_wal_s
MG_ASSERT(!replica_stream_);
try {
replica_stream_.emplace(
ReplicaStream{this, storage_->replication_state_.last_commit_timestamp_.load(), current_wal_seq_num});
ReplicaStream{this, storage_->repl_storage_state_.last_commit_timestamp_.load(), current_wal_seq_num});
replica_state_.store(replication::ReplicaState::REPLICATING);
} catch (const rpc::RpcFailedException &) {
replica_state_.store(replication::ReplicaState::INVALID);
@@ -182,8 +177,6 @@ void ReplicationClient::StartTransactionReplication(const uint64_t current_wal_s
}
}
auto ReplicationClient::GetEpochId() const -> std::string const & { return storage_->replication_state_.GetEpoch().id; }
bool ReplicationClient::FinalizeTransactionReplication() {
// We can only check the state because it guarantees to be only
// valid during a single transaction replication (if the assumption
@@ -217,7 +210,7 @@ bool ReplicationClient::FinalizeTransactionReplication() {
return false;
};
if (mode_ == replication::ReplicationMode::ASYNC) {
if (mode_ == memgraph::replication::ReplicationMode::ASYNC) {
thread_pool_.AddTask([=] { (void)task(); });
return true;
}
@@ -289,7 +282,7 @@ ReplicaStream::ReplicaStream(ReplicationClient *self, const uint64_t previous_co
stream_(self_->rpc_client_.Stream<replication::AppendDeltasRpc>(previous_commit_timestamp, current_seq_num)) {
replication::Encoder encoder{stream_.GetBuilder()};
encoder.WriteString(self_->GetEpochId());
encoder.WriteString(self->repl_epoch_->id());
}
void ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp) {
@@ -308,10 +301,12 @@ void ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) {
EncodeTransactionEnd(&encoder, final_commit_timestamp);
}
void ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t timestamp) {
void ReplicaStream::AppendOperation(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats, uint64_t timestamp) {
replication::Encoder encoder(stream_.GetBuilder());
EncodeOperation(&encoder, self_->GetStorage()->name_id_mapper_.get(), operation, label, properties, timestamp);
EncodeOperation(&encoder, self_->GetStorage()->name_id_mapper_.get(), operation, label, properties, stats,
property_stats, timestamp);
}
replication::AppendDeltasRes ReplicaStream::Finalize() { return stream_.AwaitResponse(); }

View File

@@ -11,10 +11,13 @@
#pragma once
#include "replication/config.hpp"
#include "replication/epoch.hpp"
#include "rpc/client.hpp"
#include "storage/v2/durability/storage_global_operation.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/global.hpp"
#include "storage/v2/replication/rpc.hpp"
@@ -50,8 +53,9 @@ class ReplicaStream {
void AppendTransactionEnd(uint64_t final_commit_timestamp);
/// @throw rpc::RpcFailedException
void AppendOperation(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t timestamp);
void AppendOperation(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats, uint64_t timestamp);
/// @throw rpc::RpcFailedException
replication::AppendDeltasRes Finalize();
@@ -66,7 +70,8 @@ class ReplicationClient {
friend class ReplicaStream;
public:
ReplicationClient(Storage *storage, replication::ReplicationClientConfig const &config);
ReplicationClient(Storage *storage, const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *epoch);
ReplicationClient(ReplicationClient const &) = delete;
ReplicationClient &operator=(ReplicationClient const &) = delete;
@@ -75,7 +80,7 @@ class ReplicationClient {
virtual ~ReplicationClient();
auto Mode() const -> replication::ReplicationMode { return mode_; }
auto Mode() const -> memgraph::replication::ReplicationMode { return mode_; }
auto Name() const -> std::string const & { return name_; }
auto Endpoint() const -> io::network::Endpoint const & { return rpc_client_.Endpoint(); }
auto State() const -> replication::ReplicaState { return replica_state_.load(); }
@@ -96,7 +101,6 @@ class ReplicationClient {
virtual void RecoverReplica(uint64_t replica_commit) = 0;
auto GetStorage() -> Storage * { return storage_; }
auto GetEpochId() const -> std::string const &;
auto LastCommitTimestamp() const -> uint64_t;
void InitializeClient();
void HandleRpcFailure();
@@ -110,7 +114,7 @@ class ReplicationClient {
std::chrono::seconds replica_check_frequency_;
std::optional<ReplicaStream> replica_stream_;
replication::ReplicationMode mode_{replication::ReplicationMode::SYNC};
memgraph::replication::ReplicationMode mode_{memgraph::replication::ReplicationMode::SYNC};
utils::SpinLock client_lock_;
// This thread pool is used for background tasks so we don't
@@ -131,6 +135,8 @@ class ReplicationClient {
utils::Scheduler replica_checker_;
Storage *storage_;
memgraph::replication::ReplicationEpoch const *repl_epoch_;
};
} // namespace memgraph::storage

View File

@@ -0,0 +1,204 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v2/replication/replication_handler.hpp"
#include "replication/state.hpp"
#include "storage/v2/storage.hpp"
namespace memgraph::storage {
namespace {
std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
switch (error) {
using enum RegisterReplicaError;
case NAME_EXISTS:
return "NAME_EXISTS";
case END_POINT_EXISTS:
return "END_POINT_EXISTS";
case CONNECTION_FAILED:
return "CONNECTION_FAILED";
case COULD_NOT_BE_PERSISTED:
return "COULD_NOT_BE_PERSISTED";
}
}
} // namespace
bool ReplicationHandler::SetReplicationRoleMain() {
// We don't want to generate new epoch_id and do the
// cleanup if we're already a MAIN
// TODO: under lock
if (repl_state_.IsMain()) {
return false;
}
// STEP 1) bring down all REPLICA servers
auto current_epoch = std::string(repl_state_.GetEpoch().id());
{ // TODO: foreach storage
// ensure replica server brought down
storage_.repl_storage_state_.replication_server_.reset(nullptr);
// Remember old epoch + storage timestamp association
storage_.repl_storage_state_.AddEpochToHistory(current_epoch);
}
// STEP 2) Change to MAIN
repl_state_.GetEpoch().NewEpoch();
if (!repl_state_.TryPersistRoleMain()) {
// TODO: On failure restore old epoch? restore replication servers?
return false;
}
repl_state_.SetRole(memgraph::replication::ReplicationRole::MAIN);
return true;
}
memgraph::utils::BasicResult<RegisterReplicaError> ReplicationHandler::RegisterReplica(
const RegistrationMode registration_mode, const memgraph::replication::ReplicationClientConfig &config) {
MG_ASSERT(repl_state_.IsMain(), "Only main instance can register a replica!");
auto name_check = [&config](auto &clients) {
auto name_matches = [&name = config.name](const auto &client) { return client->Name() == name; };
return std::any_of(clients.begin(), clients.end(), name_matches);
};
auto desired_endpoint = io::network::Endpoint{config.ip_address, config.port};
auto endpoint_check = [&](auto &clients) {
auto endpoint_matches = [&](const auto &client) { return client->Endpoint() == desired_endpoint; };
return std::any_of(clients.begin(), clients.end(), endpoint_matches);
};
auto task = [&](auto &clients) -> utils::BasicResult<RegisterReplicaError> {
if (name_check(clients)) {
return RegisterReplicaError::NAME_EXISTS;
}
if (endpoint_check(clients)) {
return RegisterReplicaError::END_POINT_EXISTS;
}
using enum RegistrationMode;
if (registration_mode != RESTORE && !repl_state_.TryPersistRegisteredReplica(config)) {
return RegisterReplicaError::COULD_NOT_BE_PERSISTED;
}
auto client = storage_.CreateReplicationClient(config);
client->Start();
if (client->State() == replication::ReplicaState::INVALID) {
if (registration_mode != RESTORE) {
return RegisterReplicaError::CONNECTION_FAILED;
}
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.", client->Name());
}
clients.push_back(std::move(client));
return {};
};
return storage_.repl_storage_state_.replication_clients_.WithLock(task);
}
bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) {
// We don't want to restart the server if we're already a REPLICA
if (repl_state_.IsReplica()) {
return false;
}
std::unique_ptr<ReplicationServer> replication_server = storage_.CreateReplicationServer(config);
bool res = replication_server->Start();
if (!res) {
spdlog::error("Unable to start the replication server.");
return false;
}
storage_.repl_storage_state_.replication_server_ = std::move(replication_server);
if (!repl_state_.TryPersistRoleReplica(config)) {
return false;
}
repl_state_.SetRole(memgraph::replication::ReplicationRole::REPLICA);
return true;
}
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterReplicaResult {
if (repl_state_.IsReplica()) {
return UnregisterReplicaResult::NOT_MAIN;
}
if (!repl_state_.TryPersistUnregisterReplica(name)) {
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
}
auto const n_unregistered = storage_.repl_storage_state_.replication_clients_.WithLock([&](auto &clients) {
return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; });
});
return (n_unregistered != 0) ? UnregisterReplicaResult::SUCCESS : UnregisterReplicaResult::CAN_NOT_UNREGISTER;
}
void ReplicationHandler::RestoreReplication() {
if (!repl_state_.ShouldPersist()) {
return;
}
spdlog::info("Restoring replication role.");
using memgraph::replication::ReplicationState;
auto replicationData = repl_state_.FetchReplicationData();
if (replicationData.HasError()) {
switch (replicationData.GetError()) {
using enum ReplicationState::FetchReplicationError;
case NOTHING_FETCHED: {
spdlog::debug("Cannot find data needed for restore replication role in persisted metadata.");
return;
}
case PARSE_ERROR: {
LOG_FATAL("Cannot parse previously saved configuration of replication role.");
return;
}
}
}
/// MAIN
auto const recover_main = [this](ReplicationState::ReplicationDataMain const &configs) {
storage_.repl_storage_state_.replication_server_.reset();
repl_state_.SetRole(memgraph::replication::ReplicationRole::MAIN);
for (const auto &config : configs) {
spdlog::info("Replica {} restored for {}.", config.name, storage_.id());
auto ret = RegisterReplica(RegistrationMode::RESTORE, config);
if (ret.HasError()) {
MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
LOG_FATAL("Failure when restoring replica {}: {}.", config.name, RegisterReplicaErrorToString(ret.GetError()));
}
spdlog::info("Replica {} restored for {}.", config.name, storage_.id());
}
spdlog::info("Replication role restored to MAIN.");
};
/// REPLICA
auto const recover_replica = [this](ReplicationState::ReplicationDataReplica const &config) {
auto replication_server = storage_.CreateReplicationServer(config);
if (!replication_server->Start()) {
LOG_FATAL("Unable to start the replication server.");
}
storage_.repl_storage_state_.replication_server_ = std::move(replication_server);
repl_state_.SetRole(memgraph::replication::ReplicationRole::REPLICA);
spdlog::info("Replication role restored to REPLICA.");
};
std::visit(
utils::Overloaded{
recover_main,
recover_replica,
},
*replicationData);
}
auto ReplicationHandler::GetRole() const -> memgraph::replication::ReplicationRole { return repl_state_.GetRole(); }
bool ReplicationHandler::IsMain() const { return repl_state_.IsMain(); }
bool ReplicationHandler::IsReplica() const { return repl_state_.IsReplica(); }
} // namespace memgraph::storage

View File

@@ -0,0 +1,71 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "replication/role.hpp"
#include "utils/result.hpp"
// BEGIN fwd declares
namespace memgraph::replication {
struct ReplicationState;
struct ReplicationServerConfig;
struct ReplicationClientConfig;
} // namespace memgraph::replication
namespace memgraph::storage {
class Storage;
}
// END fwd declares
namespace memgraph::storage {
enum class RegistrationMode : std::uint8_t { MUST_BE_INSTANTLY_VALID, RESTORE };
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
enum class UnregisterReplicaResult : uint8_t {
NOT_MAIN,
COULD_NOT_BE_PERSISTED,
CAN_NOT_UNREGISTER,
SUCCESS,
};
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
/// TODO: extend to do multiple storages
struct ReplicationHandler {
ReplicationHandler(memgraph::replication::ReplicationState &replState, Storage &storage)
: repl_state_(replState), storage_(storage) {}
// as REPLICA, become MAIN
bool SetReplicationRoleMain();
// as MAIN, become REPLICA
bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config);
// as MAIN, define and connect to REPLICAs
auto RegisterReplica(RegistrationMode registration_mode, const memgraph::replication::ReplicationClientConfig &config)
-> utils::BasicResult<RegisterReplicaError>;
// as MAIN, remove a REPLICA connection
auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult;
// Generic restoration
// TODO: decouple storage restoration from epoch restoration
void RestoreReplication();
// Helper pass-through (TODO: remove)
auto GetRole() const -> memgraph::replication::ReplicationRole;
bool IsMain() const;
bool IsReplica() const;
private:
memgraph::replication::ReplicationState &repl_state_;
Storage &storage_;
};
} // namespace memgraph::storage

View File

@@ -11,13 +11,13 @@
#include "replication_server.hpp"
#include "io/network/endpoint.hpp"
#include "replication/config.hpp"
#include "rpc.hpp"
#include "storage/v2/replication/config.hpp"
namespace memgraph::storage {
namespace {
auto CreateServerContext(const replication::ReplicationServerConfig &config) -> communication::ServerContext {
auto CreateServerContext(const memgraph::replication::ReplicationServerConfig &config) -> communication::ServerContext {
return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
config.ssl->verify_peer}
: communication::ServerContext{};
@@ -30,7 +30,7 @@ auto CreateServerContext(const replication::ReplicationServerConfig &config) ->
constexpr auto kReplictionServerThreads = 1;
} // namespace
ReplicationServer::ReplicationServer(const replication::ReplicationServerConfig &config)
ReplicationServer::ReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
: rpc_server_context_{CreateServerContext(config)},
rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
kReplictionServerThreads} {

View File

@@ -11,16 +11,16 @@
#pragma once
#include "replication/config.hpp"
#include "rpc/server.hpp"
#include "slk/streams.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/global.hpp"
namespace memgraph::storage {
class ReplicationServer {
public:
explicit ReplicationServer(const replication::ReplicationServerConfig &config);
explicit ReplicationServer(const memgraph::replication::ReplicationServerConfig &config);
ReplicationServer(const ReplicationServer &) = delete;
ReplicationServer(ReplicationServer &&) = delete;
ReplicationServer &operator=(const ReplicationServer &) = delete;

View File

@@ -0,0 +1,111 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
namespace memgraph::storage {
void ReplicationStorageState::InitializeTransaction(uint64_t seq_num) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->StartTransactionReplication(seq_num);
}
});
}
void ReplicationStorageState::AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendDelta(delta, vertex, timestamp); });
}
});
}
void ReplicationStorageState::AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendDelta(delta, edge, timestamp); });
}
});
}
void ReplicationStorageState::AppendOperation(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats,
uint64_t final_commit_timestamp) {
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) {
stream.AppendOperation(operation, label, properties, stats, property_stats, final_commit_timestamp);
});
}
});
}
bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp) {
return replication_clients_.WithLock([=](auto &clients) {
bool finalized_on_all_replicas = true;
for (ReplicationClientPtr &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(timestamp); });
const auto finalized = client->FinalizeTransactionReplication();
if (client->Mode() == memgraph::replication::ReplicationMode::SYNC) {
finalized_on_all_replicas = finalized && finalized_on_all_replicas;
}
}
return finalized_on_all_replicas;
});
}
std::optional<replication::ReplicaState> ReplicationStorageState::GetReplicaState(std::string_view name) const {
return replication_clients_.WithReadLock([&](auto const &clients) -> std::optional<replication::ReplicaState> {
auto const name_matches = [=](ReplicationClientPtr const &client) { return client->Name() == name; };
auto const client_it = std::find_if(clients.cbegin(), clients.cend(), name_matches);
if (client_it == clients.cend()) {
return std::nullopt;
}
return (*client_it)->State();
});
}
std::vector<ReplicaInfo> ReplicationStorageState::ReplicasInfo() const {
return replication_clients_.WithReadLock([](auto const &clients) {
std::vector<ReplicaInfo> replica_infos;
replica_infos.reserve(clients.size());
auto const asReplicaInfo = [](ReplicationClientPtr const &client) -> ReplicaInfo {
return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo()};
};
std::transform(clients.begin(), clients.end(), std::back_inserter(replica_infos), asReplicaInfo);
return replica_infos;
});
}
void ReplicationStorageState::Reset() {
replication_server_.reset();
replication_clients_.WithLock([](auto &clients) { clients.clear(); });
}
void ReplicationStorageState::AddEpochToHistory(std::string prev_epoch) {
constexpr uint16_t kEpochHistoryRetention = 1000;
// Generate new epoch id and save the last one to the history.
if (history.size() == kEpochHistoryRetention) {
history.pop_front();
}
history.emplace_back(std::move(prev_epoch), last_commit_timestamp_);
}
void ReplicationStorageState::AddEpochToHistoryForce(std::string prev_epoch) {
history.emplace_back(std::move(prev_epoch), last_commit_timestamp_);
}
} // namespace memgraph::storage

View File

@@ -0,0 +1,85 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include "kvstore/kvstore.hpp"
#include "storage/v2/delta.hpp"
#include "storage/v2/durability/storage_global_operation.hpp"
#include "storage/v2/transaction.hpp"
#include "utils/result.hpp"
/// REPLICATION ///
#include "replication/config.hpp"
#include "replication/epoch.hpp"
#include "replication/state.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/global.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
namespace memgraph::storage {
class Storage;
class ReplicationServer;
class ReplicationClient;
struct ReplicationStorageState {
// Only MAIN can send
void InitializeTransaction(uint64_t seq_num);
void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp);
void AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp);
void AppendOperation(durability::StorageMetadataOperation operation, LabelId label,
const std::set<PropertyId> &properties, const LabelIndexStats &stats,
const LabelPropertyIndexStats &property_stats, uint64_t final_commit_timestamp);
bool FinalizeTransaction(uint64_t timestamp);
// Getters
auto GetReplicaState(std::string_view name) const -> std::optional<replication::ReplicaState>;
auto ReplicasInfo() const -> std::vector<ReplicaInfo>;
// History
void AddEpochToHistory(std::string prev_epoch);
void AddEpochToHistoryForce(std::string prev_epoch);
void Reset();
// Questions:
// - storage durability <- databases/*name*/wal and snapshots (where this for epoch_id)
// - multi-tenant durability <- databases/.durability (there is a list of all active tenants)
// History of the previous epoch ids.
// Each value consists of the epoch id along the last commit belonging to that
// epoch.
std::deque<std::pair<std::string, uint64_t>> history;
std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId};
// We create ReplicationClient using unique_ptr so we can move
// newly created client into the vector.
// We cannot move the client directly because it contains ThreadPool
// which cannot be moved. Also, the move is necessary because
// we don't want to create the client directly inside the vector
// because that would require the lock on the list putting all
// commits (they iterate list of clients) to halt.
// This way we can initialize client in main thread which means
// that we can immediately notify the user if the initialization
// failed.
using ReplicationClientPtr = std::unique_ptr<ReplicationClient>;
using ReplicationClientList = utils::Synchronized<std::vector<ReplicationClientPtr>, utils::RWSpinLock>;
// NOTE: Server is not in MAIN it is in REPLICA
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
ReplicationClientList replication_clients_;
};
} // namespace memgraph::storage

View File

@@ -63,7 +63,7 @@ struct HeartbeatReq {
static void Save(const HeartbeatReq &self, memgraph::slk::Builder *builder);
HeartbeatReq() {}
HeartbeatReq(uint64_t main_commit_timestamp, std::string epoch_id)
: main_commit_timestamp(main_commit_timestamp), epoch_id(epoch_id) {}
: main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {}
uint64_t main_commit_timestamp;
std::string epoch_id;

View File

@@ -9,6 +9,7 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <thread>
#include "absl/container/flat_hash_set.h"
#include "spdlog/spdlog.h"
@@ -39,8 +40,14 @@ class InMemoryStorage;
using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler;
auto ReplicationStateHelper(Config const &config) -> std::optional<std::filesystem::path> {
if (!config.durability.restore_replication_state_on_startup) return std::nullopt;
return {config.durability.storage_directory};
}
Storage::Storage(Config config, StorageMode storage_mode)
: name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> {
: repl_state_(ReplicationStateHelper(config)),
name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> {
if (storage_mode == StorageMode::ON_DISK_TRANSACTIONAL) {
return std::make_unique<DiskNameIdMapper>(config.disk.name_id_mapper_directory,
config.disk.id_name_mapper_directory);
@@ -52,16 +59,28 @@ Storage::Storage(Config config, StorageMode storage_mode)
storage_mode_(storage_mode),
indices_(config, storage_mode),
constraints_(config, storage_mode),
id_(config.name),
replication_state_(config_.durability.restore_replication_state_on_startup,
config_.durability.storage_directory) {}
id_(config.name) {}
Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode)
Storage::Accessor::Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_),
unique_guard_(storage_->main_lock_, std::defer_lock),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}
Storage::Accessor::Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_, std::defer_lock),
unique_guard_(storage_->main_lock_),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}
@@ -69,6 +88,7 @@ Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level, St
Storage::Accessor::Accessor(Accessor &&other) noexcept
: storage_(other.storage_),
storage_guard_(std::move(other.storage_guard_)),
unique_guard_(std::move(other.unique_guard_)),
transaction_(std::move(other.transaction_)),
commit_timestamp_(other.commit_timestamp_),
is_transaction_active_(other.is_transaction_active_),
@@ -78,16 +98,6 @@ Storage::Accessor::Accessor(Accessor &&other) noexcept
other.commit_timestamp_.reset();
}
IndicesInfo Storage::ListAllIndices() const {
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
return {indices_.label_index_->ListIndices(), indices_.label_property_index_->ListIndices()};
}
ConstraintsInfo Storage::ListAllConstraints() const {
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
return {constraints_.existence_constraints_->ListConstraints(), constraints_.unique_constraints_->ListConstraints()};
}
/// Main lock is taken by the caller.
void Storage::SetStorageMode(StorageMode storage_mode) {
std::unique_lock main_guard{main_lock_};

View File

@@ -11,11 +11,15 @@
#pragma once
#include <chrono>
#include <semaphore>
#include <span>
#include <thread>
#include "io/network/endpoint.hpp"
#include "kvstore/kvstore.hpp"
#include "query/exceptions.hpp"
#include "replication/config.hpp"
#include "storage/v2/all_vertices_iterable.hpp"
#include "storage/v2/commit_log.hpp"
#include "storage/v2/config.hpp"
@@ -24,17 +28,17 @@
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/indices/indices.hpp"
#include "storage/v2/mvcc.hpp"
#include "storage/v2/replication/config.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/replication.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/storage_error.hpp"
#include "storage/v2/storage_mode.hpp"
#include "storage/v2/transaction.hpp"
#include "storage/v2/vertices_iterable.hpp"
#include "utils/event_counter.hpp"
#include "utils/event_histogram.hpp"
#include "utils/resource_lock.hpp"
#include "utils/scheduler.hpp"
#include "utils/timer.hpp"
#include "utils/uuid.hpp"
@@ -94,7 +98,13 @@ class Storage {
class Accessor {
public:
Accessor(Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
static constexpr struct SharedAccess {
} shared_access;
static constexpr struct UniqueAccess {
} unique_access;
Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
Accessor(const Accessor &) = delete;
Accessor &operator=(const Accessor &) = delete;
Accessor &operator=(Accessor &&other) = delete;
@@ -149,14 +159,10 @@ class Storage {
virtual void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
const LabelPropertyIndexStats &stats) = 0;
virtual std::vector<std::pair<LabelId, PropertyId>> ClearLabelPropertyIndexStats() = 0;
virtual std::vector<LabelId> ClearLabelIndexStats() = 0;
virtual std::vector<std::pair<LabelId, PropertyId>> DeleteLabelPropertyIndexStats(
std::span<std::string> labels) = 0;
const storage::LabelId &label) = 0;
virtual std::vector<LabelId> DeleteLabelIndexStats(std::span<std::string> labels) = 0;
virtual bool DeleteLabelIndexStats(const storage::LabelId &label) = 0;
virtual void PrefetchInEdges(const VertexAccessor &vertex_acc) = 0;
@@ -179,7 +185,7 @@ class Storage {
virtual ConstraintsInfo ListAllConstraints() const = 0;
// NOLINTNEXTLINE(google-default-arguments)
virtual utils::BasicResult<StorageDataManipulationError, void> Commit(
virtual utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) = 0;
virtual void Abort() = 0;
@@ -206,9 +212,30 @@ class Storage {
const std::string &id() const { return storage_->id(); }
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) = 0;
virtual utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property) = 0;
virtual utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property) = 0;
virtual utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties) = 0;
virtual UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label,
const std::set<PropertyId> &properties) = 0;
protected:
Storage *storage_;
std::shared_lock<utils::RWLock> storage_guard_;
std::shared_lock<utils::ResourceLock> storage_guard_;
std::unique_lock<utils::ResourceLock> unique_guard_; // TODO: Split the accessor into Shared/Unique
Transaction transaction_;
std::optional<uint64_t> commit_timestamp_;
bool is_transaction_active_;
@@ -251,58 +278,15 @@ class Storage {
StorageMode GetStorageMode() const;
virtual void FreeMemory(std::unique_lock<utils::RWLock> main_guard) = 0;
virtual void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) = 0;
void FreeMemory() { FreeMemory({}); }
virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) = 0;
std::unique_ptr<Accessor> Access() { return Access(std::optional<IsolationLevel>{}); }
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) = 0;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) {
return CreateIndex(label, std::optional<uint64_t>{});
}
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) {
return CreateIndex(label, property, std::optional<uint64_t>{});
}
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp) = 0;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) {
return DropIndex(label, std::optional<uint64_t>{});
}
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label, PropertyId property) {
return DropIndex(label, property, std::optional<uint64_t>{});
}
IndicesInfo ListAllIndices() const;
virtual utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0;
virtual utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp) = 0;
virtual utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
std::optional<uint64_t> desired_commit_timestamp) = 0;
virtual utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus>
DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
std::optional<uint64_t> desired_commit_timestamp) = 0;
ConstraintsInfo ListAllConstraints() const;
virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) = 0;
std::unique_ptr<Accessor> UniqueAccess() { return UniqueAccess(std::optional<IsolationLevel>{}); }
enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode };
@@ -313,37 +297,22 @@ class Storage {
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) = 0;
virtual void EstablishNewEpoch() = 0;
virtual void PrepareForNewEpoch(std::string prev_epoch) = 0;
virtual auto CreateReplicationClient(replication::ReplicationClientConfig const &config)
virtual auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
-> std::unique_ptr<ReplicationClient> = 0;
virtual auto CreateReplicationServer(const replication::ReplicationServerConfig &config)
virtual auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> = 0;
/// REPLICATION
bool SetReplicaRole(const replication::ReplicationServerConfig &config) {
return replication_state_.SetReplicaRole(config, this);
}
bool SetMainReplicationRole() { return replication_state_.SetMainReplicationRole(this); }
/// @pre The instance should have a MAIN role
/// @pre Timeout can only be set for SYNC replication
auto RegisterReplica(const replication::RegistrationMode registration_mode,
const replication::ReplicationClientConfig &config) {
return replication_state_.RegisterReplica(registration_mode, config, this);
}
/// @pre The instance should have a MAIN role
bool UnregisterReplica(const std::string &name) { return replication_state_.UnregisterReplica(name); }
replication::ReplicationRole GetReplicationRole() const { return replication_state_.GetRole(); }
auto ReplicasInfo() { return replication_state_.ReplicasInfo(); }
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name) {
return replication_state_.GetReplicaState(name);
auto ReplicasInfo() { return repl_storage_state_.ReplicasInfo(); }
auto GetReplicaState(std::string_view name) -> std::optional<replication::ReplicaState> {
return repl_storage_state_.GetReplicaState(name);
}
protected:
void RestoreReplicas() { return replication_state_.RestoreReplicas(this); }
void RestoreReplicationRole() { return replication_state_.RestoreReplicationRole(this); }
// TODO: make non-public
memgraph::replication::ReplicationState repl_state_;
ReplicationStorageState repl_storage_state_;
public:
// Main storage lock.
@@ -351,7 +320,7 @@ class Storage {
// creation of new accessors by taking a unique lock. This is used when doing
// operations on storage that affect the global state, for example index
// creation.
mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE};
mutable utils::ResourceLock main_lock_;
// Even though the edge count is already kept in the `edges_` SkipList, the
// list is used only when properties are enabled for edges. Because of that we
@@ -376,9 +345,6 @@ class Storage {
std::atomic<uint64_t> vertex_id_{0};
std::atomic<uint64_t> edge_id_{0};
const std::string id_; //!< High-level assigned ID
protected:
ReplicationState replication_state_;
};
} // namespace memgraph::storage

View File

@@ -19,30 +19,27 @@
namespace memgraph::storage {
struct ReplicationError {};
struct PersistenceError {}; // TODO: Generalize and add to InMemory durability as well (currently durability just
// asserts and terminated if failed)
struct IndexPersistenceError {};
struct IndexDefinitionError {};
struct ConstraintsPersistenceError {};
struct SerializationError {};
inline bool operator==(const SerializationError & /*err1*/, const SerializationError & /*err2*/) { return true; }
using StorageDataManipulationError = std::variant<ConstraintViolation, ReplicationError, SerializationError>;
using StorageManipulationError =
std::variant<ConstraintViolation, ReplicationError, SerializationError, PersistenceError>;
struct IndexDefinitionError {};
using StorageIndexDefinitionError = std::variant<IndexDefinitionError, ReplicationError, IndexPersistenceError>;
using StorageIndexDefinitionError = IndexDefinitionError;
struct ConstraintDefinitionError {};
using StorageExistenceConstraintDefinitionError =
std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>;
using StorageExistenceConstraintDefinitionError = std::variant<ConstraintViolation, ConstraintDefinitionError>;
using StorageExistenceConstraintDroppingError =
std::variant<ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>;
using StorageExistenceConstraintDroppingError = ConstraintDefinitionError;
using StorageUniqueConstraintDefinitionError =
std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError, ConstraintsPersistenceError>;
using StorageUniqueConstraintDroppingError = std::variant<ReplicationError, ConstraintsPersistenceError>;
using StorageUniqueConstraintDefinitionError = std::variant<ConstraintViolation, ConstraintDefinitionError>;
} // namespace memgraph::storage

View File

@@ -22,6 +22,7 @@
#include "storage/v2/delta.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/isolation_level.hpp"
#include "storage/v2/metadata_delta.hpp"
#include "storage/v2/modified_edge.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/storage_mode.hpp"
@@ -44,6 +45,7 @@ struct Transaction {
start_timestamp(start_timestamp),
command_id(0),
deltas(1024UL),
md_deltas(utils::NewDeleteResource()),
must_abort(false),
isolation_level(isolation_level),
storage_mode(storage_mode),
@@ -55,6 +57,7 @@ struct Transaction {
commit_timestamp(std::move(other.commit_timestamp)),
command_id(other.command_id),
deltas(std::move(other.deltas)),
md_deltas(std::move(other.md_deltas)),
must_abort(other.must_abort),
isolation_level(other.isolation_level),
storage_mode(other.storage_mode),
@@ -93,6 +96,7 @@ struct Transaction {
uint64_t command_id;
Bond<PmrListDelta> deltas;
utils::pmr::list<MetadataDelta> md_deltas;
bool must_abort;
IsolationLevel isolation_level;
StorageMode storage_mode;

View File

@@ -30,7 +30,7 @@ struct Vertex {
"Vertex must be created with an initial DELETE_OBJECT delta!");
}
Gid gid;
const Gid gid;
std::vector<LabelId> labels;
PropertyStore properties;

View File

@@ -25,6 +25,7 @@ find_package(gflags REQUIRED)
find_package(Threads REQUIRED)
add_library(mg-utils STATIC ${utils_src_files})
add_library(mg::utils ALIAS mg-utils)
target_link_libraries(mg-utils PUBLIC Boost::headers fmt::fmt spdlog::spdlog)
target_link_libraries(mg-utils PRIVATE librdtsc stdc++fs Threads::Threads gflags json uuid rt)

View File

@@ -0,0 +1,81 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <condition_variable>
#include <cstdint>
#include <mutex>
namespace memgraph::utils {
/* A type that complies to the
* C++ named requirements `SharedLockable`
*
* Unlike `std::shared_mutex` it can be locked in one thread
* and unlocked in another.
*/
struct ResourceLock {
private:
enum states { UNLOCKED, UNIQUE, SHARED };
public:
void lock() {
auto lock = std::unique_lock{mtx};
// block until available
cv.wait(lock, [this] { return state == UNLOCKED; });
state = UNIQUE;
}
void lock_shared() {
auto lock = std::unique_lock{mtx};
// block until available
cv.wait(lock, [this] { return state != UNIQUE; });
state = SHARED;
++count;
}
bool try_lock() {
auto lock = std::unique_lock{mtx};
if (state == UNLOCKED) {
state = UNIQUE;
return true;
}
return false;
}
bool try_lock_shared() {
auto lock = std::unique_lock{mtx};
if (state != UNIQUE) {
state = SHARED;
++count;
return true;
}
return false;
}
void unlock() {
auto lock = std::unique_lock{mtx};
state = UNLOCKED;
cv.notify_all(); // multiple lock_shared maybe waiting
}
void unlock_shared() {
auto lock = std::unique_lock{mtx};
--count;
if (count == 0) {
state = UNLOCKED;
cv.notify_one(); // should be 0 waiting in lock_shared, only 1 wait in lock can progress
}
}
private:
std::mutex mtx;
std::condition_variable cv;
states state = UNLOCKED;
uint64_t count = 0;
};
} // namespace memgraph::utils

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -126,4 +126,9 @@ class WritePrioritizedRWLock final : public RWLock {
WritePrioritizedRWLock() : RWLock{Priority::WRITE} {};
};
class ReadPrioritizedRWLock final : public RWLock {
public:
ReadPrioritizedRWLock() : RWLock{Priority::READ} {};
};
} // namespace memgraph::utils

56
src/utils/simple_json.hpp Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <string>
namespace memgraph::utils {
static inline std::regex GetRegexKey(const std::string &key) {
return std::regex("\"" + key + "\"\\s*:\\s*\"([^\"]+)\"|\"" + key + "\"\\s*:\\s*([^,\\}]+)");
}
// Function to parse JSON string and return a value by key as a string.
static inline bool GetJsonValue(const std::string &jsonString, const std::string &key, std::string &out) {
const auto regex = GetRegexKey(key);
std::smatch match;
if (std::regex_search(jsonString, match, regex)) {
out = match[1].str();
return true;
}
return false;
}
// Overloaded GetJsonValue function for different types.
template <typename T>
static inline bool GetJsonValue(const std::string &jsonString, const std::string &key, T &out) {
const auto regex = GetRegexKey(key);
std::smatch match;
if (std::regex_search(jsonString, match, regex)) {
for (size_t i = 1; i < match.size(); ++i) {
const auto &m = match[i].str();
if (m.empty()) continue;
std::istringstream ss(m);
ss >> out;
return true;
}
}
return false;
}
}; // namespace memgraph::utils

View File

@@ -163,7 +163,8 @@ enum class TypeId : uint64_t {
AST_MERGE,
AST_UNWIND,
AST_AUTH_QUERY,
AST_INFO_QUERY,
AST_DATABASE_INFO_QUERY,
AST_SYSTEM_INFO_QUERY,
AST_CONSTRAINT,
AST_CONSTRAINT_QUERY,
AST_DUMP_QUERY,