Merge branch 'master' into T1312-MG-Cache-vertex-reads-on-disk
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
*
|
||||
|
||||
4
src/distributed/CMakeLists.txt
Normal file
4
src/distributed/CMakeLists.txt
Normal 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)
|
||||
61
src/distributed/include/distributed/lamport_clock.hpp
Normal file
61
src/distributed/include/distributed/lamport_clock.hpp
Normal 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
|
||||
11
src/distributed/lamport_clock.cpp
Normal file
11
src/distributed/lamport_clock.cpp
Normal 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"
|
||||
@@ -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 ¤t = GetDatabaseName();
|
||||
const auto ¤t = 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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ query : cypherQuery
|
||||
| indexQuery
|
||||
| explainQuery
|
||||
| profileQuery
|
||||
| infoQuery
|
||||
| databaseInfoQuery
|
||||
| systemInfoQuery
|
||||
| constraintQuery
|
||||
| authQuery
|
||||
| dumpQuery
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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> ¶ms,
|
||||
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> ¶ms,
|
||||
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
|
||||
|
||||
@@ -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([¬_found_midpoint, &maybe_kill_transaction_ids, username, hasTransactionManagementPrivilege,
|
||||
filter_db_acc = &calling_interpreter.db_acc_](const auto &interpreters) {
|
||||
interpreters.WithLock([¬_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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 "
|
||||
|
||||
24
src/replication/CMakeLists.txt
Normal file
24
src/replication/CMakeLists.txt
Normal 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
|
||||
)
|
||||
11
src/replication/config.cpp
Normal file
11
src/replication/config.cpp
Normal 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
12
src/replication/epoch.cpp
Normal 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"
|
||||
@@ -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
|
||||
49
src/replication/include/replication/epoch.hpp
Normal file
49
src/replication/include/replication/epoch.hpp
Normal 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
|
||||
16
src/replication/include/replication/mode.hpp
Normal file
16
src/replication/include/replication/mode.hpp
Normal 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 };
|
||||
}
|
||||
18
src/replication/include/replication/role.hpp
Normal file
18
src/replication/include/replication/role.hpp
Normal 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 };
|
||||
}
|
||||
69
src/replication/include/replication/state.hpp
Normal file
69
src/replication/include/replication/state.hpp
Normal 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
|
||||
@@ -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
144
src/replication/state.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
35
src/storage/v2/indices/label_index_stats.hpp
Normal file
35
src/storage/v2/indices/label_index_stats.hpp
Normal 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
|
||||
40
src/storage/v2/indices/label_property_index_stats.hpp
Normal file
40
src/storage/v2/indices/label_property_index_stats.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
157
src/storage/v2/metadata_delta.hpp
Normal file
157
src/storage/v2/metadata_delta.hpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
|
||||
|
||||
204
src/storage/v2/replication/replication_handler.cpp
Normal file
204
src/storage/v2/replication/replication_handler.cpp
Normal 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
|
||||
71
src/storage/v2/replication/replication_handler.hpp
Normal file
71
src/storage/v2/replication/replication_handler.hpp
Normal 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
|
||||
@@ -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} {
|
||||
|
||||
@@ -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;
|
||||
|
||||
111
src/storage/v2/replication/replication_storage_state.cpp
Normal file
111
src/storage/v2/replication/replication_storage_state.cpp
Normal 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
|
||||
85
src/storage/v2/replication/replication_storage_state.hpp
Normal file
85
src/storage/v2/replication/replication_storage_state.hpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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_};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
81
src/utils/resource_lock.hpp
Normal file
81
src/utils/resource_lock.hpp
Normal 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
|
||||
@@ -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
56
src/utils/simple_json.hpp
Normal 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
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user