Replication refactor (part 5) (#1378)

This commit is contained in:
andrejtonev 2023-11-06 12:50:49 +01:00 committed by GitHub
parent 16b8c7b27c
commit dbc6054689
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 3180 additions and 2313 deletions

View File

@ -42,10 +42,6 @@ endif()
project(memgraph LANGUAGES C CXX)
# NOTE: once in a while this needs to be toggled to check headers are
# correct and PCH isn't masking any include issues
set(CMAKE_DISABLE_PRECOMPILE_HEADERS OFF)
#TODO: upgrade to cmake 3.24 + CheckIPOSupported
#cmake_policy(SET CMP0138 NEW)
#include(CheckIPOSupported)

View File

@ -143,7 +143,7 @@ install(CODE "file(MAKE_DIRECTORY \$ENV{DESTDIR}/var/log/memgraph
# Memgraph CSV Import Tool Executable
# ----------------------------------------------------------------------------
add_executable(mg_import_csv mg_import_csv.cpp)
target_link_libraries(mg_import_csv mg-storage-v2)
target_link_libraries(mg_import_csv mg-storage-v2 mg-dbms)
# Strip the executable in release build.
if(lower_build_type STREQUAL "release")

View File

@ -25,4 +25,3 @@ target_link_libraries(mg-communication Boost::headers Threads::Threads mg-utils
find_package(OpenSSL REQUIRED)
target_link_libraries(mg-communication ${OPENSSL_LIBRARIES})
target_include_directories(mg-communication SYSTEM PUBLIC ${OPENSSL_INCLUDE_DIR})
target_precompile_headers(mg-communication INTERFACE http/server.hpp <boost/beast/websocket.hpp> bolt/v1/session.hpp)

View File

@ -209,7 +209,11 @@ 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");
#if MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
// Increment number of queries in the metrics
IncrementQueryMetrics(session);
@ -276,7 +280,11 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
return HandleFailure(session, e);
}
#if MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
// Increment number of queries in the metrics
IncrementQueryMetrics(session);

View File

@ -1,3 +1,3 @@
add_library(mg-dbms STATIC database.cpp)
add_library(mg-dbms STATIC database.cpp replication_handler.cpp inmemory/replication_handlers.cpp)
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query)

View File

@ -10,6 +10,8 @@
// licenses/APL.txt.
#include "dbms/database.hpp"
#include "dbms/inmemory/storage_helper.hpp"
#include "dbms/replication_handler.hpp"
#include "flags/storage_mode.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
@ -19,14 +21,15 @@ template struct memgraph::utils::Gatekeeper<memgraph::dbms::Database>;
namespace memgraph::dbms {
Database::Database(const storage::Config &config)
Database::Database(storage::Config config, const replication::ReplicationState &repl_state)
: trigger_store_(config.durability.storage_directory / "triggers"),
streams_{config.durability.storage_directory / "streams"} {
streams_{config.durability.storage_directory / "streams"},
repl_state_(&repl_state) {
if (config.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
utils::DirExists(config.disk.main_storage_directory)) {
storage_ = std::make_unique<storage::DiskStorage>(config);
storage_ = std::make_unique<storage::DiskStorage>(std::move(config));
} else {
storage_ = std::make_unique<storage::InMemoryStorage>(config, config.storage_mode);
storage_ = dbms::CreateInMemoryStorage(std::move(config), repl_state);
}
}

View File

@ -46,7 +46,7 @@ class Database {
*
* @param config storage configuration
*/
explicit Database(const storage::Config &config);
explicit Database(storage::Config config, const replication::ReplicationState &repl_state);
/**
* @brief Returns the raw storage pointer.
@ -56,6 +56,7 @@ class Database {
* @return storage::Storage*
*/
storage::Storage *storage() { return storage_.get(); }
storage::Storage const *storage() const { return storage_.get(); }
/**
* @brief Storage's Accessor
@ -65,12 +66,12 @@ class Database {
*/
std::unique_ptr<storage::Storage::Accessor> Access(
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
return storage_->Access(override_isolation_level);
return storage_->Access(override_isolation_level, repl_state_->IsMain());
}
std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
return storage_->UniqueAccess(override_isolation_level);
return storage_->UniqueAccess(override_isolation_level, repl_state_->IsMain());
}
/**
@ -157,6 +158,8 @@ class Database {
// TODO: Move to a better place
utils::SkipList<query::PlanCacheEntry> plan_cache_; //!< Plan cache associated with the storage
const replication::ReplicationState *repl_state_;
};
} // namespace memgraph::dbms

View File

@ -51,7 +51,8 @@ class DatabaseHandler : public Handler<Database> {
* @param config Storage configuration
* @return HandlerT::NewResult
*/
HandlerT::NewResult New(std::string_view name, storage::Config config) {
HandlerT::NewResult New(std::string_view name, storage::Config config,
const replication::ReplicationState &repl_state) {
// Control that no one is using the same data directory
if (std::any_of(begin(), end(), [&](auto &elem) {
auto db_acc = elem.second.access();
@ -62,7 +63,7 @@ class DatabaseHandler : public Handler<Database> {
return NewError::EXISTS;
}
config.name = name; // Set storage id via config
return HandlerT::New(std::piecewise_construct, name, config);
return HandlerT::New(std::piecewise_construct, name, config, repl_state);
}
/**
@ -93,5 +94,4 @@ class DatabaseHandler : public Handler<Database> {
};
} // namespace memgraph::dbms
#endif

View File

@ -26,7 +26,9 @@
#include "auth/auth.hpp"
#include "constants.hpp"
#include "dbms/database.hpp"
#ifdef MG_ENTERPRISE
#include "dbms/database_handler.hpp"
#endif
#include "global.hpp"
#include "query/config.hpp"
#include "query/interpreter_context.hpp"
@ -81,32 +83,35 @@ static inline nlohmann::json ToJson(const Statistics &stats) {
return res;
}
#ifdef MG_ENTERPRISE
using DeleteResult = utils::BasicResult<DeleteError>;
/**
* @brief Multi-database session contexts handler.
*/
class DbmsHandler {
public:
using LockT = utils::RWLock;
#ifdef MG_ENTERPRISE
using NewResultT = utils::BasicResult<NewError, DatabaseAccess>;
using DeleteResult = utils::BasicResult<DeleteError>;
/**
* @brief Initialize the handler.
*
* @param configs storage and interpreter configurations
* @param configs storage configuration
* @param auth pointer to the global authenticator
* @param recovery_on_startup restore databases (and its content) and authentication data
* @param delete_on_drop when dropping delete any associated directories on disk
*/
DbmsHandler(storage::Config config, auto *auth, bool recovery_on_startup, bool delete_on_drop)
: lock_{utils::RWLock::Priority::READ}, default_config_{std::move(config)}, delete_on_drop_(delete_on_drop) {
DbmsHandler(storage::Config config, const replication::ReplicationState &repl_state, auto *auth,
bool recovery_on_startup, bool delete_on_drop)
: lock_{utils::RWLock::Priority::READ},
default_config_{std::move(config)},
repl_state_(repl_state),
delete_on_drop_(delete_on_drop) {
// TODO: Decouple storage config from dbms config
// TODO: Save individual db configs inside the kvstore and restore from there
storage::UpdatePaths(*default_config_, default_config_->durability.storage_directory / "databases");
const auto &db_dir = default_config_->durability.storage_directory;
storage::UpdatePaths(default_config_, default_config_.durability.storage_directory / "databases");
const auto &db_dir = default_config_.durability.storage_directory;
const auto durability_dir = db_dir / ".durability";
utils::EnsureDirOrDie(db_dir);
utils::EnsureDirOrDie(durability_dir);
@ -114,7 +119,6 @@ class DbmsHandler {
// Generate the default database
MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB.");
// Recover previous databases
if (recovery_on_startup) {
for (const auto &[name, _] : *durability_) {
@ -132,7 +136,21 @@ class DbmsHandler {
}
}
}
#else
/**
* @brief Initialize the handler. A single database is supported in community edition.
*
* @param configs storage configuration
*/
DbmsHandler(storage::Config config, const replication::ReplicationState &repl_state)
: db_gatekeeper_{[&] {
config.name = kDefaultDB;
return std::move(config);
}(),
repl_state} {}
#endif
#ifdef MG_ENTERPRISE
/**
* @brief Create a new Database associated with the "name" database
*
@ -151,11 +169,24 @@ class DbmsHandler {
* @return DatabaseAccess
* @throw UnknownDatabaseException if database not found
*/
DatabaseAccess Get(std::string_view name) {
DatabaseAccess Get(std::string_view name = kDefaultDB) {
std::shared_lock<LockT> rd(lock_);
return Get_(name);
}
#else
/**
* @brief Get the context associated with the default database
*
* @return DatabaseAccess
*/
DatabaseAccess Get() {
auto acc = db_gatekeeper_.access();
MG_ASSERT(acc, "Failed to get default database!");
return *acc;
}
#endif
#ifdef MG_ENTERPRISE
/**
* @brief Delete database.
*
@ -201,6 +232,7 @@ class DbmsHandler {
return {}; // Success
}
#endif
/**
* @brief Return all active databases.
@ -208,8 +240,12 @@ class DbmsHandler {
* @return std::vector<std::string>
*/
std::vector<std::string> All() const {
#ifdef MG_ENTERPRISE
std::shared_lock<LockT> rd(lock_);
return db_handler_.All();
#else
return {db_gatekeeper_.access()->get()->id()};
#endif
}
/**
@ -220,24 +256,30 @@ class DbmsHandler {
Statistics Stats() {
Statistics stats{};
// TODO: Handle overflow?
#ifdef MG_ENTERPRISE
std::shared_lock<LockT> rd(lock_);
for (auto &[_, db_gk] : db_handler_) {
#else
{
auto &db_gk = db_gatekeeper_;
#endif
auto db_acc_opt = db_gk.access();
if (!db_acc_opt) continue;
auto &db_acc = *db_acc_opt;
const auto &info = db_acc->GetInfo();
const auto &storage_info = info.storage_info;
stats.num_vertex += storage_info.vertex_count;
stats.num_edges += storage_info.edge_count;
stats.triggers += info.triggers;
stats.streams += info.streams;
++stats.num_databases;
stats.indices += storage_info.label_indices + storage_info.label_property_indices;
stats.constraints += storage_info.existence_constraints + storage_info.unique_constraints;
++stats.storage_modes[(int)storage_info.storage_mode];
++stats.isolation_levels[(int)storage_info.isolation_level];
stats.snapshot_enabled += storage_info.durability_snapshot_enabled;
stats.wal_enabled += storage_info.durability_wal_enabled;
if (db_acc_opt) {
auto &db_acc = *db_acc_opt;
const auto &info = db_acc->GetInfo();
const auto &storage_info = info.storage_info;
stats.num_vertex += storage_info.vertex_count;
stats.num_edges += storage_info.edge_count;
stats.triggers += info.triggers;
stats.streams += info.streams;
++stats.num_databases;
stats.indices += storage_info.label_indices + storage_info.label_property_indices;
stats.constraints += storage_info.existence_constraints + storage_info.unique_constraints;
++stats.storage_modes[(int)storage_info.storage_mode];
++stats.isolation_levels[(int)storage_info.isolation_level];
stats.snapshot_enabled += storage_info.durability_snapshot_enabled;
stats.wal_enabled += storage_info.durability_wal_enabled;
}
}
return stats;
}
@ -249,13 +291,19 @@ class DbmsHandler {
*/
std::vector<DatabaseInfo> Info() {
std::vector<DatabaseInfo> res;
res.reserve(std::distance(db_handler_.cbegin(), db_handler_.cend()));
#ifdef MG_ENTERPRISE
std::shared_lock<LockT> rd(lock_);
res.reserve(std::distance(db_handler_.cbegin(), db_handler_.cend()));
for (auto &[_, db_gk] : db_handler_) {
#else
{
auto &db_gk = db_gatekeeper_;
#endif
auto db_acc_opt = db_gk.access();
if (!db_acc_opt) continue;
auto &db_acc = *db_acc_opt;
res.push_back(db_acc->GetInfo());
if (db_acc_opt) {
auto &db_acc = *db_acc_opt;
res.push_back(db_acc->GetInfo());
}
}
return res;
}
@ -267,15 +315,21 @@ class DbmsHandler {
* @param ic global InterpreterContext
*/
void RestoreTriggers(query::InterpreterContext *ic) {
#ifdef MG_ENTERPRISE
std::lock_guard<LockT> wr(lock_);
for (auto &[_, db_gk] : db_handler_) {
#else
{
auto &db_gk = db_gatekeeper_;
#endif
auto db_acc_opt = db_gk.access();
if (!db_acc_opt) continue;
auto &db_acc = *db_acc_opt;
spdlog::debug("Restoring trigger for database \"{}\"", db_acc->id());
auto storage_accessor = db_acc->Access();
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
if (db_acc_opt) {
auto &db_acc = *db_acc_opt;
spdlog::debug("Restoring trigger for database \"{}\"", db_acc->id());
auto storage_accessor = db_acc->Access();
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
}
}
}
@ -286,17 +340,67 @@ class DbmsHandler {
* @param ic global InterpreterContext
*/
void RestoreStreams(query::InterpreterContext *ic) {
#ifdef MG_ENTERPRISE
std::lock_guard<LockT> wr(lock_);
for (auto &[_, db_gk] : db_handler_) {
#else
{
auto &db_gk = db_gatekeeper_;
#endif
auto db_acc = db_gk.access();
if (!db_acc) continue;
auto *db = db_acc->get();
spdlog::debug("Restoring streams for database \"{}\"", db->id());
db->streams()->RestoreStreams(*db_acc, ic);
if (db_acc) {
auto *db = db_acc->get();
spdlog::debug("Restoring streams for database \"{}\"", db->id());
db->streams()->RestoreStreams(*db_acc, ic);
}
}
}
/**
* @brief todo
*
* @param f
*/
void ForEach(auto f) {
#ifdef MG_ENTERPRISE
std::shared_lock<LockT> rd(lock_);
for (auto &[_, db_gk] : db_handler_) {
#else
{
auto &db_gk = db_gatekeeper_;
#endif
auto db_acc = db_gk.access();
if (db_acc) { // This isn't an error, just a defunct db
f(db_acc->get());
}
}
}
/**
* @brief todo
*
* @param f
*/
void ForOne(auto f) {
#ifdef MG_ENTERPRISE
std::shared_lock<LockT> rd(lock_);
for (auto &[_, db_gk] : db_handler_) {
auto db_acc = db_gk.access();
if (db_acc) { // This isn't an error, just a defunct db
if (f(db_acc->get())) break; // Run until the first successful one
}
}
#else
{
auto db_acc = db_gatekeeper_.access();
MG_ASSERT(db_acc, "Should always have the database");
f(db_acc->get());
}
#endif
}
private:
#ifdef MG_ENTERPRISE
/**
* @brief return the storage directory of the associated database
*
@ -328,13 +432,9 @@ class DbmsHandler {
* @return NewResultT context on success, error on failure
*/
NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) {
if (default_config_) {
auto config_copy = *default_config_;
storage::UpdatePaths(config_copy, default_config_->durability.storage_directory / storage_subdir);
return New_(name, config_copy);
}
spdlog::info("Trying to generate session context without any configurations.");
return NewError::NO_CONFIGS;
auto config_copy = default_config_;
storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / storage_subdir);
return New_(name, config_copy);
}
/**
@ -351,7 +451,7 @@ class DbmsHandler {
return NewError::DEFUNCT;
}
auto new_db = db_handler_.New(name, storage_config);
auto new_db = db_handler_.New(name, storage_config, repl_state_);
if (new_db.HasValue()) {
// Success
if (durability_) durability_->Put(name, "ok"); // TODO: Serialize the configuration?
@ -436,14 +536,16 @@ class DbmsHandler {
throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name);
}
// Should storage objects ever be deleted?
mutable LockT lock_; //!< protective lock
DatabaseHandler db_handler_; //!< multi-tenancy storage handler
std::optional<storage::Config> default_config_; //!< Storage configuration used when creating new databases
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
std::set<std::string> defunct_dbs_; //!< Databases that are in an unknown state due to various failures
bool delete_on_drop_; //!< Flag defining if dropping storage also deletes its directory
};
mutable LockT lock_; //!< protective lock
storage::Config default_config_; //!< Storage configuration used when creating new databases
const replication::ReplicationState &repl_state_; //!< Global replication state
DatabaseHandler db_handler_; //!< multi-tenancy storage handler
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
bool delete_on_drop_; //!< Flag defining if dropping storage also deletes its directory
std::set<std::string> defunct_dbs_; //!< Databases that are in an unknown state due to various failures
#else
mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper
#endif
};
} // namespace memgraph::dbms

View File

@ -19,6 +19,7 @@
namespace memgraph::dbms {
#ifdef MG_ENTERPRISE
enum class DeleteError : uint8_t {
DEFAULT_DB,
USING,
@ -34,11 +35,7 @@ enum class NewError : uint8_t {
GENERIC,
};
enum class SetForResult : uint8_t {
SUCCESS,
ALREADY_SET,
FAIL,
};
#endif
/**
* UnknownSession Exception

View File

@ -49,7 +49,7 @@ class Handler {
* @return NewResult
*/
template <typename... Args>
NewResult New(std::piecewise_construct_t /* marker */, std::string_view name, Args... args) {
NewResult New(std::piecewise_construct_t /* marker */, std::string_view name, Args &&...args) {
// Make sure the emplace will succeed, since we don't want to create temporary objects that could break something
if (!Has(name)) {
auto [itr, _] = items_.emplace(std::piecewise_construct, std::forward_as_tuple(name),

View File

@ -9,7 +9,11 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "storage/v2/inmemory/replication/replication_server.hpp"
#include "dbms/inmemory/replication_handlers.hpp"
#include "dbms/constants.hpp"
#include "dbms/dbms_handler.hpp"
#include "replication/replication_server.hpp"
#include "spdlog/spdlog.h"
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/snapshot.hpp"
#include "storage/v2/durability/version.hpp"
@ -17,9 +21,20 @@
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
namespace memgraph::storage {
using memgraph::storage::Delta;
using memgraph::storage::EdgeAccessor;
using memgraph::storage::EdgeRef;
using memgraph::storage::EdgeTypeId;
using memgraph::storage::LabelIndexStats;
using memgraph::storage::LabelPropertyIndexStats;
using memgraph::storage::PropertyId;
using memgraph::storage::UniqueConstraints;
using memgraph::storage::View;
using memgraph::storage::durability::WalDeltaData;
namespace memgraph::dbms {
namespace {
std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder *decoder) {
std::pair<uint64_t, WalDeltaData> ReadDelta(storage::durability::BaseDecoder *decoder) {
try {
auto timestamp = ReadWalDeltaHeader(decoder);
SPDLOG_INFO(" Timestamp {}", timestamp);
@ -27,78 +42,114 @@ std::pair<uint64_t, durability::WalDeltaData> ReadDelta(durability::BaseDecoder
return {timestamp, delta};
} catch (const slk::SlkReaderException &) {
throw utils::BasicException("Missing data!");
} catch (const durability::RecoveryFailure &) {
} catch (const storage::durability::RecoveryFailure &) {
throw utils::BasicException("Invalid data!");
}
};
std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, std::string_view db_name) {
try {
#ifdef MG_ENTERPRISE
auto acc = dbms_handler->Get(db_name);
#else
if (db_name != dbms::kDefaultDB) {
spdlog::warn("Trying to replicate a non-default database on a community replica.");
return std::nullopt;
}
auto acc = dbms_handler->Get();
#endif
if (!acc) {
spdlog::error("Failed to get access to ", db_name);
return std::nullopt;
}
auto *inmem_storage = dynamic_cast<storage::InMemoryStorage *>(acc.get()->storage());
if (!inmem_storage || inmem_storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
spdlog::error("Database \"{}\" is not IN_MEMORY_TRANSACTIONAL.", db_name);
return std::nullopt;
}
return std::optional{std::move(acc)};
} catch (const dbms::UnknownDatabaseException &e) {
spdlog::warn("No database \"{}\" on replica!", db_name);
return std::nullopt;
}
}
} // namespace
InMemoryReplicationServer::InMemoryReplicationServer(InMemoryStorage *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) {
void InMemoryReplicationHandlers::Register(dbms::DbmsHandler *dbms_handler, replication::ReplicationServer &server) {
server.rpc_server_.Register<storage::replication::HeartbeatRpc>([dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received HeartbeatRpc");
this->HeartbeatHandler(req_reader, res_builder);
InMemoryReplicationHandlers::HeartbeatHandler(dbms_handler, req_reader, res_builder);
});
rpc_server_.Register<replication::AppendDeltasRpc>([this](auto *req_reader, auto *res_builder) {
spdlog::debug("Received AppendDeltasRpc");
this->AppendDeltasHandler(req_reader, res_builder);
});
rpc_server_.Register<replication::SnapshotRpc>([this](auto *req_reader, auto *res_builder) {
server.rpc_server_.Register<storage::replication::AppendDeltasRpc>(
[dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received AppendDeltasRpc");
InMemoryReplicationHandlers::AppendDeltasHandler(dbms_handler, req_reader, res_builder);
});
server.rpc_server_.Register<storage::replication::SnapshotRpc>([dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received SnapshotRpc");
this->SnapshotHandler(req_reader, res_builder);
InMemoryReplicationHandlers::SnapshotHandler(dbms_handler, req_reader, res_builder);
});
rpc_server_.Register<replication::WalFilesRpc>([this](auto *req_reader, auto *res_builder) {
server.rpc_server_.Register<storage::replication::WalFilesRpc>([dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received WalFilesRpc");
this->WalFilesHandler(req_reader, res_builder);
InMemoryReplicationHandlers::WalFilesHandler(dbms_handler, req_reader, res_builder);
});
rpc_server_.Register<replication::CurrentWalRpc>([this](auto *req_reader, auto *res_builder) {
server.rpc_server_.Register<storage::replication::CurrentWalRpc>([dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received CurrentWalRpc");
this->CurrentWalHandler(req_reader, res_builder);
InMemoryReplicationHandlers::CurrentWalHandler(dbms_handler, req_reader, res_builder);
});
rpc_server_.Register<replication::TimestampRpc>([this](auto *req_reader, auto *res_builder) {
server.rpc_server_.Register<storage::replication::TimestampRpc>([dbms_handler](auto *req_reader, auto *res_builder) {
spdlog::debug("Received TimestampRpc");
this->TimestampHandler(req_reader, res_builder);
InMemoryReplicationHandlers::TimestampHandler(dbms_handler, req_reader, res_builder);
});
}
void InMemoryReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::HeartbeatReq req;
void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::HeartbeatReq req;
slk::Load(&req, req_reader);
replication::HeartbeatRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load(),
std::string{repl_epoch_->id()}};
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
auto const *storage = db_acc->get()->storage();
storage::replication::HeartbeatRes res{storage->id(), true,
storage->repl_storage_state_.last_commit_timestamp_.load(),
std::string{storage->repl_storage_state_.epoch_.id()}};
slk::Save(res, res_builder);
}
void InMemoryReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::AppendDeltasReq req;
void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::AppendDeltasReq req;
slk::Load(&req, req_reader);
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
replication::Decoder decoder(req_reader);
storage::replication::Decoder decoder(req_reader);
auto maybe_epoch_id = decoder.ReadString();
MG_ASSERT(maybe_epoch_id, "Invalid replication message");
auto &repl_storage_state = storage_->repl_storage_state_;
if (*maybe_epoch_id != repl_epoch_->id()) {
auto prev_epoch = repl_epoch_->SetEpoch(*maybe_epoch_id);
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
auto &repl_storage_state = storage->repl_storage_state_;
if (*maybe_epoch_id != storage->repl_storage_state_.epoch_.id()) {
auto prev_epoch = storage->repl_storage_state_.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 != repl_epoch_->id()) {
storage_->wal_file_->FinalizeWal();
storage_->wal_file_.reset();
storage_->wal_seq_num_ = req.seq_num;
if (storage->wal_file_) {
if (req.seq_num > storage->wal_file_->SequenceNumber() ||
*maybe_epoch_id != storage->repl_storage_state_.epoch_.id()) {
storage->wal_file_->FinalizeWal();
storage->wal_file_.reset();
storage->wal_seq_num_ = req.seq_num;
spdlog::trace("Finalized WAL file");
} else {
MG_ASSERT(storage_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file");
storage_->wal_seq_num_ = req.seq_num + 1;
MG_ASSERT(storage->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file");
storage->wal_seq_num_ = req.seq_num + 1;
}
} else {
storage_->wal_seq_num_ = req.seq_num;
storage->wal_seq_num_ = req.seq_num;
}
if (req.previous_commit_timestamp != repl_storage_state.last_commit_timestamp_.load()) {
@ -107,144 +158,161 @@ void InMemoryReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk
while (!transaction_complete) {
SPDLOG_INFO("Skipping delta");
const auto [timestamp, delta] = ReadDelta(&decoder);
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(
delta.type, durability::kVersion); // TODO: Check if we are always using the latest version when replicating
transaction_complete = storage::durability::IsWalDeltaDataTypeTransactionEnd(
delta.type,
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
}
replication::AppendDeltasRes res{false, repl_storage_state.last_commit_timestamp_.load()};
storage::replication::AppendDeltasRes res{storage->id(), false, repl_storage_state.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
return;
}
ReadAndApplyDelta(storage_, &decoder,
durability::kVersion); // TODO: Check if we are always using the latest version when replicating
ReadAndApplyDelta(
storage, &decoder,
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
replication::AppendDeltasRes res{true, repl_storage_state.last_commit_timestamp_.load()};
storage::replication::AppendDeltasRes res{storage->id(), 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!");
}
void InMemoryReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::SnapshotReq req;
void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::SnapshotReq req;
slk::Load(&req, req_reader);
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
replication::Decoder decoder(req_reader);
storage::replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(storage_->snapshot_directory_);
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
utils::EnsureDirOrDie(storage->snapshot_directory_);
const auto maybe_snapshot_path = decoder.ReadFile(storage_->snapshot_directory_);
const auto maybe_snapshot_path = decoder.ReadFile(storage->snapshot_directory_);
MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!");
spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path);
auto storage_guard = std::unique_lock{storage_->main_lock_};
auto storage_guard = std::unique_lock{storage->main_lock_};
spdlog::trace("Clearing database since recovering from snapshot.");
// Clear the database
storage_->vertices_.clear();
storage_->edges_.clear();
storage->vertices_.clear();
storage->edges_.clear();
storage_->constraints_.existence_constraints_ = std::make_unique<ExistenceConstraints>();
storage_->constraints_.unique_constraints_ = std::make_unique<InMemoryUniqueConstraints>();
storage_->indices_.label_index_ = std::make_unique<InMemoryLabelIndex>();
storage_->indices_.label_property_index_ = std::make_unique<InMemoryLabelPropertyIndex>();
storage->constraints_.existence_constraints_ = std::make_unique<storage::ExistenceConstraints>();
storage->constraints_.unique_constraints_ = std::make_unique<storage::InMemoryUniqueConstraints>();
storage->indices_.label_index_ = std::make_unique<storage::InMemoryLabelIndex>();
storage->indices_.label_property_index_ = std::make_unique<storage::InMemoryLabelPropertyIndex>();
try {
spdlog::debug("Loading snapshot");
auto recovered_snapshot = durability::LoadSnapshot(
*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, &storage_->repl_storage_state_.history,
storage_->name_id_mapper_.get(), &storage_->edge_count_, storage_->config_);
auto recovered_snapshot = storage::durability::LoadSnapshot(
*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);
repl_epoch_->SetEpoch(std::move(recovered_snapshot.snapshot_info.epoch_id));
storage->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid);
storage->repl_storage_state_.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;
storage_->timestamp_ = std::max(storage_->timestamp_, recovery_info.next_timestamp);
storage->vertex_id_ = recovery_info.next_vertex_id;
storage->edge_id_ = recovery_info.next_edge_id;
storage->timestamp_ = std::max(storage->timestamp_, recovery_info.next_timestamp);
spdlog::trace("Recovering indices and constraints from snapshot.");
durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage_->indices_,
&storage_->constraints_, &storage_->vertices_);
} catch (const durability::RecoveryFailure &e) {
storage::durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage->indices_,
&storage->constraints_, &storage->vertices_);
} catch (const storage::durability::RecoveryFailure &e) {
LOG_FATAL("Couldn't load the snapshot because of: {}", e.what());
}
storage_guard.unlock();
replication::SnapshotRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
storage::replication::SnapshotRes res{storage->id(), true,
storage->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
spdlog::trace("Deleting old snapshot files due to snapshot recovery.");
// Delete other durability files
auto snapshot_files = durability::GetSnapshotFiles(storage_->snapshot_directory_, storage_->uuid_);
auto snapshot_files = storage::durability::GetSnapshotFiles(storage->snapshot_directory_, storage->uuid_);
for (const auto &[path, uuid, _] : snapshot_files) {
if (path != *maybe_snapshot_path) {
spdlog::trace("Deleting snapshot file {}", path);
storage_->file_retainer_.DeleteFile(path);
storage->file_retainer_.DeleteFile(path);
}
}
spdlog::trace("Deleting old WAL files due to snapshot recovery.");
auto wal_files = durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_);
auto wal_files = storage::durability::GetWalFiles(storage->wal_directory_, storage->uuid_);
if (wal_files) {
for (const auto &wal_file : *wal_files) {
spdlog::trace("Deleting WAL file {}", wal_file.path);
storage_->file_retainer_.DeleteFile(wal_file.path);
storage->file_retainer_.DeleteFile(wal_file.path);
}
storage_->wal_file_.reset();
storage->wal_file_.reset();
}
spdlog::debug("Replication recovery from snapshot finished!");
}
void InMemoryReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::WalFilesReq req;
void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::WalFilesReq req;
slk::Load(&req, req_reader);
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
const auto wal_file_number = req.file_number;
spdlog::debug("Received WAL files: {}", wal_file_number);
replication::Decoder decoder(req_reader);
storage::replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(storage_->wal_directory_);
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
utils::EnsureDirOrDie(storage->wal_directory_);
for (auto i = 0; i < wal_file_number; ++i) {
LoadWal(storage_, *repl_epoch_, &decoder);
LoadWal(storage, &decoder);
}
replication::WalFilesRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
storage::replication::WalFilesRes res{storage->id(), 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!");
}
void InMemoryReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::CurrentWalReq req;
void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::CurrentWalReq req;
slk::Load(&req, req_reader);
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
replication::Decoder decoder(req_reader);
storage::replication::Decoder decoder(req_reader);
utils::EnsureDirOrDie(storage_->wal_directory_);
auto *storage = static_cast<storage::InMemoryStorage *>(db_acc->get()->storage());
utils::EnsureDirOrDie(storage->wal_directory_);
LoadWal(storage_, *repl_epoch_, &decoder);
LoadWal(storage, &decoder);
replication::CurrentWalRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
storage::replication::CurrentWalRes res{storage->id(), 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, memgraph::replication::ReplicationEpoch &epoch,
replication::Decoder *decoder) {
const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory;
void InMemoryReplicationHandlers::LoadWal(storage::InMemoryStorage *storage, storage::replication::Decoder *decoder) {
const auto temp_wal_directory =
std::filesystem::temp_directory_path() / "memgraph" / storage::durability::kWalDirectory;
utils::EnsureDir(temp_wal_directory);
auto maybe_wal_path = decoder->ReadFile(temp_wal_directory);
MG_ASSERT(maybe_wal_path, "Failed to load WAL!");
spdlog::trace("Received WAL saved to {}", *maybe_wal_path);
try {
auto wal_info = durability::ReadWalInfo(*maybe_wal_path);
auto wal_info = storage::durability::ReadWalInfo(*maybe_wal_path);
if (wal_info.seq_num == 0) {
storage->uuid_ = wal_info.uuid;
}
if (wal_info.epoch_id != epoch.id()) {
auto prev_epoch = epoch.SetEpoch(wal_info.epoch_id);
auto &replica_epoch = storage->repl_storage_state_.epoch_;
if (wal_info.epoch_id != replica_epoch.id()) {
auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id);
storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
}
@ -259,11 +327,12 @@ void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, memgraph::repl
storage->wal_seq_num_ = wal_info.seq_num;
}
spdlog::trace("Loading WAL deltas from {}", *maybe_wal_path);
durability::Decoder wal;
const auto version = wal.Initialize(*maybe_wal_path, durability::kWalMagic);
storage::durability::Decoder wal;
const auto version = wal.Initialize(*maybe_wal_path, storage::durability::kWalMagic);
spdlog::debug("WAL file {} loaded successfully", *maybe_wal_path);
if (!version) throw durability::RecoveryFailure("Couldn't read WAL magic and/or version!");
if (!durability::IsVersionSupported(*version)) throw durability::RecoveryFailure("Invalid WAL version!");
if (!version) throw storage::durability::RecoveryFailure("Couldn't read WAL magic and/or version!");
if (!storage::durability::IsVersionSupported(*version))
throw storage::durability::RecoveryFailure("Invalid WAL version!");
wal.SetPosition(wal_info.offset_deltas);
for (size_t i = 0; i < wal_info.num_deltas;) {
@ -271,38 +340,46 @@ void InMemoryReplicationServer::LoadWal(InMemoryStorage *storage, memgraph::repl
}
spdlog::debug("Replication from current WAL successful!");
} catch (const durability::RecoveryFailure &e) {
} catch (const storage::durability::RecoveryFailure &e) {
LOG_FATAL("Couldn't recover WAL deltas from {} because of: {}", *maybe_wal_path, e.what());
}
}
void InMemoryReplicationServer::TimestampHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::TimestampReq req;
void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader,
slk::Builder *res_builder) {
storage::replication::TimestampReq req;
slk::Load(&req, req_reader);
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
if (!db_acc) return;
replication::TimestampRes res{true, storage_->repl_storage_state_.last_commit_timestamp_.load()};
// TODO: this handler is agnostic of InMemory, move to be reused by on-disk
auto const *storage = db_acc->get()->storage();
storage::replication::TimestampRes res{storage->id(), true,
storage->repl_storage_state_.last_commit_timestamp_.load()};
slk::Save(res, res_builder);
}
uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder,
const uint64_t version) {
uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage *storage,
storage::durability::BaseDecoder *decoder,
const uint64_t version) {
auto edge_acc = storage->edges_.access();
auto vertex_acc = storage->vertices_.access();
constexpr bool kUniqueAccess = true;
constexpr bool kSharedAccess = false;
std::optional<std::pair<uint64_t, InMemoryStorage::ReplicationAccessor>> commit_timestamp_and_accessor;
std::optional<std::pair<uint64_t, storage::InMemoryStorage::ReplicationAccessor>> commit_timestamp_and_accessor;
auto get_transaction = [storage, &commit_timestamp_and_accessor](uint64_t commit_timestamp,
bool unique = !kUniqueAccess) {
bool unique = kSharedAccess) {
if (!commit_timestamp_and_accessor) {
std::unique_ptr<Storage::Accessor> acc = nullptr;
std::unique_ptr<storage::Storage::Accessor> acc = nullptr;
if (unique) {
acc = storage->UniqueAccess(std::nullopt);
acc = storage->UniqueAccess(std::nullopt, false /*not main*/);
} else {
acc = storage->Access(std::nullopt);
acc = storage->Access(std::nullopt, false /*not main*/);
}
auto inmem_acc = std::unique_ptr<InMemoryStorage::InMemoryAccessor>(
static_cast<InMemoryStorage::InMemoryAccessor *>(acc.release()));
auto inmem_acc = std::unique_ptr<storage::InMemoryStorage::InMemoryAccessor>(
static_cast<storage::InMemoryStorage::InMemoryAccessor *>(acc.release()));
commit_timestamp_and_accessor.emplace(commit_timestamp, std::move(*inmem_acc));
} else if (commit_timestamp_and_accessor->first != commit_timestamp) {
throw utils::BasicException("Received more than one transaction!");
@ -319,7 +396,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
max_commit_timestamp = timestamp;
}
transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type, version);
transaction_complete = storage::durability::IsWalDeltaDataTypeTransactionEnd(delta.type, version);
if (timestamp < storage->timestamp_) {
continue;
@ -327,13 +404,13 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
SPDLOG_INFO(" Delta {}", applied_deltas);
switch (delta.type) {
case durability::WalDeltaData::Type::VERTEX_CREATE: {
case WalDeltaData::Type::VERTEX_CREATE: {
spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint());
auto *transaction = get_transaction(timestamp);
transaction->CreateVertexEx(delta.vertex_create_delete.gid);
break;
}
case durability::WalDeltaData::Type::VERTEX_DELETE: {
case WalDeltaData::Type::VERTEX_DELETE: {
spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint());
auto *transaction = get_transaction(timestamp);
auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW);
@ -342,7 +419,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: {
case WalDeltaData::Type::VERTEX_ADD_LABEL: {
spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(),
delta.vertex_add_remove_label.label);
auto *transaction = get_transaction(timestamp);
@ -352,7 +429,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
case WalDeltaData::Type::VERTEX_REMOVE_LABEL: {
spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(),
delta.vertex_add_remove_label.label);
auto *transaction = get_transaction(timestamp);
@ -362,7 +439,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: {
case WalDeltaData::Type::VERTEX_SET_PROPERTY: {
spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
auto *transaction = get_transaction(timestamp);
@ -373,7 +450,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EDGE_CREATE: {
case WalDeltaData::Type::EDGE_CREATE: {
spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}",
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
@ -388,7 +465,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (edge.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EDGE_DELETE: {
case WalDeltaData::Type::EDGE_DELETE: {
spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}",
delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type,
delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint());
@ -406,7 +483,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: {
case WalDeltaData::Type::EDGE_SET_PROPERTY: {
spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(),
delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value);
if (!storage->config_.items.properties_on_edges)
@ -469,17 +546,18 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
break;
}
case durability::WalDeltaData::Type::TRANSACTION_END: {
case WalDeltaData::Type::TRANSACTION_END: {
spdlog::trace(" Transaction end");
if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp)
throw utils::BasicException("Invalid commit data!");
auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first);
auto ret =
commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first, false /* not main */);
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
commit_timestamp_and_accessor = std::nullopt;
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: {
case WalDeltaData::Type::LABEL_INDEX_CREATE: {
spdlog::trace(" Create label index on :{}", delta.operation_label.label);
// Need to send the timestamp
auto *transaction = get_transaction(timestamp, kUniqueAccess);
@ -487,14 +565,14 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_DROP: {
case WalDeltaData::Type::LABEL_INDEX_DROP: {
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
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: {
case 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);
@ -506,7 +584,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
transaction->SetIndexStats(label, stats);
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_STATS_CLEAR: {
case 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
@ -514,7 +592,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
transaction->DeleteLabelIndexStats(storage->NameToLabel(info.label));
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: {
spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
@ -525,7 +603,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: {
spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
@ -536,7 +614,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_SET: {
case 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
@ -550,7 +628,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
transaction->SetIndexStats(label, property, stats);
break;
}
case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_STATS_CLEAR: {
case 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
@ -558,7 +636,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
transaction->DeleteLabelPropertyIndexStats(storage->NameToLabel(info.label));
break;
}
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: {
spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
@ -568,7 +646,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
auto *transaction = get_transaction(timestamp, kUniqueAccess);
@ -579,7 +657,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: {
std::stringstream ss;
utils::PrintIterable(ss, delta.operation_label_properties.properties);
spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
@ -594,7 +672,7 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: {
std::stringstream ss;
utils::PrintIterable(ss, delta.operation_label_properties.properties);
spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str());
@ -621,4 +699,4 @@ uint64_t InMemoryReplicationServer::ReadAndApplyDelta(InMemoryStorage *storage,
return applied_deltas;
}
} // namespace memgraph::storage
} // namespace memgraph::dbms

View File

@ -0,0 +1,49 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "replication/replication_server.hpp"
#include "replication/state.hpp"
#include "storage/v2/replication/serialization.hpp"
namespace memgraph::storage {
class InMemoryStorage;
}
namespace memgraph::dbms {
class DbmsHandler;
class InMemoryReplicationHandlers {
public:
static void Register(dbms::DbmsHandler *dbms_handler, replication::ReplicationServer &server);
private:
// RPC handlers
static void HeartbeatHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void AppendDeltasHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void SnapshotHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void WalFilesHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void CurrentWalHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void TimestampHandler(dbms::DbmsHandler *dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
static void LoadWal(storage::InMemoryStorage *storage, storage::replication::Decoder *decoder);
static uint64_t ReadAndApplyDelta(storage::InMemoryStorage *storage, storage::durability::BaseDecoder *decoder,
uint64_t version);
};
} // namespace memgraph::dbms

View File

@ -0,0 +1,68 @@
// 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 <variant>
#include "dbms/constants.hpp"
#include "dbms/replication_handler.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/storage.hpp"
namespace memgraph::dbms {
#ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
constexpr bool allow_mt_repl = true;
#else
constexpr bool allow_mt_repl = false;
#endif
inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(
storage::Config config, const ::memgraph::replication::ReplicationState &repl_state) {
const auto wal_mode = config.durability.snapshot_wal_mode;
const auto name = config.name;
auto storage = std::make_unique<storage::InMemoryStorage>(std::move(config));
// Connect replication state and storage
storage->CreateSnapshotHandler(
[storage = storage.get(),
&repl_state](bool is_periodic) -> utils::BasicResult<storage::InMemoryStorage::CreateSnapshotError> {
if (repl_state.IsReplica()) {
return storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica;
}
return storage->CreateSnapshot(is_periodic);
});
if (allow_mt_repl || name == dbms::kDefaultDB) {
// Handle global replication state
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
// RECOVER REPLICA CONNECTIONS
memgraph::dbms::RestoreReplication(repl_state, *storage);
} else if (const ::memgraph::replication::RoleMainData *data =
std::get_if<::memgraph::replication::RoleMainData>(&repl_state.ReplicationData());
data && !data->registered_replicas_.empty()) {
spdlog::warn("Multi-tenant replication is currently not supported!");
}
if (wal_mode == storage::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 "
"without write-ahead logs this instance is not replicating any data.");
}
return std::move(storage);
}
} // namespace memgraph::dbms

View File

@ -0,0 +1,234 @@
// 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 "dbms/replication_handler.hpp"
#include "dbms/constants.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/inmemory/replication_handlers.hpp"
#include "dbms/inmemory/storage_helper.hpp"
#include "replication/state.hpp"
using memgraph::replication::ReplicationClientConfig;
using memgraph::replication::ReplicationState;
using memgraph::replication::RoleMainData;
using memgraph::replication::RoleReplicaData;
namespace memgraph::dbms {
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() {
auto const main_handler = [](RoleMainData const &) {
// If we are already MAIN, we don't want to change anything
return false;
};
auto const replica_handler = [this](RoleReplicaData const &) {
// STEP 1) bring down all REPLICA servers
dbms_handler_.ForEach([](Database *db) {
auto *storage = db->storage();
// Remember old epoch + storage timestamp association
storage->PrepareForNewEpoch();
});
// STEP 2) Change to MAIN
// TODO: restore replication servers if false?
if (!repl_state_.SetReplicationRoleMain()) {
// TODO: Handle recovery on failure???
return false;
}
// STEP 3) We are now MAIN, update storage local epoch
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
storage->repl_storage_state_.epoch_ = std::get<RoleMainData>(std::as_const(repl_state_).ReplicationData()).epoch_;
});
return true;
};
// TODO: under lock
return std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
}
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;
}
// Remove registered replicas
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
});
// Creates the server
repl_state_.SetReplicationRoleReplica(config);
// Start
const auto success =
std::visit(utils::Overloaded{[](auto) {
// ASSERT
return false;
},
[this](RoleReplicaData const &data) {
// Register handlers
InMemoryReplicationHandlers::Register(&dbms_handler_, *data.server);
if (!data.server->Start()) {
spdlog::error("Unable to start the replication server.");
return false;
}
return true;
}},
repl_state_.ReplicationData());
// TODO Handle error (restore to main?)
return success;
}
auto ReplicationHandler::RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
-> memgraph::utils::BasicResult<RegisterReplicaError> {
MG_ASSERT(repl_state_.IsMain(), "Only main instance can register a replica!");
auto res = repl_state_.RegisterReplica(config);
switch (res) {
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
MG_ASSERT(false, "Only main instance can register a replica!");
return {};
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
return memgraph::dbms::RegisterReplicaError::NAME_EXISTS;
case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS:
return memgraph::dbms::RegisterReplicaError::END_POINT_EXISTS;
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
return memgraph::dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
case memgraph::replication::RegisterReplicaError::SUCCESS:
break;
}
bool all_clients_good = true;
if (!allow_mt_repl && dbms_handler_.All().size() > 1) {
spdlog::warn("Multi-tenant replication is currently not supported!");
}
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
if (!allow_mt_repl && storage->id() != kDefaultDB) {
return;
}
// TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
all_clients_good &=
storage->repl_storage_state_.replication_clients_.WithLock([storage, &config](auto &clients) -> bool {
auto client = storage->CreateReplicationClient(config, &storage->repl_storage_state_.epoch_);
client->Start();
if (client->State() == storage::replication::ReplicaState::INVALID) {
return false;
}
clients.push_back(std::move(client));
return true;
});
});
if (!all_clients_good) return RegisterReplicaError::CONNECTION_FAILED; // TODO: this happen to 1 or many...what to do
return {};
}
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterReplicaResult {
auto const replica_handler = [](RoleReplicaData const &) -> UnregisterReplicaResult {
return UnregisterReplicaResult::NOT_MAIN;
};
auto const main_handler = [this, name](RoleMainData &mainData) -> UnregisterReplicaResult {
if (!repl_state_.TryPersistUnregisterReplica(name)) {
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
}
auto const n_unregistered =
std::erase_if(mainData.registered_replicas_,
[&](ReplicationClientConfig const &registered_config) { return registered_config.name == name; });
dbms_handler_.ForEach([&](Database *db) {
db->storage()->repl_storage_state_.replication_clients_.WithLock(
[&](auto &clients) { std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); });
});
return n_unregistered != 0 ? UnregisterReplicaResult::SUCCESS : UnregisterReplicaResult::CAN_NOT_UNREGISTER;
};
return std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.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(); }
void RestoreReplication(const replication::ReplicationState &repl_state, storage::Storage &storage) {
spdlog::info("Restoring replication role.");
/// MAIN
auto const recover_main = [&storage](RoleMainData const &mainData) {
for (const auto &config : mainData.registered_replicas_) {
spdlog::info("Replica {} restoration started for {}.", config.name, storage.id());
auto register_replica = [&storage](const memgraph::replication::ReplicationClientConfig &config)
-> memgraph::utils::BasicResult<RegisterReplicaError> {
return storage.repl_storage_state_.replication_clients_.WithLock(
[&storage, &config](auto &clients) -> utils::BasicResult<RegisterReplicaError> {
auto client = storage.CreateReplicationClient(config, &storage.repl_storage_state_.epoch_);
client->Start();
if (client->State() == storage::replication::ReplicaState::INVALID) {
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.",
client->Name());
}
clients.push_back(std::move(client));
return {};
});
};
auto ret = register_replica(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 = [](RoleReplicaData const &data) { /*nothing to do*/ };
std::visit(
utils::Overloaded{
recover_main,
recover_replica,
},
std::as_const(repl_state).ReplicationData());
}
} // namespace memgraph::dbms

View File

@ -12,6 +12,7 @@
#pragma once
#include "replication/role.hpp"
#include "storage/v2/storage.hpp"
#include "utils/result.hpp"
// BEGIN fwd declares
@ -20,14 +21,10 @@ struct ReplicationState;
struct ReplicationServerConfig;
struct ReplicationClientConfig;
} // namespace memgraph::replication
namespace memgraph::storage {
class Storage;
}
// END fwd declares
namespace memgraph::storage {
namespace memgraph::dbms {
class DbmsHandler;
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,
@ -39,8 +36,8 @@ enum class UnregisterReplicaResult : uint8_t {
/// 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) {}
ReplicationHandler(memgraph::replication::ReplicationState &replState, DbmsHandler &dbms_handler)
: repl_state_(replState), dbms_handler_(dbms_handler) {}
// as REPLICA, become MAIN
bool SetReplicationRoleMain();
@ -49,16 +46,12 @@ struct ReplicationHandler {
bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config);
// as MAIN, define and connect to REPLICAs
auto RegisterReplica(RegistrationMode registration_mode, const memgraph::replication::ReplicationClientConfig &config)
auto RegisterReplica(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;
@ -66,6 +59,11 @@ struct ReplicationHandler {
private:
memgraph::replication::ReplicationState &repl_state_;
Storage &storage_;
DbmsHandler &dbms_handler_;
};
} // namespace memgraph::storage
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
/// TODO: extend to do multiple storages
void RestoreReplication(const replication::ReplicationState &repl_state, storage::Storage &storage);
} // namespace memgraph::dbms

View File

@ -8,4 +8,3 @@ target_sources(mg-glue PRIVATE auth.cpp
MonitoringServerT.cpp
run_id.cpp)
target_link_libraries(mg-glue mg-query mg-auth mg-audit mg-flags)
target_precompile_headers(mg-glue INTERFACE auth_checker.hpp auth_handler.hpp)

View File

@ -11,6 +11,8 @@
#pragma once
#include <string>
namespace memgraph::glue {
extern const std::string run_id_;
} // namespace memgraph::glue

View File

@ -14,6 +14,7 @@
#include "communication/websocket/auth.hpp"
#include "communication/websocket/server.hpp"
#include "dbms/constants.hpp"
#include "dbms/inmemory/replication_handlers.hpp"
#include "flags/all.hpp"
#include "flags/run_time_configurable.hpp"
#include "glue/MonitoringServerT.hpp"
@ -43,6 +44,7 @@
#include "query/auth_query_handler.hpp"
#include "query/interpreter_context.hpp"
namespace {
constexpr const char *kMgUser = "MEMGRAPH_USER";
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
constexpr const char *kMgPassfile = "MEMGRAPH_PASSFILE";
@ -107,6 +109,7 @@ void InitSignalHandlers(const std::function<void()> &shutdown_fun) {
block_shutdown_signals),
"Unable to register SIGINT handler!");
}
} // namespace
int main(int argc, char **argv) {
memgraph::memory::SetHooks();
@ -352,22 +355,35 @@ int main(int argc, char **argv) {
std::unique_ptr<memgraph::query::AuthChecker> auth_checker;
auth_glue(&auth_, auth_handler, auth_checker);
memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(db_config));
memgraph::dbms::DbmsHandler dbms_handler(db_config, repl_state
#ifdef MG_ENTERPRISE
memgraph::dbms::DbmsHandler new_handler(db_config, &auth_, FLAGS_data_recovery_on_startup,
FLAGS_storage_delete_on_drop);
auto db_acc = new_handler.Get(memgraph::dbms::kDefaultDB);
memgraph::query::InterpreterContext interpreter_context_(interp_config, &new_handler, auth_handler.get(),
auth_checker.get());
#else
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gatekeeper{db_config};
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, &db_gatekeeper, auth_handler.get(),
auth_checker.get());
,
&auth_, FLAGS_data_recovery_on_startup, FLAGS_storage_delete_on_drop
#endif
);
auto db_acc = dbms_handler.Get();
memgraph::query::InterpreterContext interpreter_context_(interp_config, &dbms_handler, &repl_state,
auth_handler.get(), auth_checker.get());
MG_ASSERT(db_acc, "Failed to access the main database");
// TODO: Move it somewhere better
// Startup replication state (if recovered at startup)
MG_ASSERT(std::visit(memgraph::utils::Overloaded{[](memgraph::replication::RoleMainData const &) { return true; },
[&](memgraph::replication::RoleReplicaData const &data) {
// Register handlers
memgraph::dbms::InMemoryReplicationHandlers::Register(
&dbms_handler, *data.server);
if (!data.server->Start()) {
spdlog::error("Unable to start the replication server.");
return false;
}
return true;
}},
repl_state.ReplicationData()),
"Replica recovery failure!");
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(memgraph::flags::ParseQueryModulesDirectory(),
FLAGS_data_directory);
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
@ -388,8 +404,8 @@ int main(int argc, char **argv) {
}
#ifdef MG_ENTERPRISE
new_handler.RestoreTriggers(&interpreter_context_);
new_handler.RestoreStreams(&interpreter_context_);
dbms_handler.RestoreTriggers(&interpreter_context_);
dbms_handler.RestoreStreams(&interpreter_context_);
#else
{
// Triggers can execute query procedures, so we need to reload the modules first and then
@ -432,11 +448,10 @@ int main(int argc, char **argv) {
if (FLAGS_telemetry_enabled) {
telemetry.emplace(telemetry_server, data_directory / "telemetry", memgraph::glue::run_id_, machine_id,
service_name == "BoltS", FLAGS_data_directory, std::chrono::minutes(10));
telemetry->AddStorageCollector(dbms_handler, auth_);
#ifdef MG_ENTERPRISE
telemetry->AddStorageCollector(new_handler, auth_);
telemetry->AddDatabaseCollector(new_handler);
telemetry->AddDatabaseCollector(dbms_handler);
#else
telemetry->AddStorageCollector(db_gatekeeper, auth_);
telemetry->AddDatabaseCollector();
#endif
telemetry->AddClientCollector();
@ -496,17 +511,16 @@ int main(int argc, char **argv) {
if (!FLAGS_init_data_file.empty()) {
spdlog::info("Running init data file.");
auto db_acc = dbms_handler.Get();
MG_ASSERT(db_acc, "Failed to gain access to the main database");
#ifdef MG_ENTERPRISE
auto db_acc = new_handler.Get(memgraph::dbms::kDefaultDB);
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
}
#else
auto db_acc_2 = db_gatekeeper.access();
MG_ASSERT(db_acc_2, "Failed to gain access to the main database");
InitFromCypherlFile(interpreter_context_, *db_acc_2, FLAGS_init_data_file);
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
#endif
}

View File

@ -19,7 +19,9 @@
#include <regex>
#include <unordered_map>
#include "dbms/inmemory/storage_helper.hpp"
#include "helpers.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/inmemory/storage.hpp"
@ -702,14 +704,16 @@ int main(int argc, char *argv[]) {
}
std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map;
auto store = std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{
memgraph::storage::Config config{
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
.durability = {.storage_directory = FLAGS_data_directory,
.recover_on_startup = false,
.snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED,
.snapshot_on_exit = true},
});
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
auto store = memgraph::dbms::CreateInMemoryStorage(config, repl_state);
memgraph::utils::Timer load_timer;

View File

@ -37,6 +37,7 @@
#include "dbms/database.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/global.hpp"
#include "dbms/inmemory/storage_helper.hpp"
#include "flags/run_time_configurable.hpp"
#include "glue/communication.hpp"
#include "license/license.hpp"
@ -100,10 +101,10 @@
#include "utils/variant_helpers.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/replication_handler.hpp"
#include "query/auth_query_handler.hpp"
#include "query/interpreter_context.hpp"
#include "replication/state.hpp"
#include "storage/v2/replication/replication_handler.hpp"
namespace memgraph::metrics {
extern Event ReadQuery;
@ -270,7 +271,8 @@ inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode
class ReplQueryHandler final : public query::ReplicationQueryHandler {
public:
explicit ReplQueryHandler(storage::Storage *db) : db_(db), handler_{db_->repl_state_, *db_} {}
explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler, memgraph::replication::ReplicationState *repl_state)
: dbms_handler_(dbms_handler), handler_{*repl_state, *dbms_handler} {}
/// @throw QueryRuntimeException if an error ocurred.
void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override {
@ -314,10 +316,6 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
throw QueryRuntimeException("Replica can't register another replica!");
}
if (name == memgraph::replication::kReservedReplicationRoleName) {
throw QueryRuntimeException("This replica name is reserved and can not be used as replica name!");
}
auto repl_mode = convertToReplicationMode(sync_mode);
auto maybe_ip_and_port =
@ -330,8 +328,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
.port = port,
.replica_check_frequency = replica_check_frequency,
.ssl = std::nullopt};
using storage::RegistrationMode;
auto ret = handler_.RegisterReplica(RegistrationMode::MUST_BE_INSTANTLY_VALID, config);
auto ret = handler_.RegisterReplica(config);
if (ret.HasError()) {
throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name));
}
@ -344,7 +341,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
void DropReplica(std::string_view replica_name) override {
auto const result = handler_.UnregisterReplica(replica_name);
switch (result) {
using enum memgraph::storage::UnregisterReplicaResult;
using enum memgraph::dbms::UnregisterReplicaResult;
case NOT_MAIN:
throw QueryRuntimeException("Replica can't unregister a replica!");
case COULD_NOT_BE_PERSISTED:
@ -358,13 +355,22 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
using Replica = ReplicationQueryHandler::Replica;
std::vector<Replica> ShowReplicas() const override {
auto const &replState = db_->repl_state_;
if (replState.IsReplica()) {
if (handler_.IsReplica()) {
// replica can't show registered replicas (it shouldn't have any)
throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!");
}
auto repl_infos = db_->ReplicasInfo();
// TODO: Combine results? Have a single place with clients???
// Also authentication checks (replica + database visibility)
std::vector<storage::ReplicaInfo> repl_infos{};
dbms_handler_->ForOne([&repl_infos](dbms::Database *db) -> bool {
auto infos = db->storage()->ReplicasInfo();
if (!infos.empty()) {
repl_infos = std::move(infos);
return true;
}
return false;
});
std::vector<Replica> replicas;
replicas.reserve(repl_infos.size());
@ -408,8 +414,8 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
}
private:
storage::Storage *db_;
storage::ReplicationHandler handler_;
dbms::DbmsHandler *dbms_handler_;
dbms::ReplicationHandler handler_;
};
/// returns false if the replication role can't be set
@ -418,7 +424,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_context, const Parameters &parameters) {
AuthQueryHandler *auth = interpreter_context->auth;
#ifdef MG_ENTERPRISE
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
#endif
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
@ -702,8 +708,10 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
}
} // namespace
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters, storage::Storage *storage,
const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters,
dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config,
std::vector<Notification> *notifications,
memgraph::replication::ReplicationState *repl_state) {
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
EvaluationContext evaluation_context;
@ -723,7 +731,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
notifications->emplace_back(SeverityLevel::WARNING, NotificationCode::REPLICA_PORT_WARNING,
"Be careful the replication port must be different from the memgraph port!");
}
callback.fn = [handler = ReplQueryHandler{storage}, role = repl_query->role_, maybe_port]() mutable {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, role = repl_query->role_,
maybe_port]() mutable {
handler.SetReplicationRole(role, maybe_port);
return std::vector<std::vector<TypedValue>>();
};
@ -735,7 +744,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
}
case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: {
callback.header = {"replication role"};
callback.fn = [handler = ReplQueryHandler{storage}] {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}] {
auto mode = handler.ShowReplicationRole();
switch (mode) {
case ReplicationQuery::ReplicationRole::MAIN: {
@ -754,7 +763,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
auto socket_address = repl_query->socket_address_->Accept(evaluator);
const auto replica_check_frequency = config.replication_replica_check_frequency;
callback.fn = [handler = ReplQueryHandler{storage}, name, socket_address, sync_mode,
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, name, socket_address, sync_mode,
replica_check_frequency]() mutable {
handler.RegisterReplica(name, std::string(socket_address.ValueString()), sync_mode, replica_check_frequency);
return std::vector<std::vector<TypedValue>>();
@ -765,7 +774,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
}
case ReplicationQuery::Action::DROP_REPLICA: {
const auto &name = repl_query->replica_name_;
callback.fn = [handler = ReplQueryHandler{storage}, name]() mutable {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, name]() mutable {
handler.DropReplica(name);
return std::vector<std::vector<TypedValue>>();
};
@ -777,7 +786,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
callback.header = {
"name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master",
"state"};
callback.fn = [handler = ReplQueryHandler{storage}, replica_nfields = callback.header.size()] {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, replica_nfields = callback.header.size()] {
const auto &replicas = handler.ShowReplicas();
auto typed_replicas = std::vector<std::vector<TypedValue>>{};
typed_replicas.reserve(replicas.size());
@ -1399,42 +1408,16 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
using RWType = plan::ReadWriteTypeChecker::RWType;
bool IsWriteQueryOnMainMemoryReplica(storage::Storage *storage,
const query::plan::ReadWriteTypeChecker::RWType query_type) {
if (auto storage_mode = storage->GetStorageMode(); storage_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL ||
storage_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
auto const &replState = storage->repl_state_;
return replState.IsReplica() && (query_type == RWType::W || query_type == RWType::RW);
}
return false;
}
bool IsReplica(storage::Storage *storage) {
if (auto storage_mode = storage->GetStorageMode(); storage_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL ||
storage_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
auto const &replState = storage->repl_state_;
return replState.IsReplica();
}
return false;
bool IsQueryWrite(const query::plan::ReadWriteTypeChecker::RWType query_type) {
return query_type == RWType::W || query_type == RWType::RW;
}
} // namespace
#ifdef MG_ENTERPRISE
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config, memgraph::dbms::DbmsHandler *handler,
query::AuthQueryHandler *ah, query::AuthChecker *ac)
: db_handler(handler), config(interpreter_config), auth(ah), auth_checker(ac) {}
#else
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config,
memgraph::utils::Gatekeeper<memgraph::dbms::Database> *db_gatekeeper,
query::AuthQueryHandler *ah, query::AuthChecker *ac)
: db_gatekeeper(db_gatekeeper), config(interpreter_config), auth(ah), auth_checker(ac) {}
#endif
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
#ifndef MG_ENTERPRISE
auto db_acc = interpreter_context_->db_gatekeeper->access();
auto db_acc = interpreter_context_->dbms_handler->Get();
MG_ASSERT(db_acc, "Database accessor needs to be valid");
current_db_.db_acc_ = std::move(db_acc);
#endif
@ -2284,21 +2267,16 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
}
PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::vector<Notification> *notifications, CurrentDB &current_db,
const InterpreterConfig &config) {
std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
const InterpreterConfig &config,
memgraph::replication::ReplicationState *repl_state) {
if (in_explicit_transaction) {
throw ReplicationModificationInMulticommandTxException();
}
MG_ASSERT(current_db.db_acc_, "Replication query expects a current DB");
storage::Storage *storage = current_db.db_acc_->get()->storage();
if (storage->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) {
throw ReplicationDisabledOnDiskStorage();
}
auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, storage, config, notifications);
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, config,
notifications, repl_state);
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
@ -2834,7 +2812,7 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli
std::move(parsed_query.required_privileges),
[storage](AnyStream * /*stream*/, std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> {
auto *mem_storage = static_cast<storage::InMemoryStorage *>(storage);
if (auto maybe_error = mem_storage->CreateSnapshot(storage->repl_state_, {}); maybe_error.HasError()) {
if (auto maybe_error = mem_storage->CreateSnapshot(false); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica:
throw utils::BasicException(
@ -3375,22 +3353,23 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &current_db,
InterpreterContext *interpreter_context,
std::optional<std::function<void(std::string_view)>> on_change_cb) {
std::optional<std::function<void(std::string_view)>> on_change_cb,
memgraph::replication::ReplicationState *repl_state) {
#ifdef MG_ENTERPRISE
if (!license::global_license_checker.IsEnterpriseValidFast()) {
throw QueryException("Trying to use enterprise feature without a valid license.");
}
// TODO: Remove once replicas support multi-tenant replication
if (!current_db.db_acc_) throw DatabaseContextRequiredException("Multi database queries require a defined database.");
if (IsReplica(current_db.db_acc_->get()->storage())) {
throw QueryException("Query forbidden on the replica!");
}
auto *query = utils::Downcast<MultiDatabaseQuery>(parsed_query.query);
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
switch (query->action_) {
case MultiDatabaseQuery::Action::CREATE:
if (repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{
{"STATUS"},
std::move(parsed_query.required_privileges),
@ -3433,6 +3412,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
if (current_db.in_explicit_db_) {
throw QueryException("Database switching is prohibited if session explicitly defines the used database");
}
if (!dbms::allow_mt_repl && repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{{"STATUS"},
std::move(parsed_query.required_privileges),
[db_name = query->db_name_, db_handler, &current_db, on_change_cb](
@ -3464,6 +3446,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
query->db_name_};
case MultiDatabaseQuery::Action::DROP:
if (repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{
{"STATUS"},
std::move(parsed_query.required_privileges),
@ -3471,11 +3456,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
std::vector<std::vector<TypedValue>> status;
memgraph::dbms::DeleteResult success{};
try {
// Remove database
success = db_handler->Delete(db_name);
auto success = db_handler->Delete(db_name);
if (!success.HasError()) {
// Remove from auth
auth->DeleteDatabase(db_name);
@ -3524,14 +3507,9 @@ PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB &cur
if (!license::global_license_checker.IsEnterpriseValidFast()) {
throw QueryException("Trying to use enterprise feature without a valid license.");
}
// TODO: Remove once replicas support multi-tenant replication
auto &replState = storage->repl_state_;
if (replState.IsReplica()) {
throw QueryException("SHOW DATABASES forbidden on the replica!");
}
// TODO pick directly from ic
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
AuthQueryHandler *auth = interpreter_context->auth;
Callback callback;
@ -3635,7 +3613,7 @@ void Interpreter::RollbackTransaction() {
void Interpreter::SetCurrentDB(std::string_view db_name, bool in_explicit_db) {
// Can throw
// do we lock here?
current_db_.SetCurrentDB(interpreter_context_->db_handler->Get(db_name), in_explicit_db);
current_db_.SetCurrentDB(interpreter_context_->dbms_handler->Get(db_name), in_explicit_db);
}
#endif
@ -3790,9 +3768,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
/// TODO: make replication DB agnostic
prepared_query =
PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
current_db_, interpreter_context_->config);
prepared_query = PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, *interpreter_context_->dbms_handler,
interpreter_context_->config, interpreter_context_->repl_state);
} else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
} else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) {
@ -3832,8 +3810,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
throw MultiDatabaseQueryInMulticommandTxException();
}
/// SYSTEM (Replication) + INTERPRETER
prepared_query =
PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_);
prepared_query = PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_,
interpreter_context_->repl_state);
} else if (utils::Downcast<ShowDatabasesQuery>(parsed_query.query)) {
/// SYSTEM PURE ("SHOW DATABASES")
/// INTERPRETER (TODO: "SHOW DATABASE")
@ -3855,7 +3833,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
UpdateTypeCount(rw_type);
if (IsWriteQueryOnMainMemoryReplica(current_db_.db_acc_->get()->storage(), rw_type)) {
if (interpreter_context_->repl_state->IsReplica() && IsQueryWrite(rw_type)) {
query_execution = nullptr;
throw QueryException("Write query forbidden on the replica!");
}
@ -4084,7 +4062,8 @@ void Interpreter::Commit() {
auto commit_confirmed_by_all_sync_repplicas = true;
auto maybe_commit_error = current_db_.db_transactional_accessor_->Commit();
auto maybe_commit_error =
current_db_.db_transactional_accessor_->Commit(std::nullopt, interpreter_context_->repl_state->IsMain());
if (maybe_commit_error.HasError()) {
const auto &error = maybe_commit_error.GetError();

View File

@ -13,6 +13,12 @@
#include "query/interpreter.hpp"
namespace memgraph::query {
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, query::AuthQueryHandler *ah,
query::AuthChecker *ac)
: dbms_handler(dbms_handler), config(interpreter_config), repl_state(rs), auth(ah), auth_checker(ac) {}
std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions(
std::vector<std::string> maybe_kill_transaction_ids, const std::optional<std::string> &username,
std::function<bool(std::string const &)> privilege_checker) {

View File

@ -21,17 +21,14 @@
#include "query/config.hpp"
#include "query/cypher_query_interpreter.hpp"
#include "query/typed_value.hpp"
#include "replication/state.hpp"
#include "utils/gatekeeper.hpp"
#include "utils/skip_list.hpp"
#include "utils/spin_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::dbms {
#ifdef MG_ENTERPRISE
class DbmsHandler;
#else
class Database;
#endif
} // namespace memgraph::dbms
namespace memgraph::query {
@ -48,20 +45,10 @@ class Interpreter;
*
*/
struct InterpreterContext {
#ifdef MG_ENTERPRISE
InterpreterContext(InterpreterConfig interpreter_config, memgraph::dbms::DbmsHandler *db_handler,
AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr);
#else
InterpreterContext(InterpreterConfig interpreter_config,
memgraph::utils::Gatekeeper<memgraph::dbms::Database> *db_gatekeeper,
query::AuthQueryHandler *ah = nullptr, query::AuthChecker *ac = nullptr);
#endif
InterpreterContext(InterpreterConfig interpreter_config, dbms::DbmsHandler *dbms_handler,
replication::ReplicationState *rs, AuthQueryHandler *ah = nullptr, AuthChecker *ac = nullptr);
#ifdef MG_ENTERPRISE
memgraph::dbms::DbmsHandler *db_handler;
#else
memgraph::utils::Gatekeeper<memgraph::dbms::Database> *db_gatekeeper;
#endif
memgraph::dbms::DbmsHandler *dbms_handler;
// Internal
const InterpreterConfig config;
@ -69,6 +56,7 @@ struct InterpreterContext {
memgraph::utils::SkipList<QueryCacheEntry> ast_cache;
// GLOBAL
memgraph::replication::ReplicationState *repl_state;
AuthQueryHandler *auth;
AuthChecker *auth_checker;

View File

@ -8,17 +8,19 @@ target_sources(mg-replication
include/replication/mode.hpp
include/replication/role.hpp
include/replication/status.hpp
include/replication/replication_server.hpp
PRIVATE
state.cpp
epoch.cpp
config.cpp
status.cpp
replication_server.cpp
)
target_include_directories(mg-replication PUBLIC include)
find_package(fmt REQUIRED)
target_link_libraries(mg-replication
PUBLIC mg::utils mg::kvstore lib::json
PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk
PRIVATE fmt::fmt
)

View File

@ -21,13 +21,12 @@ namespace memgraph::replication {
inline constexpr uint16_t kDefaultReplicationPort = 10000;
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
inline constexpr auto *kReservedReplicationRoleName{"__replication_role"};
struct ReplicationClientConfig {
std::string name;
ReplicationMode mode;
ReplicationMode mode{};
std::string ip_address;
uint16_t port;
uint16_t port{};
// The default delay between main checking/pinging replicas is 1s because
// that seems like a reasonable timeframe in which main should notice a
@ -42,18 +41,23 @@ struct ReplicationClientConfig {
};
std::optional<SSL> ssl;
friend bool operator==(ReplicationClientConfig const &, ReplicationClientConfig const &) = default;
};
struct ReplicationServerConfig {
std::string ip_address;
uint16_t port;
uint16_t port{};
struct SSL {
std::string key_file;
std::string cert_file;
std::string ca_file;
bool verify_peer;
bool verify_peer{};
friend bool operator==(SSL const &, SSL const &) = default;
};
std::optional<SSL> ssl;
friend bool operator==(ReplicationServerConfig const &, ReplicationServerConfig const &) = default;
};
} // namespace memgraph::replication

View File

@ -19,16 +19,21 @@ 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;
explicit ReplicationEpoch(std::string explicit_id) : id_(std::move(explicit_id)) {}
ReplicationEpoch(ReplicationEpoch const &) = default; // TODO: passkey idiom
ReplicationEpoch(ReplicationEpoch &&) = default;
ReplicationEpoch &operator=(ReplicationEpoch const &) = default; // TODO: passkey idiom
ReplicationEpoch &operator=(ReplicationEpoch &&) = default;
auto id() const -> std::string_view { return id_; }
auto NewEpoch() -> std::string { return std::exchange(id_, memgraph::utils::GenerateUUID()); }
// TODO: passkey idiom
friend struct ReplicationState;
auto SetEpoch(std::string new_epoch) -> std::string { return std::exchange(id_, std::move(new_epoch)); }
friend bool operator==(ReplicationEpoch const &, ReplicationEpoch const &) = default;
private:
// UUID to distinguish different main instance runs for replication process
// on SAME storage.

View File

@ -14,9 +14,32 @@
#include "replication/config.hpp"
#include "rpc/server.hpp"
#include "slk/streams.hpp"
#include "storage/v2/replication/global.hpp"
namespace memgraph::storage {
namespace memgraph::replication {
struct FrequentHeartbeatReq {
static const utils::TypeInfo kType; // TODO: make constexpr?
static const utils::TypeInfo &GetTypeInfo() { return kType; } // WHAT?
static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader);
static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder);
FrequentHeartbeatReq() {}
};
struct FrequentHeartbeatRes {
static const utils::TypeInfo kType;
static const utils::TypeInfo &GetTypeInfo() { return kType; }
static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
FrequentHeartbeatRes() {}
explicit FrequentHeartbeatRes(bool success) : success(success) {}
bool success;
};
// TODO: move to own header
using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>;
class ReplicationServer {
public:
@ -31,10 +54,10 @@ class ReplicationServer {
bool Start();
protected:
static void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
communication::ServerContext rpc_server_context_;
rpc::Server rpc_server_;
public:
rpc::Server rpc_server_; // TODO: Interface or something
};
} // namespace memgraph::storage
} // namespace memgraph::replication

View File

@ -21,48 +21,70 @@
#include "replication/epoch.hpp"
#include "replication/mode.hpp"
#include "replication/role.hpp"
#include "replication_server.hpp"
#include "status.hpp"
#include "utils/result.hpp"
namespace memgraph::replication {
enum class RolePersisted : uint8_t { UNKNOWN_OR_NO, YES };
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, COULD_NOT_BE_PERSISTED, NOT_MAIN, SUCCESS };
struct RoleMainData {
ReplicationEpoch epoch_;
std::vector<ReplicationClientConfig> registered_replicas_;
};
struct RoleReplicaData {
ReplicationServerConfig config;
std::unique_ptr<ReplicationServer> server;
};
struct ReplicationState {
ReplicationState(std::optional<std::filesystem::path> durability_dir);
explicit 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;
using ReplicationData_t = std::variant<RoleMainData, RoleReplicaData>;
using FetchReplicationResult_t = utils::BasicResult<FetchReplicationError, ReplicationData_t>;
auto FetchReplicationData() -> FetchReplicationResult_t;
auto GetRole() const -> ReplicationRole {
return std::holds_alternative<RoleReplicaData>(replication_data_) ? ReplicationRole::REPLICA
: ReplicationRole::MAIN;
}
bool IsMain() const { return GetRole() == ReplicationRole::MAIN; }
bool IsReplica() const { return GetRole() == ReplicationRole::REPLICA; }
bool ShouldPersist() const { return nullptr != durability_; }
bool TryPersistRoleMain();
bool TryPersistRoleMain(std::string new_epoch);
bool TryPersistRoleReplica(const ReplicationServerConfig &config);
bool TryPersistUnregisterReplica(std::string_view &name);
bool TryPersistUnregisterReplica(std::string_view name);
bool TryPersistRegisteredReplica(const ReplicationClientConfig &config);
// TODO: locked access
auto ReplicationData() -> ReplicationData_t & { return replication_data_; }
auto ReplicationData() const -> ReplicationData_t const & { return replication_data_; }
auto RegisterReplica(const ReplicationClientConfig &config) -> RegisterReplicaError;
bool SetReplicationRoleMain();
bool SetReplicationRoleReplica(const ReplicationServerConfig &config);
private:
ReplicationEpoch epoch_;
std::atomic<ReplicationRole> replication_role_{ReplicationRole::MAIN};
bool HandleVersionMigration(durability::ReplicationRoleEntry &data) const;
std::unique_ptr<kvstore::KVStore> durability_;
ReplicationData_t replication_data_;
std::atomic<RolePersisted> role_persisted = RolePersisted::UNKNOWN_OR_NO;
};

View File

@ -15,25 +15,56 @@
#include <cstdint>
#include <optional>
#include <string>
#include <variant>
#include "json/json.hpp"
#include "replication/config.hpp"
#include "replication/epoch.hpp"
#include "replication/role.hpp"
namespace memgraph::replication {
struct ReplicationStatus {
std::string name;
std::string ip_address;
uint16_t port;
ReplicationMode sync_mode;
std::chrono::seconds replica_check_frequency;
std::optional<ReplicationClientConfig::SSL> ssl;
std::optional<ReplicationRole> role;
namespace memgraph::replication::durability {
friend bool operator==(const ReplicationStatus &, const ReplicationStatus &) = default;
// Keys
constexpr auto *kReplicationRoleName{"__replication_role"};
constexpr auto *kReplicationReplicaPrefix{"__replication_replica:"}; // introduced in V2
enum class DurabilityVersion : uint8_t {
V1, // no distinct key for replicas
V2, // this version, epoch, replica prefix introduced
};
nlohmann::json ReplicationStatusToJSON(ReplicationStatus &&status);
std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data);
} // namespace memgraph::replication
// fragment of key: "__replication_role"
struct MainRole {
ReplicationEpoch epoch{};
friend bool operator==(MainRole const &, MainRole const &) = default;
};
// fragment of key: "__replication_role"
struct ReplicaRole {
ReplicationServerConfig config;
friend bool operator==(ReplicaRole const &, ReplicaRole const &) = default;
};
// from key: "__replication_role"
struct ReplicationRoleEntry {
DurabilityVersion version =
DurabilityVersion::V2; // if not latest then migration required for kReplicationReplicaPrefix
std::variant<MainRole, ReplicaRole> role;
friend bool operator==(ReplicationRoleEntry const &, ReplicationRoleEntry const &) = default;
};
// from key: "__replication_replica:"
struct ReplicationReplicaEntry {
ReplicationClientConfig config;
friend bool operator==(ReplicationReplicaEntry const &, ReplicationReplicaEntry const &) = default;
};
void to_json(nlohmann::json &j, const ReplicationRoleEntry &p);
void from_json(const nlohmann::json &j, ReplicationRoleEntry &p);
void to_json(nlohmann::json &j, const ReplicationReplicaEntry &p);
void from_json(const nlohmann::json &j, ReplicationReplicaEntry &p);
} // namespace memgraph::replication::durability

View File

@ -0,0 +1,96 @@
// 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/replication_server.hpp"
#include "rpc/messages.hpp"
#include "slk/serialization.hpp"
#include "slk/streams.hpp"
namespace memgraph::slk {
// Serialize code for FrequentHeartbeatRes
void Save(const memgraph::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.success, builder);
}
void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->success, reader);
}
// Serialize code for FrequentHeartbeatReq
void Save(const memgraph::replication::FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {}
void Load(memgraph::replication::FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {}
} // namespace memgraph::slk
namespace memgraph::replication {
namespace {
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{};
}
void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
FrequentHeartbeatReq req;
memgraph::slk::Load(&req, req_reader);
FrequentHeartbeatRes res{true};
memgraph::slk::Save(res, res_builder);
}
// NOTE: The replication server must have a single thread for processing
// because there is no need for more processing threads - each replica can
// have only a single main server. Also, the single-threaded guarantee
// simplifies the rest of the implementation.
constexpr auto kReplicationServerThreads = 1;
} // namespace
constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq",
nullptr};
constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes",
nullptr};
void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
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_,
kReplicationServerThreads} {
rpc_server_.Register<FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
spdlog::debug("Received FrequentHeartbeatRpc");
FrequentHeartbeatHandler(req_reader, res_builder);
});
}
ReplicationServer::~ReplicationServer() {
if (rpc_server_.IsRunning()) {
auto const &endpoint = rpc_server_.endpoint();
spdlog::trace("Closing replication server on {}:{}", endpoint.address, endpoint.port);
rpc_server_.Shutdown();
}
rpc_server_.AwaitShutdown();
}
bool ReplicationServer::Start() { return rpc_server_.Start(); }
} // namespace memgraph::replication

View File

@ -11,134 +11,265 @@
#include "replication/state.hpp"
#include "replication/status.hpp" //TODO: don't use status for durability
#include "replication/replication_server.hpp"
#include "replication/status.hpp"
#include "utils/file.hpp"
#include "utils/variant_helpers.hpp"
constexpr auto kReplicationDirectory = std::string_view{"replication"};
namespace memgraph::replication {
auto BuildReplicaKey(std::string_view name) -> std::string {
auto key = std::string{durability::kReplicationReplicaPrefix};
key.append(name);
return key;
}
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));
auto replicationData = 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.");
replication_data_ = RoleMainData{};
return;
}
case PARSE_ERROR: {
LOG_FATAL("Cannot parse previously saved configuration of replication role.");
return;
}
}
}
replication_data_ = std::move(replicationData).GetValue();
}
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;
auto data = durability::ReplicationRoleEntry{.role = durability::ReplicaRole{
.config = config,
}};
if (!durability_->Put(durability::kReplicationRoleName, nlohmann::json(data).dump())) {
spdlog::error("Error when saving REPLICA replication role in settings.");
return false;
}
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});
role_persisted = RolePersisted::YES;
if (durability_->Put(kReservedReplicationRoleName, data.dump())) {
// Cleanup remove registered replicas (assume successful delete)
// NOTE: we could do the alternative which would be on REPLICA -> MAIN we recover these registered replicas
auto b = durability_->begin(durability::kReplicationReplicaPrefix);
auto e = durability_->end(durability::kReplicationReplicaPrefix);
for (; b != e; ++b) {
durability_->Delete(b->first);
}
return true;
}
bool ReplicationState::TryPersistRoleMain(std::string new_epoch) {
if (!ShouldPersist()) return true;
auto data =
durability::ReplicationRoleEntry{.role = durability::MainRole{.epoch = ReplicationEpoch{std::move(new_epoch)}}};
if (durability_->Put(durability::kReplicationRoleName, nlohmann::json(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) {
bool ReplicationState::TryPersistUnregisterReplica(std::string_view name) {
if (!ShouldPersist()) return true;
if (durability_->Delete(name)) return true;
auto key = BuildReplicaKey(name);
if (durability_->Delete(key)) return true;
spdlog::error("Error when removing replica {} from settings.", name);
return false;
}
auto ReplicationState::FetchReplicationData() -> FetchReplicationResult {
// TODO: FetchEpochData (agnostic of FetchReplicationData, but should be done before)
auto ReplicationState::FetchReplicationData() -> FetchReplicationResult_t {
if (!ShouldPersist()) return FetchReplicationError::NOTHING_FETCHED;
const auto replication_data = durability_->Get(kReservedReplicationRoleName);
const auto replication_data = durability_->Get(durability::kReplicationRoleName);
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()) {
auto json = nlohmann::json::parse(*replication_data, nullptr, false);
if (json.is_discarded()) {
return FetchReplicationError::PARSE_ERROR;
}
try {
durability::ReplicationRoleEntry data = json.get<durability::ReplicationRoleEntry>();
// 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,
}};
if (!HandleVersionMigration(data)) {
return FetchReplicationError::PARSE_ERROR;
}
case ReplicationRole::MAIN: {
auto res = ReplicationState::ReplicationDataMain{};
res.reserve(durability_->Size() - 1);
for (const auto &[replica_name, replica_data] : *durability_) {
if (replica_name == kReservedReplicationRoleName) {
// To get here this must be the case
role_persisted = memgraph::replication::RolePersisted::YES;
return std::visit(
utils::Overloaded{
[&](durability::MainRole &&r) -> FetchReplicationResult_t {
auto res = RoleMainData{
.epoch_ = std::move(r.epoch),
};
auto b = durability_->begin(durability::kReplicationReplicaPrefix);
auto e = durability_->end(durability::kReplicationReplicaPrefix);
res.registered_replicas_.reserve(durability_->Size(durability::kReplicationReplicaPrefix));
for (; b != e; ++b) {
auto const &[replica_name, replica_data] = *b;
auto json = nlohmann::json::parse(replica_data, nullptr, false);
if (json.is_discarded()) return FetchReplicationError::PARSE_ERROR;
try {
durability::ReplicationReplicaEntry data = json.get<durability::ReplicationReplicaEntry>();
auto key_name = std::string_view{replica_name}.substr(strlen(durability::kReplicationReplicaPrefix));
if (key_name != data.config.name) {
return FetchReplicationError::PARSE_ERROR;
}
res.registered_replicas_.emplace_back(std::move(data.config));
} catch (...) {
return FetchReplicationError::PARSE_ERROR;
}
}
return {std::move(res)};
},
[&](durability::ReplicaRole &&r) -> FetchReplicationResult_t {
return {RoleReplicaData{r.config, std::make_unique<ReplicationServer>(r.config)}};
},
},
std::move(data.role));
} catch (...) {
return FetchReplicationError::PARSE_ERROR;
}
}
bool ReplicationState::HandleVersionMigration(durability::ReplicationRoleEntry &data) const {
switch (data.version) {
case durability::DurabilityVersion::V1: {
// For each replica config, change key to use the prefix
std::map<std::string, std::string> to_put;
std::vector<std::string> to_delete;
for (auto [old_key, old_data] : *durability_) {
// skip reserved keys
if (old_key == durability::kReplicationRoleName) {
continue;
}
const auto maybe_replica_status = JSONToReplicationStatus(nlohmann::json::parse(replica_data));
if (!maybe_replica_status.has_value()) {
return FetchReplicationError::PARSE_ERROR;
}
// Turn old data to new data
auto old_json = nlohmann::json::parse(old_data, nullptr, false);
if (old_json.is_discarded()) return false; // Can not read old_data as json
try {
durability::ReplicationReplicaEntry new_data = old_json.get<durability::ReplicationReplicaEntry>();
auto replica_status = *maybe_replica_status;
if (replica_status.name != replica_name) {
return FetchReplicationError::PARSE_ERROR;
// Migrate to using new key
to_put.emplace(BuildReplicaKey(old_key), nlohmann::json(new_data).dump());
} catch (...) {
return false; // Can not parse as ReplicationReplicaEntry
}
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,
});
to_delete.push_back(std::move(old_key));
}
return {std::move(res)};
// Set version
data.version = durability::DurabilityVersion::V2;
// Re-serialise (to include version + epoch)
to_put.emplace(durability::kReplicationRoleName, nlohmann::json(data).dump());
if (!durability_->PutAndDeleteMultiple(to_put, to_delete)) return false; // some reason couldn't persist
[[fallthrough]];
}
case durability::DurabilityVersion::V2: {
// do nothing - add code if V3 ever happens
break;
}
}
return true;
}
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 epoch_str = std::string(std::get<RoleMainData>(replication_data_).epoch_.id());
if (!TryPersistRoleMain(std::move(epoch_str))) 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;
auto data = durability::ReplicationReplicaEntry{.config = config};
auto key = BuildReplicaKey(config.name);
if (durability_->Put(key, nlohmann::json(data).dump())) return true;
spdlog::error("Error when saving replica {} in settings.", config.name);
return false;
}
bool ReplicationState::SetReplicationRoleMain() {
auto new_epoch = utils::GenerateUUID();
if (!TryPersistRoleMain(new_epoch)) {
return false;
}
replication_data_ = RoleMainData{.epoch_ = ReplicationEpoch{new_epoch}};
return true;
}
bool ReplicationState::SetReplicationRoleReplica(const ReplicationServerConfig &config) {
if (!TryPersistRoleReplica(config)) {
return false;
}
replication_data_ = RoleReplicaData{config, std::make_unique<ReplicationServer>(config)};
return true;
}
auto ReplicationState::RegisterReplica(const ReplicationClientConfig &config) -> RegisterReplicaError {
auto const replica_handler = [](RoleReplicaData const &) -> RegisterReplicaError {
return RegisterReplicaError::NOT_MAIN;
};
auto const main_handler = [this, &config](RoleMainData &mainData) -> RegisterReplicaError {
// name check
auto name_check = [&config](auto const &replicas) {
auto name_matches = [&name = config.name](ReplicationClientConfig const &registered_config) {
return registered_config.name == name;
};
return std::any_of(replicas.begin(), replicas.end(), name_matches);
};
if (name_check(mainData.registered_replicas_)) {
return RegisterReplicaError::NAME_EXISTS;
}
// endpoint check
auto endpoint_check = [&](auto const &replicas) {
auto endpoint_matches = [&config](ReplicationClientConfig const &registered_config) {
return registered_config.ip_address == config.ip_address && registered_config.port == config.port;
};
return std::any_of(replicas.begin(), replicas.end(), endpoint_matches);
};
if (endpoint_check(mainData.registered_replicas_)) {
return RegisterReplicaError::END_POINT_EXISTS;
}
// Durability
if (!TryPersistRegisteredReplica(config)) {
return RegisterReplicaError::COULD_NOT_BE_PERSISTED;
}
// set
mainData.registered_replicas_.emplace_back(config);
return RegisterReplicaError::SUCCESS;
};
return std::visit(utils::Overloaded{main_handler, replica_handler}, replication_data_);
}
} // namespace memgraph::replication

View File

@ -12,6 +12,9 @@
#include "fmt/format.h"
#include "utils/logging.hpp"
#include "utils/variant_helpers.hpp"
namespace memgraph::replication::durability {
constexpr auto *kReplicaName = "replica_name";
constexpr auto *kIpAddress = "replica_ip_address";
@ -21,71 +24,87 @@ 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";
constexpr auto *kEpoch = "epoch";
constexpr auto *kVersion = "durability_version";
namespace memgraph::replication {
nlohmann::json ReplicationStatusToJSON(ReplicationStatus &&status) {
auto data = nlohmann::json::object();
data[kReplicaName] = std::move(status.name);
data[kIpAddress] = std::move(status.ip_address);
data[kPort] = status.port;
data[kSyncMode] = status.sync_mode;
data[kCheckFrequency] = status.replica_check_frequency.count();
if (status.ssl.has_value()) {
data[kSSLKeyFile] = std::move(status.ssl->key_file);
data[kSSLCertFile] = std::move(status.ssl->cert_file);
} else {
data[kSSLKeyFile] = nullptr;
data[kSSLCertFile] = nullptr;
}
if (status.role.has_value()) {
data[kReplicationRole] = *status.role;
}
return data;
}
std::optional<ReplicationStatus> JSONToReplicationStatus(nlohmann::json &&data) {
ReplicationStatus replica_status;
const auto get_failed_message = [](const std::string_view message, const std::string_view nested_message) {
return fmt::format("Failed to deserialize replica's configuration: {} : {}", message, nested_message);
void to_json(nlohmann::json &j, const ReplicationRoleEntry &p) {
auto processMAIN = [&](MainRole const &main) {
j = nlohmann::json{{kVersion, p.version}, {kReplicationRole, ReplicationRole::MAIN}, {kEpoch, main.epoch.id()}};
};
try {
data.at(kReplicaName).get_to(replica_status.name);
data.at(kIpAddress).get_to(replica_status.ip_address);
data.at(kPort).get_to(replica_status.port);
data.at(kSyncMode).get_to(replica_status.sync_mode);
replica_status.replica_check_frequency = std::chrono::seconds(data.at(kCheckFrequency));
const auto &key_file = data.at(kSSLKeyFile);
const auto &cert_file = data.at(kSSLCertFile);
MG_ASSERT(key_file.is_null() == cert_file.is_null());
if (!key_file.is_null()) {
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 = ReplicationRole::MAIN;
data.at(kReplicationRole).get_to(replica_status.role.value());
}
} catch (const nlohmann::json::type_error &exception) {
spdlog::error(get_failed_message("Invalid type conversion", exception.what()));
return std::nullopt;
} catch (const nlohmann::json::out_of_range &exception) {
spdlog::error(get_failed_message("Non existing field", exception.what()));
return std::nullopt;
}
return replica_status;
auto processREPLICA = [&](ReplicaRole const &replica) {
j = nlohmann::json{
{kVersion, p.version},
{kReplicationRole, ReplicationRole::REPLICA},
{kIpAddress, replica.config.ip_address},
{kPort, replica.config.port}
// TODO: SSL
};
};
std::visit(utils::Overloaded{processMAIN, processREPLICA}, p.role);
}
} // namespace memgraph::replication
void from_json(const nlohmann::json &j, ReplicationRoleEntry &p) {
// This value did not exist in V1, hence default DurabilityVersion::V1
DurabilityVersion version = j.value(kVersion, DurabilityVersion::V1);
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
ReplicationRole role;
j.at(kReplicationRole).get_to(role);
switch (role) {
case ReplicationRole::MAIN: {
auto json_epoch = j.value(kEpoch, std::string{});
auto epoch = ReplicationEpoch{};
if (!json_epoch.empty()) epoch.SetEpoch(json_epoch);
p = ReplicationRoleEntry{.version = version, .role = MainRole{.epoch = std::move(epoch)}};
break;
}
case ReplicationRole::REPLICA: {
std::string ip_address;
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
uint16_t port;
j.at(kIpAddress).get_to(ip_address);
j.at(kPort).get_to(port);
auto config = ReplicationServerConfig{.ip_address = std::move(ip_address), .port = port};
p = ReplicationRoleEntry{.version = version, .role = ReplicaRole{.config = std::move(config)}};
break;
}
}
}
void to_json(nlohmann::json &j, const ReplicationReplicaEntry &p) {
auto common = nlohmann::json{{kReplicaName, p.config.name},
{kIpAddress, p.config.ip_address},
{kPort, p.config.port},
{kSyncMode, p.config.mode},
{kCheckFrequency, p.config.replica_check_frequency.count()}};
if (p.config.ssl.has_value()) {
common[kSSLKeyFile] = p.config.ssl->key_file;
common[kSSLCertFile] = p.config.ssl->cert_file;
} else {
common[kSSLKeyFile] = nullptr;
common[kSSLCertFile] = nullptr;
}
j = std::move(common);
}
void from_json(const nlohmann::json &j, ReplicationReplicaEntry &p) {
const auto &key_file = j.at(kSSLKeyFile);
const auto &cert_file = j.at(kSSLCertFile);
MG_ASSERT(key_file.is_null() == cert_file.is_null());
auto seconds = j.at(kCheckFrequency).get<std::chrono::seconds::rep>();
auto config = ReplicationClientConfig{
.name = j.at(kReplicaName).get<std::string>(),
.mode = j.at(kSyncMode).get<ReplicationMode>(),
.ip_address = j.at(kIpAddress).get<std::string>(),
.port = j.at(kPort).get<uint16_t>(),
.replica_check_frequency = std::chrono::seconds{seconds},
};
if (!key_file.is_null()) {
config.ssl = ReplicationClientConfig::SSL{};
key_file.get_to(config.ssl->key_file);
cert_file.get_to(config.ssl->cert_file);
}
p = ReplicationReplicaEntry{.config = std::move(config)};
}
} // namespace memgraph::replication::durability

View File

@ -7,5 +7,6 @@ find_package(fmt REQUIRED)
find_package(gflags REQUIRED)
add_library(mg-rpc STATIC ${rpc_src_files})
add_library(mg::rpc ALIAS mg-rpc)
target_link_libraries(mg-rpc Threads::Threads mg-communication mg-utils mg-io fmt::fmt gflags)
target_link_libraries(mg-rpc mg-slk)

View File

@ -19,6 +19,7 @@
#include "io/network/endpoint.hpp"
#include "rpc/exceptions.hpp"
#include "rpc/messages.hpp"
#include "rpc/version.hpp"
#include "slk/serialization.hpp"
#include "slk/streams.hpp"
#include "utils/logging.hpp"
@ -43,7 +44,7 @@ class Client {
: self_(self),
guard_(std::move(guard)),
req_builder_([self](const uint8_t *data, size_t size, bool have_more) {
if (!self->client_->Write(data, size, have_more)) throw RpcFailedException(self->endpoint_);
if (!self->client_->Write(data, size, have_more)) throw GenericRpcFailedException();
}),
res_load_(res_load) {}
@ -69,11 +70,11 @@ class Client {
while (true) {
auto ret = slk::CheckStreamComplete(self_->client_->GetData(), self_->client_->GetDataSize());
if (ret.status == slk::StreamStatus::INVALID) {
throw RpcFailedException(self_->endpoint_);
throw GenericRpcFailedException();
} else if (ret.status == slk::StreamStatus::PARTIAL) {
if (!self_->client_->Read(ret.stream_size - self_->client_->GetDataSize(),
/* exactly_len = */ false)) {
throw RpcFailedException(self_->endpoint_);
throw GenericRpcFailedException();
}
} else {
response_data_size = ret.stream_size;
@ -88,11 +89,22 @@ class Client {
utils::TypeId res_id{utils::TypeId::UNKNOWN};
slk::Load(&res_id, &res_reader);
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
rpc::Version version;
slk::Load(&version, &res_reader);
if (version != rpc::current_version) {
// V1 we introduced versioning with, absolutely no backwards compatibility,
// because it's impossible to provide backwards compatibility with pre versioning.
// Future versions this may require mechanism for graceful version handling.
throw VersionMismatchRpcFailedException();
}
// Check the response ID.
if (res_id != res_type.id && res_id != utils::TypeId::UNKNOWN) {
spdlog::error("Message response was of unexpected type");
self_->client_ = std::nullopt;
throw RpcFailedException(self_->endpoint_);
throw GenericRpcFailedException();
}
SPDLOG_TRACE("[RpcClient] received {}", res_type.name);
@ -153,7 +165,7 @@ class Client {
if (!client_->Connect(endpoint_)) {
SPDLOG_ERROR("Couldn't connect to remote address {}", endpoint_);
client_ = std::nullopt;
throw RpcFailedException(endpoint_);
throw GenericRpcFailedException();
}
}
@ -162,6 +174,8 @@ class Client {
// Build and send the request.
slk::Save(req_type.id, handler.GetBuilder());
slk::Save(rpc::current_version, handler.GetBuilder());
TRequestResponse::Request::Save(request, handler.GetBuilder());
// Return the handler to the user.

View File

@ -19,19 +19,30 @@ namespace memgraph::rpc {
/// `utils::BasicException` is used for transient errors that should be reported
/// to the user and `utils::StacktraceException` is used for fatal errors.
/// This exception always requires explicit handling.
class RpcFailedException final : public utils::BasicException {
class RpcFailedException : public utils::BasicException {
public:
RpcFailedException(const io::network::Endpoint &endpoint)
: utils::BasicException::BasicException(
"Couldn't communicate with the cluster! Please contact your "
"database administrator."),
endpoint_(endpoint) {}
/// Returns the endpoint associated with the error.
const io::network::Endpoint &endpoint() const { return endpoint_; }
SPECIALIZE_GET_EXCEPTION_NAME(RpcFailedException)
private:
io::network::Endpoint endpoint_;
RpcFailedException(std::string_view msg) : utils::BasicException(msg) {}
SPECIALIZE_GET_EXCEPTION_NAME(RpcFailedException);
};
class VersionMismatchRpcFailedException : public RpcFailedException {
public:
VersionMismatchRpcFailedException()
: RpcFailedException(
"Couldn't communicate with the cluster! There was a version mismatch. "
"Please contact your database administrator.") {}
SPECIALIZE_GET_EXCEPTION_NAME(VersionMismatchRpcFailedException);
};
class GenericRpcFailedException : public RpcFailedException {
public:
GenericRpcFailedException()
: RpcFailedException(
"Couldn't communicate with the cluster! Please contact your "
"database administrator.") {}
SPECIALIZE_GET_EXCEPTION_NAME(GenericRpcFailedException);
};
} // namespace memgraph::rpc

View File

@ -13,6 +13,7 @@
#include "rpc/messages.hpp"
#include "rpc/server.hpp"
#include "rpc/version.hpp"
#include "slk/serialization.hpp"
#include "slk/streams.hpp"
#include "utils/on_scope_exit.hpp"
@ -44,6 +45,16 @@ void Session::Execute() {
// Load the request ID.
utils::TypeId req_id{utils::TypeId::UNKNOWN};
slk::Load(&req_id, &req_reader);
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
rpc::Version version;
slk::Load(&version, &req_reader);
if (version != rpc::current_version) {
// V1 we introduced versioning with, absolutely no backwards compatibility,
// because it's impossible to provide backwards compatibility with pre versioning.
// Future versions this may require mechanism for graceful version handling.
throw SessionException("Session trying to execute a RPC call of an incorrect version!");
}
// Access to `callbacks_` and `extended_callbacks_` is done here without
// acquiring the `mutex_` because we don't allow RPC registration after the
@ -62,10 +73,12 @@ void Session::Execute() {
}
SPDLOG_TRACE("[RpcServer] received {}", extended_it->second.req_type.name);
slk::Save(extended_it->second.res_type.id, &res_builder);
slk::Save(rpc::current_version, &res_builder);
extended_it->second.callback(endpoint_, &req_reader, &res_builder);
} else {
SPDLOG_TRACE("[RpcServer] received {}", it->second.req_type.name);
slk::Save(it->second.res_type.id, &res_builder);
slk::Save(rpc::current_version, &res_builder);
it->second.callback(&req_reader, &res_builder);
}

27
src/rpc/version.hpp Normal file
View File

@ -0,0 +1,27 @@
// 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::rpc {
using Version = uint64_t;
// versioning of RPC was/will be introduced in 2.13
// We start the versioning with a strange number, to radically reduce the
// probability of accidental match/conformance with pre 2.13 versions
constexpr auto v1 = Version{2023'10'30'0'2'13};
constexpr auto current_version = v1;
} // namespace memgraph::rpc

View File

@ -4,5 +4,6 @@ set(slk_src_files
find_package(gflags REQUIRED)
add_library(mg-slk STATIC ${slk_src_files})
add_library(mg::slk ALIAS mg-slk)
target_link_libraries(mg-slk gflags)
target_link_libraries(mg-slk mg-utils)

View File

@ -35,24 +35,10 @@ add_library(mg-storage-v2 STATIC
disk/unique_constraints.cpp
storage_mode.cpp
replication/replication_client.cpp
replication/replication_server.cpp
replication/serialization.cpp
replication/slk.cpp
replication/rpc.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 mg::replication Threads::Threads mg-utils gflags absl::flat_hash_map mg-rpc mg-slk mg-events)
# Until we get LTO there is an advantage to do some unity builds
set_target_properties(mg-storage-v2
PROPERTIES
UNITY_BUILD ON
UNITY_BUILD_MODE GROUP
)
set_source_files_properties(
vertex_info_cache.cpp vertex_accessor.cpp
PROPERTIES UNITY_GROUP "ensure inline of vertex_info_cache"
)

View File

@ -17,6 +17,7 @@
#include "storage/v2/isolation_level.hpp"
#include "storage/v2/storage_mode.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
namespace memgraph::storage {
@ -34,10 +35,12 @@ struct Config {
Type type{Type::PERIODIC};
std::chrono::milliseconds interval{std::chrono::milliseconds(1000)};
friend bool operator==(const Gc &lrh, const Gc &rhs) = default;
} gc;
struct Items {
bool properties_on_edges{true};
friend bool operator==(const Items &lrh, const Items &rhs) = default;
} items;
struct Durability {
@ -62,10 +65,12 @@ struct Config {
uint64_t recovery_thread_count{8};
bool allow_parallel_index_creation{false};
friend bool operator==(const Durability &lrh, const Durability &rhs) = default;
} durability;
struct Transaction {
IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION};
friend bool operator==(const Transaction &lrh, const Transaction &rhs) = default;
} transaction;
struct DiskConfig {
@ -77,13 +82,26 @@ struct Config {
std::filesystem::path id_name_mapper_directory{"storage/rocksdb_id_name_mapper"};
std::filesystem::path durability_directory{"storage/rocksdb_durability"};
std::filesystem::path wal_directory{"storage/rocksdb_wal"};
friend bool operator==(const DiskConfig &lrh, const DiskConfig &rhs) = default;
} disk;
std::string name;
bool force_on_disk{false};
StorageMode storage_mode{StorageMode::IN_MEMORY_TRANSACTIONAL};
friend bool operator==(const Config &lrh, const Config &rhs) = default;
};
inline auto ReplicationStateRootPath(memgraph::storage::Config const &config) -> std::optional<std::filesystem::path> {
if (!config.durability.restore_replication_state_on_startup) {
spdlog::warn(
"Replication configuration will NOT be stored. When the server restarts, replication state will be "
"forgotten.");
return std::nullopt;
}
return {config.durability.storage_directory};
}
static inline void UpdatePaths(Config &config, const std::filesystem::path &storage_dir) {
auto contained = [](const auto &path, const auto &base) -> std::optional<std::filesystem::path> {
auto rel = std::filesystem::relative(path, base);

View File

@ -19,6 +19,7 @@
#include "storage/v2/indices/label_index.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/rocksdb_serialization.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {
class DiskLabelIndex : public storage::LabelIndex {

View File

@ -13,6 +13,7 @@
#include "storage/v2/disk/rocksdb_storage.hpp"
#include "storage/v2/indices/label_property_index.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {

View File

@ -1551,7 +1551,7 @@ DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label,
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
const std::optional<uint64_t> desired_commit_timestamp, bool /*is_main*/) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@ -1957,7 +1957,7 @@ UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstrain
return UniqueConstraints::DeletionStatus::SUCCESS;
}
Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool /*is_main*/) {
/// We acquire the transaction engine lock here because we access (and
/// modify) the transaction engine variables (`transaction_id` and
/// `timestamp`) below.
@ -1982,7 +1982,8 @@ 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) {
std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level,
bool /*is_main*/) {
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.");
@ -1990,7 +1991,8 @@ std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLe
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) {
std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool /*is_main*/) {
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.");

View File

@ -142,8 +142,8 @@ class DiskStorage final : public Storage {
ConstraintsInfo ListAllConstraints() const override;
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) override;
utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {},
bool is_main = true) override;
void UpdateObjectsCountOnAbort();
@ -172,9 +172,13 @@ class DiskStorage final : public Storage {
const std::set<PropertyId> &properties) override;
};
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override;
using Storage::Access;
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level,
bool is_main) override;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) override;
using Storage::UniqueAccess;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) override;
/// Flushing methods
[[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushIndexCache(Transaction *transaction);
@ -277,7 +281,8 @@ class DiskStorage final : public Storage {
RocksDBStorage *GetRocksDBStorage() const { return kvstore_.get(); }
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
using Storage::CreateTransaction;
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override;
void SetEdgeImportMode(EdgeImportMode edge_import_status);
@ -304,22 +309,16 @@ class DiskStorage final : public Storage {
void FreeMemory(std::unique_lock<utils::ResourceLock> /*lock*/) override {}
void PrepareForNewEpoch(std::string /*prev_epoch*/) override {
throw utils::BasicException("Disk storage mode does not support replication.");
}
void PrepareForNewEpoch() override { throw utils::BasicException("Disk storage mode does not support replication."); }
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig & /*config*/)
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig & /*config*/,
const memgraph::replication::ReplicationEpoch * /*current_epoch*/)
-> std::unique_ptr<ReplicationClient> override {
throw utils::BasicException("Disk storage mode does not support replication.");
}
auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig & /*config*/)
-> std::unique_ptr<ReplicationServer> override {
throw utils::BasicException("Disk storage mode does not support replication.");
}
std::unique_ptr<RocksDBStorage> kvstore_;
DurableMetadata durable_metadata_;
EdgeImportMode edge_import_status_{EdgeImportMode::INACTIVE};

View File

@ -211,12 +211,10 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
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,
Indices *indices, Constraints *constraints, const Config &config,
uint64_t *wal_seq_num) {
ReplicationStorageState &repl_storage_state, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges, std::atomic<uint64_t> *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
const Config &config, uint64_t *wal_seq_num) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory,
wal_directory);
@ -226,6 +224,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
return std::nullopt;
}
auto *const epoch_history = &repl_storage_state.history;
utils::Timer timer;
auto snapshot_files = GetSnapshotFiles(snapshot_directory);
@ -264,7 +263,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.SetEpoch(std::move(recovered_snapshot->snapshot_info.epoch_id));
repl_storage_state.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
@ -309,7 +308,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.SetEpoch(std::move(wal_files.back().epoch_id));
repl_storage_state.epoch_.SetEpoch(std::move(wal_files.back().epoch_id));
}
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
@ -365,7 +364,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 != repl_storage_state.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
@ -373,7 +372,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.SetEpoch(std::move(wal_file.epoch_id));
repl_storage_state.epoch_.SetEpoch(std::move(wal_file.epoch_id));
}
try {
auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,

View File

@ -19,6 +19,7 @@
#include <variant>
#include "replication/epoch.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/durability/metadata.hpp"
@ -26,6 +27,7 @@
#include "storage/v2/edge.hpp"
#include "storage/v2/indices/indices.hpp"
#include "storage/v2/name_id_mapper.hpp"
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/skip_list.hpp"
@ -110,11 +112,9 @@ 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,
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,
Indices *indices, Constraints *constraints, const Config &config,
uint64_t *wal_seq_num);
ReplicationStorageState &repl_storage_state, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges, std::atomic<uint64_t> *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
const Config &config, uint64_t *wal_seq_num);
} // namespace memgraph::storage::durability

View File

@ -16,6 +16,7 @@
#include "storage/v2/indices/label_index_stats.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/rw_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {

View File

@ -15,6 +15,7 @@
#include "storage/v2/indices/label_property_index.hpp"
#include "storage/v2/indices/label_property_index_stats.hpp"
#include "utils/rw_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {

View File

@ -44,7 +44,7 @@ class CurrentWalHandler {
////// CurrentWalHandler //////
CurrentWalHandler::CurrentWalHandler(ReplicationClient *self)
: self_(self), stream_(self_->rpc_client_.Stream<replication::CurrentWalRpc>()) {}
: self_(self), stream_(self_->rpc_client_.Stream<replication::CurrentWalRpc>(self->GetStorageId())) {}
void CurrentWalHandler::AppendFilename(const std::string &filename) {
replication::Encoder encoder(stream_.GetBuilder());
@ -70,9 +70,10 @@ replication::CurrentWalRes CurrentWalHandler::Finalize() { return stream_.AwaitR
////// ReplicationClient Helpers //////
replication::WalFilesRes TransferWalFiles(rpc::Client &client, const std::vector<std::filesystem::path> &wal_files) {
replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &client,
const std::vector<std::filesystem::path> &wal_files) {
MG_ASSERT(!wal_files.empty(), "Wal files list is empty!");
auto stream = client.Stream<replication::WalFilesRpc>(wal_files.size());
auto stream = client.Stream<replication::WalFilesRpc>(std::move(db_name), wal_files.size());
replication::Encoder encoder(stream.GetBuilder());
for (const auto &wal : wal_files) {
spdlog::debug("Sending wal file: {}", wal);
@ -81,8 +82,8 @@ replication::WalFilesRes TransferWalFiles(rpc::Client &client, const std::vector
return stream.AwaitResponse();
}
replication::SnapshotRes TransferSnapshot(rpc::Client &client, const std::filesystem::path &path) {
auto stream = client.Stream<replication::SnapshotRpc>();
replication::SnapshotRes TransferSnapshot(std::string db_name, rpc::Client &client, const std::filesystem::path &path) {
auto stream = client.Stream<replication::SnapshotRpc>(std::move(db_name));
replication::Encoder encoder(stream.GetBuilder());
encoder.WriteFile(path);
return stream.AwaitResponse();
@ -115,19 +116,19 @@ void InMemoryReplicationClient::RecoverReplica(uint64_t replica_commit) {
const auto steps = GetRecoverySteps(replica_commit, &file_locker);
int i = 0;
for (const auto &recovery_step : steps) {
for (const InMemoryReplicationClient::RecoveryStep &recovery_step : steps) {
spdlog::trace("Recovering in step: {}", i++);
try {
std::visit(
[&, this]<typename T>(T &&arg) {
using StepType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<StepType, RecoverySnapshot>) {
if constexpr (std::is_same_v<StepType, RecoverySnapshot>) { // TODO: split into 3 overloads
spdlog::debug("Sending the latest snapshot file: {}", arg);
auto response = TransferSnapshot(rpc_client_, arg);
auto response = TransferSnapshot(storage->id(), rpc_client_, arg);
replica_commit = response.current_commit_timestamp;
} else if constexpr (std::is_same_v<StepType, RecoveryWals>) {
spdlog::debug("Sending the latest wal files");
auto response = TransferWalFiles(rpc_client_, arg);
auto response = TransferWalFiles(storage->id(), rpc_client_, arg);
replica_commit = response.current_commit_timestamp;
spdlog::debug("Wal files successfully transferred.");
} else if constexpr (std::is_same_v<StepType, RecoveryCurrentWal>) {

View File

@ -1,52 +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 "replication/epoch.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/replication/serialization.hpp"
namespace memgraph::storage {
class InMemoryStorage;
class InMemoryReplicationServer : public ReplicationServer {
public:
explicit InMemoryReplicationServer(InMemoryStorage *storage,
const memgraph::replication::ReplicationServerConfig &config,
memgraph::replication::ReplicationEpoch *repl_epoch);
private:
// RPC handlers
void HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder);
void TimestampHandler(slk::Reader *req_reader, slk::Builder *res_builder);
static void LoadWal(InMemoryStorage *storage, memgraph::replication::ReplicationEpoch &epoch,
replication::Decoder *decoder);
static uint64_t ReadAndApplyDelta(InMemoryStorage *storage, durability::BaseDecoder *decoder, uint64_t version);
InMemoryStorage *storage_;
memgraph::replication::ReplicationEpoch *repl_epoch_;
};
} // namespace memgraph::storage

View File

@ -16,10 +16,9 @@
#include "storage/v2/metadata_delta.hpp"
/// REPLICATION ///
#include "dbms/inmemory/replication_handlers.hpp"
#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 {
@ -60,12 +59,10 @@ 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 = 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_);
auto info =
durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, repl_storage_state_, &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;
@ -103,51 +100,14 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
"those files into a .backup directory inside the storage directory.");
}
}
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] {
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(
utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/durability"));
break;
case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
});
}
if (config_.gc.type == Config::Gc::Type::PERIODIC) {
gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->CollectGarbage<false>(); });
}
if (timestamp_ == kTimestampInitialId) {
commit_log_.emplace();
} else {
commit_log_.emplace(timestamp_);
}
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.");
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 && 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 "
"without write-ahead logs this instance is not replicating any data.");
}
}
InMemoryStorage::InMemoryStorage(Config config) : InMemoryStorage(config, StorageMode::IN_MEMORY_TRANSACTIONAL) {}
@ -167,29 +127,15 @@ InMemoryStorage::~InMemoryStorage() {
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Stop();
}
if (config_.durability.snapshot_on_exit) {
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"));
break;
case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/replication"));
break;
case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
if (config_.durability.snapshot_on_exit && this->create_snapshot_handler) {
create_snapshot_handler(false);
}
committed_transactions_.WithLock([](auto &transactions) { transactions.clear(); });
}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: Accessor(tag, storage, isolation_level, storage_mode), config_(storage->config_.items) {}
StorageMode storage_mode, bool is_main)
: Accessor(tag, storage, isolation_level, storage_mode, is_main), config_(storage->config_.items) {}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept
: Accessor(std::move(other)), config_(other.config_) {}
@ -711,7 +657,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
const std::optional<uint64_t> desired_commit_timestamp, bool is_main) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@ -719,7 +665,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
auto const &replState = mem_storage->repl_state_;
// TODO: duplicated transaction finalisation in md_deltas and deltas processing cases
if (!transaction_.md_deltas.empty()) {
// This is usually done by the MVCC, but it does not handle the metadata deltas
transaction_.EnsureCommitTimestampExists();
@ -739,14 +685,14 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
// 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()) {
if (is_main || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataDefinition(transaction_, *commit_timestamp_); // protected by engine_guard
// TODO: release lock, and update all deltas to have a local copy of the commit timestamp
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); // protected by engine_guard
// Replica can only update the last commit timestamp with
// the commits received from main.
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_); // protected by engine_guard
}
@ -820,7 +766,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
// 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()) {
if (is_main || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_); // protected by engine_guard
}
@ -831,7 +777,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
std::memory_order_release); // protected by engine_guard
// Replica can only update the last commit timestamp with
// the commits received from main.
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->repl_storage_state_.last_commit_timestamp_.store(
*commit_timestamp_); // protected by engine_guard
@ -1195,7 +1141,7 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(
mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_));
}
Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) {
// We acquire the transaction engine lock here because we access (and
// modify) the transaction engine variables (`transaction_id` and
// `timestamp`) below.
@ -1210,11 +1156,10 @@ 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.
auto const &replState = repl_state_;
if (replState.IsReplica()) {
start_timestamp = timestamp_;
} else {
if (is_main) {
start_timestamp = timestamp_++;
} else {
start_timestamp = timestamp_;
}
}
return {transaction_id, start_timestamp, isolation_level, storage_mode, false};
@ -1629,8 +1574,7 @@ void InMemoryStorage::FinalizeWalFile() {
}
bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
if (!InitializeWalFile(repl_storage_state_.epoch_)) {
return true;
}
// Traverse deltas and append them to the WAL file.
@ -1800,8 +1744,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
}
bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
if (!InitializeWalFile(repl_storage_state_.epoch_)) {
return true;
}
@ -1910,13 +1853,8 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera
return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
}
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(
memgraph::replication::ReplicationState const &replicationState, std::optional<bool> is_periodic) {
if (replicationState.IsReplica()) {
return CreateSnapshotError::DisabledForReplica;
}
auto const &epoch = replicationState.GetEpoch();
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(bool is_periodic) {
auto const &epoch = repl_storage_state_.epoch_;
auto snapshot_creator = [this, &epoch]() {
utils::Timer timer;
auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_);
@ -1943,7 +1881,7 @@ utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::Create
} else {
std::unique_lock main_guard{main_lock_};
if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL) {
if (is_periodic && *is_periodic) {
if (is_periodic) {
return CreateSnapshotError::DisabledForAnalyticsPeriodicCommit;
}
snapshot_creator();
@ -1976,13 +1914,13 @@ uint64_t InMemoryStorage::CommitTimestamp(const std::optional<uint64_t> desired_
return *desired_commit_timestamp;
}
void InMemoryStorage::PrepareForNewEpoch(std::string prev_epoch) {
void InMemoryStorage::PrepareForNewEpoch() {
std::unique_lock engine_guard{engine_lock_};
if (wal_file_) {
wal_file_->FinalizeWal();
wal_file_.reset();
}
repl_storage_state_.AddEpochToHistory(std::move(prev_epoch));
repl_storage_state_.TrackLatestHistory();
}
utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::IsPathLocked() {
@ -2010,26 +1948,49 @@ utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath()
return true;
}
auto InMemoryStorage::CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
auto InMemoryStorage::CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *current_epoch)
-> std::unique_ptr<ReplicationClient> {
auto &replState = this->repl_state_;
return std::make_unique<InMemoryReplicationClient>(this, config, &replState.GetEpoch());
return std::make_unique<InMemoryReplicationClient>(this, config, current_epoch);
}
std::unique_ptr<ReplicationServer> InMemoryStorage::CreateReplicationServer(
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,
bool is_main) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::shared_access, this,
override_isolation_level.value_or(isolation_level_),
storage_mode_, is_main});
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::unique_access, this,
override_isolation_level.value_or(isolation_level_),
storage_mode_, is_main});
}
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_});
void InMemoryStorage::CreateSnapshotHandler(
std::function<utils::BasicResult<InMemoryStorage::CreateSnapshotError>(bool)> cb) {
create_snapshot_handler = [cb](bool is_periodic) {
if (auto maybe_error = cb(is_periodic); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/durability"));
break;
case CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
};
// Run the snapshot thread (if enabled)
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval,
[this]() { this->create_snapshot_handler(true); });
}
}
IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const {
auto *in_memory = static_cast<InMemoryStorage *>(storage_);

View File

@ -31,6 +31,10 @@
#include "utils/resource_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::dbms {
class InMemoryReplicationHandlers;
}
namespace memgraph::storage {
// The storage is based on this paper:
@ -39,7 +43,7 @@ namespace memgraph::storage {
// only implement snapshot isolation for transactions.
class InMemoryStorage final : public Storage {
friend class InMemoryReplicationServer;
friend class memgraph::dbms::InMemoryReplicationHandlers;
friend class InMemoryReplicationClient;
public:
@ -66,7 +70,7 @@ class InMemoryStorage final : public Storage {
friend class InMemoryStorage;
explicit InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode);
StorageMode storage_mode, bool is_main = true);
public:
InMemoryAccessor(const InMemoryAccessor &) = delete;
@ -204,8 +208,8 @@ class InMemoryStorage final : public Storage {
/// case the transaction is automatically aborted.
/// @throw std::bad_alloc
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) override;
utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {},
bool is_main = true) override;
/// @throw std::bad_alloc
void Abort() override;
@ -311,9 +315,13 @@ class InMemoryStorage final : public Storage {
Transaction &GetTransaction() { return transaction_; }
};
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override;
using Storage::Access;
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level,
bool is_main) override;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) override;
using Storage::UniqueAccess;
std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) override;
void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) override;
@ -321,17 +329,17 @@ class InMemoryStorage final : public Storage {
utils::FileRetainer::FileLockerAccessor::ret_type LockPath();
utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath();
utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot(
memgraph::replication::ReplicationState const &replicationState, std::optional<bool> is_periodic);
utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot(bool is_periodic = false);
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) override;
void CreateSnapshotHandler(std::function<utils::BasicResult<InMemoryStorage::CreateSnapshotError>(bool)> cb);
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
using Storage::CreateTransaction;
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override;
auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *current_epoch)
-> std::unique_ptr<ReplicationClient> override;
auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> override;
private:
/// The force parameter determines the behaviour of the garbage collector.
/// If it's set to true, it will behave as a global operation, i.e. it can't
@ -377,7 +385,7 @@ class InMemoryStorage final : public Storage {
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
void PrepareForNewEpoch(std::string prev_epoch) override;
void PrepareForNewEpoch() override;
// Main object storage
utils::SkipList<storage::Vertex> vertices_;
@ -444,6 +452,9 @@ class InMemoryStorage final : public Storage {
// Flags to inform CollectGarbage that it needs to do the more expensive full scans
std::atomic<bool> gc_full_scan_vertices_delete_ = false;
std::atomic<bool> gc_full_scan_edges_delete_ = false;
// Moved the create snapshot to a user defined handler so we can remove the global replication state from the storage
std::function<void(bool)> create_snapshot_handler{};
};
} // namespace memgraph::storage

View File

@ -49,8 +49,8 @@ uint64_t ReplicationClient::LastCommitTimestamp() const {
void ReplicationClient::InitializeClient() {
uint64_t current_commit_timestamp{kTimestampInitialId};
auto stream{rpc_client_.Stream<replication::HeartbeatRpc>(storage_->repl_storage_state_.last_commit_timestamp_,
std::string{repl_epoch_->id()})};
auto stream{rpc_client_.Stream<replication::HeartbeatRpc>(
storage_->id(), storage_->repl_storage_state_.last_commit_timestamp_, std::string{repl_epoch_->id()})};
const auto replica = stream.AwaitResponse();
std::optional<uint64_t> branching_point;
@ -98,7 +98,7 @@ TimestampInfo ReplicationClient::GetTimestampInfo() {
info.current_number_of_timestamp_behind_master = 0;
try {
auto stream{rpc_client_.Stream<replication::TimestampRpc>()};
auto stream{rpc_client_.Stream<replication::TimestampRpc>(storage_->id())};
const auto response = stream.AwaitResponse();
const auto is_success = response.success;
if (!is_success) {
@ -135,8 +135,15 @@ void ReplicationClient::TryInitializeClientAsync() {
void ReplicationClient::TryInitializeClientSync() {
try {
InitializeClient();
} catch (const rpc::VersionMismatchRpcFailedException &) {
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
spdlog::error(
utils::MessageWithLink("Failed to connect to replica {} at the endpoint {}. Because the replica "
"deployed is not a compatible version.",
name_, rpc_client_.Endpoint(), "https://memgr.ph/replication"));
} catch (const rpc::RpcFailedException &) {
std::unique_lock client_guarde{client_lock_};
std::unique_lock client_guard{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
spdlog::error(utils::MessageWithLink("Failed to connect to replica {} at the endpoint {}.", name_,
rpc_client_.Endpoint(), "https://memgr.ph/replication"));
@ -222,7 +229,7 @@ bool ReplicationClient::FinalizeTransactionReplication() {
void ReplicationClient::FrequentCheck() {
const auto is_success = std::invoke([this]() {
try {
auto stream{rpc_client_.Stream<replication::FrequentHeartbeatRpc>()};
auto stream{rpc_client_.Stream<memgraph::replication::FrequentHeartbeatRpc>()};
const auto response = stream.AwaitResponse();
return response.success;
} catch (const rpc::RpcFailedException &) {
@ -280,7 +287,8 @@ void ReplicationClient::IfStreamingTransaction(const std::function<void(ReplicaS
ReplicaStream::ReplicaStream(ReplicationClient *self, const uint64_t previous_commit_timestamp,
const uint64_t current_seq_num)
: self_(self),
stream_(self_->rpc_client_.Stream<replication::AppendDeltasRpc>(previous_commit_timestamp, current_seq_num)) {
stream_(self_->rpc_client_.Stream<replication::AppendDeltasRpc>(self->GetStorageId(), previous_commit_timestamp,
current_seq_num)) {
replication::Encoder encoder{stream_.GetBuilder()};
encoder.WriteString(self->repl_epoch_->id());
@ -312,4 +320,5 @@ void ReplicaStream::AppendOperation(durability::StorageMetadataOperation operati
replication::AppendDeltasRes ReplicaStream::Finalize() { return stream_.AwaitResponse(); }
auto ReplicationClient::GetStorageId() const -> std::string { return storage_->id(); }
} // namespace memgraph::storage

View File

@ -29,6 +29,7 @@
#include <optional>
#include <set>
#include <string>
#include <variant>
namespace memgraph::storage {
@ -86,6 +87,8 @@ class ReplicationClient {
auto State() const -> replication::ReplicaState { return replica_state_.load(); }
auto GetTimestampInfo() -> TimestampInfo;
auto GetStorageId() const -> std::string;
void Start();
void StartTransactionReplication(const uint64_t current_wal_seq_num);
// Replication clients can be removed at any point

View File

@ -1,209 +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_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_.PrepareForNewEpoch(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);
};
io::network::Endpoint desired_endpoint;
if (io::network::Endpoint::GetIpFamily(config.ip_address) == io::network::Endpoint::IpFamily::NONE) {
desired_endpoint = io::network::Endpoint{io::network::Endpoint::needs_resolving, config.ip_address, config.port};
} else {
desired_endpoint = io::network::Endpoint{config.ip_address, config.port};
}
auto endpoint_check = [&](auto &clients) {
auto endpoint_matches = [&](const auto &client) { return client->Endpoint() == desired_endpoint; };
return std::any_of(clients.begin(), clients.end(), endpoint_matches);
};
auto task = [&](auto &clients) -> utils::BasicResult<RegisterReplicaError> {
if (name_check(clients)) {
return RegisterReplicaError::NAME_EXISTS;
}
if (endpoint_check(clients)) {
return RegisterReplicaError::END_POINT_EXISTS;
}
using enum RegistrationMode;
if (registration_mode != RESTORE && !repl_state_.TryPersistRegisteredReplica(config)) {
return RegisterReplicaError::COULD_NOT_BE_PERSISTED;
}
auto client = storage_.CreateReplicationClient(config);
client->Start();
if (client->State() == replication::ReplicaState::INVALID) {
if (registration_mode != RESTORE) {
return RegisterReplicaError::CONNECTION_FAILED;
}
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.", client->Name());
}
clients.push_back(std::move(client));
return {};
};
return storage_.repl_storage_state_.replication_clients_.WithLock(task);
}
bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) {
// We don't want to restart the server if we're already a REPLICA
if (repl_state_.IsReplica()) {
return false;
}
std::unique_ptr<ReplicationServer> replication_server = storage_.CreateReplicationServer(config);
bool res = replication_server->Start();
if (!res) {
spdlog::error("Unable to start the replication server.");
return false;
}
storage_.repl_storage_state_.replication_server_ = std::move(replication_server);
if (!repl_state_.TryPersistRoleReplica(config)) {
return false;
}
repl_state_.SetRole(memgraph::replication::ReplicationRole::REPLICA);
return true;
}
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterReplicaResult {
if (repl_state_.IsReplica()) {
return UnregisterReplicaResult::NOT_MAIN;
}
if (!repl_state_.TryPersistUnregisterReplica(name)) {
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
}
auto const n_unregistered = storage_.repl_storage_state_.replication_clients_.WithLock([&](auto &clients) {
return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; });
});
return (n_unregistered != 0) ? UnregisterReplicaResult::SUCCESS : UnregisterReplicaResult::CAN_NOT_UNREGISTER;
}
void ReplicationHandler::RestoreReplication() {
if (!repl_state_.ShouldPersist()) {
return;
}
spdlog::info("Restoring replication role.");
using memgraph::replication::ReplicationState;
auto replicationData = repl_state_.FetchReplicationData();
if (replicationData.HasError()) {
switch (replicationData.GetError()) {
using enum ReplicationState::FetchReplicationError;
case NOTHING_FETCHED: {
spdlog::debug("Cannot find data needed for restore replication role in persisted metadata.");
return;
}
case PARSE_ERROR: {
LOG_FATAL("Cannot parse previously saved configuration of replication role.");
return;
}
}
}
/// MAIN
auto const recover_main = [this](ReplicationState::ReplicationDataMain const &configs) {
storage_.repl_storage_state_.replication_server_.reset();
repl_state_.SetRole(memgraph::replication::ReplicationRole::MAIN);
for (const auto &config : configs) {
spdlog::info("Replica {} restored for {}.", config.name, storage_.id());
auto ret = RegisterReplica(RegistrationMode::RESTORE, config);
if (ret.HasError()) {
MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
LOG_FATAL("Failure when restoring replica {}: {}.", config.name, RegisterReplicaErrorToString(ret.GetError()));
}
spdlog::info("Replica {} restored for {}.", config.name, storage_.id());
}
spdlog::info("Replication role restored to MAIN.");
};
/// REPLICA
auto const recover_replica = [this](ReplicationState::ReplicationDataReplica const &config) {
auto replication_server = storage_.CreateReplicationServer(config);
if (!replication_server->Start()) {
LOG_FATAL("Unable to start the replication server.");
}
storage_.repl_storage_state_.replication_server_ = std::move(replication_server);
repl_state_.SetRole(memgraph::replication::ReplicationRole::REPLICA);
spdlog::info("Replication role restored to REPLICA.");
};
std::visit(
utils::Overloaded{
recover_main,
recover_replica,
},
*replicationData);
}
auto ReplicationHandler::GetRole() const -> memgraph::replication::ReplicationRole { return repl_state_.GetRole(); }
bool ReplicationHandler::IsMain() const { return repl_state_.IsMain(); }
bool ReplicationHandler::IsReplica() const { return repl_state_.IsReplica(); }
} // namespace memgraph::storage

View File

@ -1,61 +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 "replication_server.hpp"
#include "io/network/endpoint.hpp"
#include "replication/config.hpp"
#include "rpc.hpp"
namespace memgraph::storage {
namespace {
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{};
}
// NOTE: The replication server must have a single thread for processing
// because there is no need for more processing threads - each replica can
// have only a single main server. Also, the single-threaded guarantee
// simplifies the rest of the implementation.
constexpr auto kReplictionServerThreads = 1;
} // namespace
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} {
rpc_server_.Register<replication::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
spdlog::debug("Received FrequentHeartbeatRpc");
FrequentHeartbeatHandler(req_reader, res_builder);
});
}
ReplicationServer::~ReplicationServer() {
if (rpc_server_.IsRunning()) {
auto const &endpoint = rpc_server_.endpoint();
spdlog::trace("Closing replication server on {}:{}", endpoint.address, endpoint.port);
rpc_server_.Shutdown();
}
rpc_server_.AwaitShutdown();
}
bool ReplicationServer::Start() { return rpc_server_.Start(); }
void ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::FrequentHeartbeatReq req;
slk::Load(&req, req_reader);
replication::FrequentHeartbeatRes res{true};
slk::Save(res, res_builder);
}
} // namespace memgraph::storage

View File

@ -11,8 +11,8 @@
#include "storage/v2/replication/replication_storage_state.hpp"
#include "replication/replication_server.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
namespace memgraph::storage {
@ -91,17 +91,16 @@ std::vector<ReplicaInfo> ReplicationStorageState::ReplicasInfo() const {
}
void ReplicationStorageState::Reset() {
replication_server_.reset();
replication_clients_.WithLock([](auto &clients) { clients.clear(); });
}
void ReplicationStorageState::AddEpochToHistory(std::string prev_epoch) {
void ReplicationStorageState::TrackLatestHistory() {
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_);
history.emplace_back(epoch_.id(), last_commit_timestamp_);
}
void ReplicationStorageState::AddEpochToHistoryForce(std::string prev_epoch) {

View File

@ -27,11 +27,12 @@
#include "storage/v2/replication/global.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {
class Storage;
class ReplicationServer;
class ReplicationClient;
struct ReplicationStorageState {
@ -49,7 +50,7 @@ struct ReplicationStorageState {
auto ReplicasInfo() const -> std::vector<ReplicaInfo>;
// History
void AddEpochToHistory(std::string prev_epoch);
void TrackLatestHistory();
void AddEpochToHistoryForce(std::string prev_epoch);
void Reset();
@ -76,10 +77,9 @@ struct ReplicationStorageState {
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_;
memgraph::replication::ReplicationEpoch epoch_;
};
} // namespace memgraph::storage

View File

@ -34,18 +34,6 @@ void HeartbeatRes::Save(const HeartbeatRes &self, memgraph::slk::Builder *builde
memgraph::slk::Save(self, builder);
}
void HeartbeatRes::Load(HeartbeatRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
void SnapshotReq::Save(const SnapshotReq &self, memgraph::slk::Builder *builder) { memgraph::slk::Save(self, builder); }
void SnapshotReq::Load(SnapshotReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); }
void SnapshotRes::Save(const SnapshotRes &self, memgraph::slk::Builder *builder) { memgraph::slk::Save(self, builder); }
@ -86,12 +74,6 @@ constexpr utils::TypeInfo storage::replication::HeartbeatReq::kType{utils::TypeI
constexpr utils::TypeInfo storage::replication::HeartbeatRes::kType{utils::TypeId::REP_HEARTBEAT_RES, "HeartbeatRes",
nullptr};
constexpr utils::TypeInfo storage::replication::FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ,
"FrequentHeartbeatReq", nullptr};
constexpr utils::TypeInfo storage::replication::FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES,
"FrequentHeartbeatRes", nullptr};
constexpr utils::TypeInfo storage::replication::SnapshotReq::kType{utils::TypeId::REP_SNAPSHOT_REQ, "SnapshotReq",
nullptr};
@ -121,47 +103,61 @@ namespace slk {
// Serialize code for TimestampRes
void Save(const memgraph::storage::replication::TimestampRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
}
void Load(memgraph::storage::replication::TimestampRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
}
// Serialize code for TimestampReq
void Save(const memgraph::storage::replication::TimestampReq &self, memgraph::slk::Builder *builder) {}
void Save(const memgraph::storage::replication::TimestampReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
}
void Load(memgraph::storage::replication::TimestampReq *self, memgraph::slk::Reader *reader) {}
void Load(memgraph::storage::replication::TimestampReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
}
// Serialize code for CurrentWalRes
void Save(const memgraph::storage::replication::CurrentWalRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
}
void Load(memgraph::storage::replication::CurrentWalRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
}
// Serialize code for CurrentWalReq
void Save(const memgraph::storage::replication::CurrentWalReq &self, memgraph::slk::Builder *builder) {}
void Save(const memgraph::storage::replication::CurrentWalReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
}
void Load(memgraph::storage::replication::CurrentWalReq *self, memgraph::slk::Reader *reader) {}
void Load(memgraph::storage::replication::CurrentWalReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
}
// Serialize code for WalFilesRes
void Save(const memgraph::storage::replication::WalFilesRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
}
void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
}
@ -169,56 +165,50 @@ void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Read
// Serialize code for WalFilesReq
void Save(const memgraph::storage::replication::WalFilesReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.file_number, builder);
}
void Load(memgraph::storage::replication::WalFilesReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->file_number, reader);
}
// Serialize code for SnapshotRes
void Save(const memgraph::storage::replication::SnapshotRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
}
void Load(memgraph::storage::replication::SnapshotRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
}
// Serialize code for SnapshotReq
void Save(const memgraph::storage::replication::SnapshotReq &self, memgraph::slk::Builder *builder) {}
void Load(memgraph::storage::replication::SnapshotReq *self, memgraph::slk::Reader *reader) {}
// Serialize code for FrequentHeartbeatRes
void Save(const memgraph::storage::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.success, builder);
void Save(const memgraph::storage::replication::SnapshotReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
}
void Load(memgraph::storage::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->success, reader);
void Load(memgraph::storage::replication::SnapshotReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
}
// Serialize code for FrequentHeartbeatReq
void Save(const memgraph::storage::replication::FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {}
void Load(memgraph::storage::replication::FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {}
// Serialize code for HeartbeatRes
void Save(const memgraph::storage::replication::HeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
memgraph::slk::Save(self.epoch_id, builder);
}
void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
memgraph::slk::Load(&self->epoch_id, reader);
@ -227,11 +217,13 @@ void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Rea
// Serialize code for HeartbeatReq
void Save(const memgraph::storage::replication::HeartbeatReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.main_commit_timestamp, builder);
memgraph::slk::Save(self.epoch_id, builder);
}
void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->main_commit_timestamp, reader);
memgraph::slk::Load(&self->epoch_id, reader);
}
@ -239,11 +231,13 @@ void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Rea
// Serialize code for AppendDeltasRes
void Save(const memgraph::storage::replication::AppendDeltasRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.success, builder);
memgraph::slk::Save(self.current_commit_timestamp, builder);
}
void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->success, reader);
memgraph::slk::Load(&self->current_commit_timestamp, reader);
}
@ -251,11 +245,13 @@ void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk::
// Serialize code for AppendDeltasReq
void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.db_name, builder);
memgraph::slk::Save(self.previous_commit_timestamp, builder);
memgraph::slk::Save(self.seq_num, builder);
}
void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->db_name, reader);
memgraph::slk::Load(&self->previous_commit_timestamp, reader);
memgraph::slk::Load(&self->seq_num, reader);
}

View File

@ -32,9 +32,10 @@ struct AppendDeltasReq {
static void Load(AppendDeltasReq *self, memgraph::slk::Reader *reader);
static void Save(const AppendDeltasReq &self, memgraph::slk::Builder *builder);
AppendDeltasReq() {}
AppendDeltasReq(uint64_t previous_commit_timestamp, uint64_t seq_num)
: previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {}
AppendDeltasReq(std::string name, uint64_t previous_commit_timestamp, uint64_t seq_num)
: db_name(std::move(name)), previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {}
std::string db_name;
uint64_t previous_commit_timestamp;
uint64_t seq_num;
};
@ -46,9 +47,10 @@ struct AppendDeltasRes {
static void Load(AppendDeltasRes *self, memgraph::slk::Reader *reader);
static void Save(const AppendDeltasRes &self, memgraph::slk::Builder *builder);
AppendDeltasRes() {}
AppendDeltasRes(bool success, uint64_t current_commit_timestamp)
: success(success), current_commit_timestamp(current_commit_timestamp) {}
AppendDeltasRes(std::string name, bool success, uint64_t current_commit_timestamp)
: db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
};
@ -62,9 +64,10 @@ struct HeartbeatReq {
static void Load(HeartbeatReq *self, memgraph::slk::Reader *reader);
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(std::move(epoch_id)) {}
HeartbeatReq(std::string name, uint64_t main_commit_timestamp, std::string epoch_id)
: db_name(std::move(name)), main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {}
std::string db_name;
uint64_t main_commit_timestamp;
std::string epoch_id;
};
@ -76,9 +79,13 @@ struct HeartbeatRes {
static void Load(HeartbeatRes *self, memgraph::slk::Reader *reader);
static void Save(const HeartbeatRes &self, memgraph::slk::Builder *builder);
HeartbeatRes() {}
HeartbeatRes(bool success, uint64_t current_commit_timestamp, std::string epoch_id)
: success(success), current_commit_timestamp(current_commit_timestamp), epoch_id(epoch_id) {}
HeartbeatRes(std::string name, bool success, uint64_t current_commit_timestamp, std::string epoch_id)
: db_name(std::move(name)),
success(success),
current_commit_timestamp(current_commit_timestamp),
epoch_id(epoch_id) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
std::string epoch_id;
@ -86,29 +93,6 @@ struct HeartbeatRes {
using HeartbeatRpc = rpc::RequestResponse<HeartbeatReq, HeartbeatRes>;
struct FrequentHeartbeatReq {
static const utils::TypeInfo kType;
static const utils::TypeInfo &GetTypeInfo() { return kType; }
static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader);
static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder);
FrequentHeartbeatReq() {}
};
struct FrequentHeartbeatRes {
static const utils::TypeInfo kType;
static const utils::TypeInfo &GetTypeInfo() { return kType; }
static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
FrequentHeartbeatRes() {}
explicit FrequentHeartbeatRes(bool success) : success(success) {}
bool success;
};
using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>;
struct SnapshotReq {
static const utils::TypeInfo kType;
static const utils::TypeInfo &GetTypeInfo() { return kType; }
@ -116,6 +100,9 @@ struct SnapshotReq {
static void Load(SnapshotReq *self, memgraph::slk::Reader *reader);
static void Save(const SnapshotReq &self, memgraph::slk::Builder *builder);
SnapshotReq() {}
explicit SnapshotReq(std::string name) : db_name(std::move(name)) {}
std::string db_name;
};
struct SnapshotRes {
@ -125,9 +112,10 @@ struct SnapshotRes {
static void Load(SnapshotRes *self, memgraph::slk::Reader *reader);
static void Save(const SnapshotRes &self, memgraph::slk::Builder *builder);
SnapshotRes() {}
SnapshotRes(bool success, uint64_t current_commit_timestamp)
: success(success), current_commit_timestamp(current_commit_timestamp) {}
SnapshotRes(std::string name, bool success, uint64_t current_commit_timestamp)
: db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
};
@ -141,8 +129,9 @@ struct WalFilesReq {
static void Load(WalFilesReq *self, memgraph::slk::Reader *reader);
static void Save(const WalFilesReq &self, memgraph::slk::Builder *builder);
WalFilesReq() {}
explicit WalFilesReq(uint64_t file_number) : file_number(file_number) {}
explicit WalFilesReq(std::string name, uint64_t file_number) : db_name(std::move(name)), file_number(file_number) {}
std::string db_name;
uint64_t file_number;
};
@ -153,9 +142,10 @@ struct WalFilesRes {
static void Load(WalFilesRes *self, memgraph::slk::Reader *reader);
static void Save(const WalFilesRes &self, memgraph::slk::Builder *builder);
WalFilesRes() {}
WalFilesRes(bool success, uint64_t current_commit_timestamp)
: success(success), current_commit_timestamp(current_commit_timestamp) {}
WalFilesRes(std::string name, bool success, uint64_t current_commit_timestamp)
: db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
};
@ -169,6 +159,9 @@ struct CurrentWalReq {
static void Load(CurrentWalReq *self, memgraph::slk::Reader *reader);
static void Save(const CurrentWalReq &self, memgraph::slk::Builder *builder);
CurrentWalReq() {}
explicit CurrentWalReq(std::string name) : db_name(std::move(name)) {}
std::string db_name;
};
struct CurrentWalRes {
@ -178,9 +171,10 @@ struct CurrentWalRes {
static void Load(CurrentWalRes *self, memgraph::slk::Reader *reader);
static void Save(const CurrentWalRes &self, memgraph::slk::Builder *builder);
CurrentWalRes() {}
CurrentWalRes(bool success, uint64_t current_commit_timestamp)
: success(success), current_commit_timestamp(current_commit_timestamp) {}
CurrentWalRes(std::string name, bool success, uint64_t current_commit_timestamp)
: db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
};
@ -194,6 +188,9 @@ struct TimestampReq {
static void Load(TimestampReq *self, memgraph::slk::Reader *reader);
static void Save(const TimestampReq &self, memgraph::slk::Builder *builder);
TimestampReq() {}
explicit TimestampReq(std::string name) : db_name(std::move(name)) {}
std::string db_name;
};
struct TimestampRes {
@ -203,9 +200,10 @@ struct TimestampRes {
static void Load(TimestampRes *self, memgraph::slk::Reader *reader);
static void Save(const TimestampRes &self, memgraph::slk::Builder *builder);
TimestampRes() {}
TimestampRes(bool success, uint64_t current_commit_timestamp)
: success(success), current_commit_timestamp(current_commit_timestamp) {}
TimestampRes(std::string name, bool success, uint64_t current_commit_timestamp)
: db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {}
std::string db_name;
bool success;
uint64_t current_commit_timestamp;
};
@ -251,14 +249,6 @@ void Save(const memgraph::storage::replication::SnapshotReq &self, memgraph::slk
void Load(memgraph::storage::replication::SnapshotReq *self, memgraph::slk::Reader *reader);
void Save(const memgraph::storage::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
void Load(memgraph::storage::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
void Save(const memgraph::storage::replication::FrequentHeartbeatReq &self, memgraph::slk::Builder *builder);
void Load(memgraph::storage::replication::FrequentHeartbeatReq *self, memgraph::slk::Reader *reader);
void Save(const memgraph::storage::replication::HeartbeatRes &self, memgraph::slk::Builder *builder);
void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Reader *reader);

View File

@ -31,14 +31,8 @@ namespace memgraph::storage {
class InMemoryStorage;
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)
: repl_state_(ReplicationStateHelper(config)),
name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> {
: 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);
@ -55,26 +49,26 @@ Storage::Storage(Config config, StorageMode storage_mode)
}
Storage::Accessor::Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
StorageMode storage_mode, bool is_main)
: 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)),
transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}
Storage::Accessor::Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
StorageMode storage_mode, bool is_main)
: 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)),
transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}

View File

@ -20,6 +20,7 @@
#include "kvstore/kvstore.hpp"
#include "query/exceptions.hpp"
#include "replication/config.hpp"
#include "replication/replication_server.hpp"
#include "storage/v2/all_vertices_iterable.hpp"
#include "storage/v2/commit_log.hpp"
#include "storage/v2/config.hpp"
@ -30,7 +31,6 @@
#include "storage/v2/mvcc.hpp"
#include "storage/v2/replication/enums.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"
@ -130,8 +130,10 @@ class Storage {
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(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
bool is_main = true);
Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
bool is_main = true);
Accessor(const Accessor &) = delete;
Accessor &operator=(const Accessor &) = delete;
Accessor &operator=(Accessor &&other) = delete;
@ -211,7 +213,7 @@ class Storage {
// NOLINTNEXTLINE(google-default-arguments)
virtual utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) = 0;
std::optional<uint64_t> desired_commit_timestamp = {}, bool is_main = true) = 0;
virtual void Abort() = 0;
@ -307,11 +309,20 @@ class Storage {
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 std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level, bool is_main) = 0;
std::unique_ptr<Accessor> Access(bool is_main = true) { return Access(std::optional<IsolationLevel>{}, is_main); }
std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) {
return Access(std::move(override_isolation_level), true);
}
virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) = 0;
std::unique_ptr<Accessor> UniqueAccess() { return UniqueAccess(std::optional<IsolationLevel>{}); }
virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) = 0;
std::unique_ptr<Accessor> UniqueAccess(bool is_main = true) {
return UniqueAccess(std::optional<IsolationLevel>{}, is_main);
}
std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) {
return UniqueAccess(std::move(override_isolation_level), true);
}
enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode };
@ -338,23 +349,24 @@ class Storage {
return GetInfo(force_dir);
}
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) = 0;
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
return CreateTransaction(isolation_level, storage_mode, true);
}
virtual void PrepareForNewEpoch(std::string prev_epoch) = 0;
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) = 0;
virtual auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
virtual void PrepareForNewEpoch() = 0;
virtual auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *current_epoch)
-> std::unique_ptr<ReplicationClient> = 0;
virtual auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> = 0;
auto ReplicasInfo() const { return repl_storage_state_.ReplicasInfo(); }
auto GetReplicaState(std::string_view name) const -> std::optional<replication::ReplicaState> {
return repl_storage_state_.GetReplicaState(name);
}
// TODO: make non-public
memgraph::replication::ReplicationState repl_state_;
ReplicationStorageState repl_storage_state_;
// Main storage lock.

View File

@ -15,6 +15,7 @@
#include <fmt/format.h>
#include "communication/bolt/metrics.hpp"
#include "requests/requests.hpp"
#include "telemetry/collectors.hpp"
#include "utils/event_counter.hpp"
@ -154,7 +155,6 @@ void Telemetry::AddDatabaseCollector(dbms::DbmsHandler &dbms_handler) {
#else
#endif
#ifdef MG_ENTERPRISE
void Telemetry::AddStorageCollector(
dbms::DbmsHandler &dbms_handler,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> &auth) {
@ -164,33 +164,6 @@ void Telemetry::AddStorageCollector(
return ToJson(stats);
});
}
#else
void Telemetry::AddStorageCollector(
memgraph::utils::Gatekeeper<memgraph::dbms::Database> &db_gatekeeper,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> &auth) {
AddCollector("storage", [&db_gatekeeper, &auth]() -> nlohmann::json {
memgraph::dbms::Statistics stats;
auto db_acc_opt = db_gatekeeper.access();
MG_ASSERT(db_acc_opt, "Failed to get access to the default database");
auto &db_acc = *db_acc_opt;
const auto &info = db_acc->GetInfo();
const auto &storage_info = info.storage_info;
stats.num_vertex = storage_info.vertex_count;
stats.num_edges = storage_info.edge_count;
stats.triggers = info.triggers;
stats.streams = info.streams;
stats.num_databases = 1;
stats.indices += storage_info.label_indices + storage_info.label_property_indices;
stats.constraints += storage_info.existence_constraints + storage_info.unique_constraints;
++stats.storage_modes[(int)storage_info.storage_mode];
++stats.isolation_levels[(int)storage_info.isolation_level];
stats.snapshot_enabled = storage_info.durability_snapshot_enabled;
stats.wal_enabled = storage_info.durability_wal_enabled;
stats.users = auth->AllUsers().size();
return ToJson(stats);
});
}
#endif
void Telemetry::AddExceptionCollector() {
AddCollector("exception", []() -> nlohmann::json { return memgraph::metrics::global_counters_map.ToJson(); });

View File

@ -43,15 +43,9 @@ class Telemetry final {
void AddCollector(const std::string &name, const std::function<const nlohmann::json(void)> &func);
// Specialized collectors
#ifdef MG_ENTERPRISE
void AddStorageCollector(
dbms::DbmsHandler &dbms_handler,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> &auth);
#else
void AddStorageCollector(
memgraph::utils::Gatekeeper<memgraph::dbms::Database> &db_gatekeeper,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> &auth);
#endif
#ifdef MG_ENTERPRISE
void AddDatabaseCollector(dbms::DbmsHandler &dbms_handler);

View File

@ -81,7 +81,7 @@ EvalResult(run_t, Func &&, T &) -> EvalResult<std::invoke_result_t<Func, T &>>;
template <typename T>
struct Gatekeeper {
template <typename... Args>
explicit Gatekeeper(Args &&...args) : value_{std::forward<Args>(args)...} {}
explicit Gatekeeper(Args &&...args) : value_{std::in_place, std::forward<Args>(args)...} {}
Gatekeeper(Gatekeeper const &) = delete;
Gatekeeper(Gatekeeper &&) noexcept = delete;

View File

@ -47,8 +47,12 @@ inline void AssertFailed(const char *file_name, int line_num, const char *expr,
#define GET_MESSAGE(...) \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 0), "", fmt::format(__VA_ARGS__))
#define MG_ASSERT(expr, ...) \
[[likely]] !!(expr) ? (void)0 : ::memgraph::logging::AssertFailed(__FILE__, __LINE__, #expr, GET_MESSAGE(__VA_ARGS__))
#define MG_ASSERT(expr, ...) \
if (expr) [[likely]] { \
(void)0; \
} else { \
::memgraph::logging::AssertFailed(__FILE__, __LINE__, #expr, GET_MESSAGE(__VA_ARGS__)); \
}
#ifndef NDEBUG
#define DMG_ASSERT(expr, ...) MG_ASSERT(expr, __VA_ARGS__)

View File

@ -17,6 +17,7 @@
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/typed_value.hpp"
#include "replication/status.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
#include "utils/logging.hpp"
@ -28,16 +29,18 @@ class ExpansionBenchFixture : public benchmark::Fixture {
std::optional<memgraph::query::InterpreterContext> interpreter_context;
std::optional<memgraph::query::Interpreter> interpreter;
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk;
std::optional<memgraph::replication::ReplicationState> repl_state;
void SetUp(const benchmark::State &state) override {
repl_state.emplace(std::nullopt); // No need for a storage directory, since we are not replicating or restoring
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = data_directory / "disk";
db_gk.emplace(std::move(config));
db_gk.emplace(std::move(config), *repl_state);
auto db_acc_opt = db_gk->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
interpreter_context.emplace(memgraph::query::InterpreterConfig{}, nullptr);
interpreter_context.emplace(memgraph::query::InterpreterConfig{}, nullptr, &repl_state.value());
auto label = db_acc->storage()->NameToLabel("Starting");

View File

@ -348,7 +348,7 @@ def test_basic_recovery(connection):
def test_replication_role_recovery(connection):
# Goal of this test is to check the recovery of main and replica role.
# 0/ We start all replicas manually: we want to be able to kill them ourselves without relying on external tooling to kill processes.
# 1/ We try to add a replica with reserved name which results in an exception
# 1/ We try to add a replica with reserved name which results in an exception <- Schema changed, there are no reserved names now
# 2/ We check that all replicas have the correct state: they should all be ready.
# 3/ We kill main.
# 4/ We re-start main. We check that main indeed has the role main and replicas still have the correct state.
@ -411,9 +411,9 @@ def test_replication_role_recovery(connection):
"data_directory": f"{data_directory.name}/main",
},
}
# 1/
with pytest.raises(mgclient.DatabaseError):
execute_and_fetch_all(cursor, "REGISTER REPLICA __replication_role SYNC TO '127.0.0.1:10002';")
# 1/ Obsolete, schema change, no longer a reserved name
# with pytest.raises(mgclient.DatabaseError):
# execute_and_fetch_all(cursor, "REGISTER REPLICA __replication_role SYNC TO '127.0.0.1:10002';")
# 2/
expected_data = {

View File

@ -181,7 +181,7 @@ def execute_test(name, test_path, test_config, memgraph_binary, mg_import_csv_bi
# Verify the queries
queries_expected.sort()
queries_got.sort()
assert queries_got == queries_expected, "Expected\n{}\nto be equal to\n" "{}".format(
assert queries_got == queries_expected, "Got:\n{}\nExpected:\n" "{}".format(
list_to_string(queries_got), list_to_string(queries_expected)
)
print("\033[1;32m~~ Test successful ~~\033[0m\n")

View File

@ -39,14 +39,16 @@ int main(int argc, char **argv) {
memgraph::storage::Config db_config;
memgraph::storage::UpdatePaths(db_config, data_directory);
memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(db_config));
memgraph::dbms::DbmsHandler dbms_handler(db_config, repl_state
#ifdef MG_ENTERPRISE
memgraph::dbms::DbmsHandler dbms_handler(db_config, &auth_, false, false);
memgraph::query::InterpreterContext interpreter_context_({}, &dbms_handler, &auth_handler, &auth_checker);
#else
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gatekeeper{db_config};
memgraph::query::InterpreterContext interpreter_context_({}, nullptr, &auth_handler, &auth_checker);
,
&auth_, false, false
#endif
);
memgraph::query::InterpreterContext interpreter_context_({}, &dbms_handler, &repl_state, &auth_handler,
&auth_checker);
memgraph::requests::Init();
memgraph::telemetry::Telemetry telemetry(FLAGS_endpoint, FLAGS_storage_directory, memgraph::utils::GenerateUUID(),
@ -61,11 +63,10 @@ int main(int argc, char **argv) {
});
// Memgraph specific collectors
#ifdef MG_ENTERPRISE
telemetry.AddStorageCollector(dbms_handler, auth_);
#ifdef MG_ENTERPRISE
telemetry.AddDatabaseCollector(dbms_handler);
#else
telemetry.AddStorageCollector(db_gatekeeper, auth_);
telemetry.AddDatabaseCollector();
#endif
telemetry.AddClientCollector();

View File

@ -30,14 +30,16 @@ int main(int argc, char *argv[]) {
auto data_directory = std::filesystem::temp_directory_path() / "single_query_test";
memgraph::utils::OnScopeExit([&data_directory] { std::filesystem::remove_all(data_directory); });
memgraph::storage::Config db_config{.durability.storage_directory = data_directory,
.disk.main_storage_directory = data_directory / "disk"};
memgraph::license::global_license_checker.EnableTesting();
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(memgraph::storage::Config{
.durability.storage_directory = data_directory, .disk.main_storage_directory = data_directory / "disk"});
memgraph::replication::ReplicationState repl_state(memgraph::storage::ReplicationStateRootPath(db_config));
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(db_config, repl_state);
auto db_acc_opt = db_gk.access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr, &repl_state);
memgraph::query::Interpreter interpreter{&interpreter_context, db_acc};
ResultStreamFaker stream(db_acc->storage());

View File

@ -313,7 +313,7 @@ add_unit_test(storage_v2_decoder_encoder.cpp)
target_link_libraries(${test_prefix}storage_v2_decoder_encoder mg-storage-v2)
add_unit_test(storage_v2_durability_inmemory.cpp)
target_link_libraries(${test_prefix}storage_v2_durability_inmemory mg-storage-v2)
target_link_libraries(${test_prefix}storage_v2_durability_inmemory mg-storage-v2 mg-dbms)
add_unit_test(storage_rocks.cpp)
target_link_libraries(${test_prefix}storage_rocks mg-storage-v2)
@ -346,7 +346,7 @@ add_unit_test(storage_v2_wal_file.cpp)
target_link_libraries(${test_prefix}storage_v2_wal_file mg-storage-v2 storage_test_utils fmt)
add_unit_test(storage_v2_replication.cpp)
target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 fmt)
target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 mg-dbms fmt)
add_unit_test(storage_v2_isolation_level.cpp)
target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2)
@ -388,19 +388,15 @@ if(MG_ENTERPRISE)
target_link_libraries(${test_prefix}slk_advanced mg-storage-v2)
endif()
if(MG_ENTERPRISE)
add_unit_test(slk_core.cpp)
target_link_libraries(${test_prefix}slk_core mg-slk gflags fmt)
add_unit_test(slk_core.cpp)
target_link_libraries(${test_prefix}slk_core mg-slk gflags fmt)
add_unit_test(slk_streams.cpp)
target_link_libraries(${test_prefix}slk_streams mg-slk gflags fmt)
endif()
add_unit_test(slk_streams.cpp)
target_link_libraries(${test_prefix}slk_streams mg-slk gflags fmt)
# Test mg-rpc
if(MG_ENTERPRISE)
add_unit_test(rpc.cpp)
target_link_libraries(${test_prefix}rpc mg-rpc)
endif()
add_unit_test(rpc.cpp)
target_link_libraries(${test_prefix}rpc mg-rpc)
# Test websocket
find_package(Boost REQUIRED)
@ -415,6 +411,9 @@ if(MG_ENTERPRISE)
add_unit_test_with_custom_main(dbms_handler.cpp)
target_link_libraries(${test_prefix}dbms_handler mg-query mg-auth mg-glue mg-dbms)
else()
add_unit_test_with_custom_main(dbms_handler_community.cpp)
target_link_libraries(${test_prefix}dbms_handler_community mg-query mg-auth mg-glue mg-dbms)
endif()
# Test distributed

View File

@ -12,13 +12,17 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include <optional>
#include "dbms/database.hpp"
#include "disk_test_utils.hpp"
#include "query/interpret/awesome_memgraph_functions.hpp"
#include "query/interpreter_context.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/replication/enums.hpp"
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace memgraph::storage;
@ -30,6 +34,8 @@ template <typename StorageType>
class InfoTest : public testing::Test {
protected:
void SetUp() {
repl_state.emplace(memgraph::storage::ReplicationStateRootPath(config));
db_gk.emplace(config, *repl_state);
auto db_acc_opt = db_gk->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
@ -43,6 +49,7 @@ class InfoTest : public testing::Test {
void TearDown() {
db_acc_.reset();
db_gk.reset();
repl_state.reset();
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}
@ -52,8 +59,10 @@ class InfoTest : public testing::Test {
StorageMode mode{std::is_same_v<StorageType, DiskStorage> ? StorageMode::ON_DISK_TRANSACTIONAL
: StorageMode::IN_MEMORY_TRANSACTIONAL};
std::optional<memgraph::replication::ReplicationState> repl_state;
std::optional<memgraph::dbms::DatabaseAccess> db_acc_;
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk{
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk;
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
memgraph::storage::UpdatePaths(config, storage_directory);

View File

@ -12,16 +12,19 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include <optional>
#include "dbms/database_handler.hpp"
#include "dbms/global.hpp"
#include "license/license.hpp"
#include "query_plan_common.hpp"
#include "replication/state.hpp"
#include "storage/v2/view.hpp"
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_database"};
memgraph::replication::ReplicationState generic_repl_state{std::nullopt};
memgraph::storage::Config default_conf(std::string name = "") {
return {.durability = {.storage_directory = storage_directory / name,
.snapshot_wal_mode =
@ -53,19 +56,19 @@ TEST_F(DBMS_Database, New) {
.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "disk"}};
auto db2 = db_handler.New("db2", db_config);
auto db2 = db_handler.New("db2", db_config, generic_repl_state);
ASSERT_TRUE(db2.HasValue() && db2.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db2"));
}
{
// With default config
auto db3 = db_handler.New("db3", default_conf("db3"));
auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
ASSERT_TRUE(db3.HasValue() && db3.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "db3"));
auto db4 = db_handler.New("db4", default_conf("four"));
auto db4 = db_handler.New("db4", default_conf("four"), generic_repl_state);
ASSERT_TRUE(db4.HasValue() && db4.GetValue());
ASSERT_TRUE(std::filesystem::exists(storage_directory / "four"));
auto db5 = db_handler.New("db5", default_conf("db3"));
auto db5 = db_handler.New("db5", default_conf("db3"), generic_repl_state);
ASSERT_TRUE(db5.HasError() && db5.GetError() == memgraph::dbms::NewError::EXISTS);
}
@ -80,9 +83,9 @@ TEST_F(DBMS_Database, New) {
TEST_F(DBMS_Database, Get) {
memgraph::dbms::DatabaseHandler db_handler;
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db3 = db_handler.New("db3", default_conf("db3"));
auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
@ -104,9 +107,9 @@ TEST_F(DBMS_Database, Get) {
TEST_F(DBMS_Database, Delete) {
memgraph::dbms::DatabaseHandler db_handler;
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db3 = db_handler.New("db3", default_conf("db3"));
auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
@ -141,8 +144,8 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
memgraph::dbms::DatabaseHandler db_handler;
{
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
memgraph::storage::Config conf_w_snap{
.durability = {.storage_directory = storage_directory / "db3",
@ -151,7 +154,7 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
.snapshot_on_exit = true},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = db_handler.New("db3", conf_w_snap);
auto db3 = db_handler.New("db3", conf_w_snap, generic_repl_state);
ASSERT_TRUE(db1.HasValue());
ASSERT_TRUE(db2.HasValue());
@ -187,8 +190,8 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
{
// Recover graphs (only db3)
auto db1 = db_handler.New("db1", default_conf("db1"));
auto db2 = db_handler.New("db2", default_conf("db2"));
auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state);
auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state);
memgraph::storage::Config conf_w_rec{
.durability = {.storage_directory = storage_directory / "db3",
@ -197,7 +200,7 @@ TEST_F(DBMS_Database, DeleteAndRecover) {
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL},
.disk = {.main_storage_directory = storage_directory / "db3" / "disk"}};
auto db3 = db_handler.New("db3", conf_w_rec);
auto db3 = db_handler.New("db3", conf_w_rec, generic_repl_state);
// Check content
{

View File

@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "query/auth_query_handler.hpp"
#include "storage/v2/config.hpp"
#ifdef MG_ENTERPRISE
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@ -51,15 +52,18 @@ class TestEnvironment : public ::testing::Environment {
auth =
std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>(
storage_directory / "auth");
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false, true);
repl_state_.emplace(memgraph::storage::ReplicationStateRootPath(storage_conf));
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *repl_state_, auth.get(), false, true);
}
void TearDown() override {
ptr_.reset();
auth.reset();
repl_state_.reset();
}
static std::unique_ptr<memgraph::dbms::DbmsHandler> ptr_;
std::optional<memgraph::replication::ReplicationState> repl_state_;
};
std::unique_ptr<memgraph::dbms::DbmsHandler> TestEnvironment::ptr_ = nullptr;

View File

@ -0,0 +1,106 @@
// 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 "query/auth_query_handler.hpp"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <filesystem>
#include <system_error>
#include "dbms/constants.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/global.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "storage/v2/config.hpp"
// Global
std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_handler_community"};
static memgraph::storage::Config storage_conf;
std::unique_ptr<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>> auth;
// Let this be global so we can test it different states throughout
class TestEnvironment : public ::testing::Environment {
public:
static memgraph::dbms::DbmsHandler *get() { return ptr_.get(); }
void SetUp() override {
// Setup config
memgraph::storage::UpdatePaths(storage_conf, storage_directory);
storage_conf.durability.snapshot_wal_mode =
memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL;
// Clean storage directory (running multiple parallel test, run only if the first process)
if (std::filesystem::exists(storage_directory)) {
memgraph::utils::OutputFile lock_file_handle_;
lock_file_handle_.Open(storage_directory / ".lock", memgraph::utils::OutputFile::Mode::OVERWRITE_EXISTING);
if (lock_file_handle_.AcquireLock()) {
std::filesystem::remove_all(storage_directory);
}
}
auth =
std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>(
storage_directory / "auth");
repl_state_.emplace(memgraph::storage::ReplicationStateRootPath(storage_conf));
ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, *repl_state_);
}
void TearDown() override {
ptr_.reset();
auth.reset();
repl_state_.reset();
}
static std::unique_ptr<memgraph::dbms::DbmsHandler> ptr_;
std::optional<memgraph::replication::ReplicationState> repl_state_;
};
std::unique_ptr<memgraph::dbms::DbmsHandler> TestEnvironment::ptr_ = nullptr;
class DBMS_Handler : public testing::Test {};
using DBMS_HandlerDeath = DBMS_Handler;
TEST(DBMS_Handler, Init) {
// Check that the default db has been created successfully
std::vector<std::string> dirs = {"snapshots", "streams", "triggers", "wal"};
for (const auto &dir : dirs)
ASSERT_TRUE(std::filesystem::exists(storage_directory / dir)) << (storage_directory / dir);
auto &dbms = *TestEnvironment::get();
{
const auto all = dbms.All();
ASSERT_EQ(all.size(), 1);
ASSERT_EQ(all[0], memgraph::dbms::kDefaultDB);
}
}
TEST(DBMS_Handler, Get) {
auto &dbms = *TestEnvironment::get();
auto default_db = dbms.Get();
ASSERT_TRUE(default_db);
ASSERT_TRUE(default_db->storage() != nullptr);
ASSERT_TRUE(default_db->streams() != nullptr);
ASSERT_TRUE(default_db->trigger_store() != nullptr);
ASSERT_TRUE(default_db->thread_pool() != nullptr);
ASSERT_EQ(default_db->storage()->id(), memgraph::dbms::kDefaultDB);
auto conf = storage_conf;
conf.name = memgraph::dbms::kDefaultDB;
ASSERT_EQ(default_db->storage()->config_, conf);
}
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
// gtest takes ownership of the TestEnvironment ptr - we don't delete it.
::testing::AddGlobalTestEnvironment(new TestEnvironment);
return RUN_ALL_TESTS();
}

View File

@ -30,6 +30,7 @@
#include "query/stream.hpp"
#include "query/typed_value.hpp"
#include "query_common.hpp"
#include "replication/state.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
#include "storage/v2/property_value.hpp"
@ -60,9 +61,9 @@ class InterpreterTest : public ::testing::Test {
const std::string testSuiteCsv = "interpreter_csv";
std::filesystem::path data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_interpreter";
InterpreterTest() : interpreter_context({}, kNoHandler) {}
InterpreterTest() {}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
@ -75,6 +76,8 @@ class InterpreterTest : public ::testing::Test {
}() // iile
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
@ -88,7 +91,7 @@ class InterpreterTest : public ::testing::Test {
}() // iile
};
memgraph::query::InterpreterContext interpreter_context;
memgraph::query::InterpreterContext interpreter_context{{}, kNoHandler, &repl_state};
void TearDown() override {
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
@ -1131,7 +1134,8 @@ TYPED_TEST(InterpreterTest, AllowLoadCsvConfig) {
config2.force_on_disk = true;
}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk2(config2);
memgraph::replication::ReplicationState repl_state2{memgraph::storage::ReplicationStateRootPath(config2)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk2(config2, repl_state2);
auto db_acc_opt = db_gk2.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db2";
auto &db_acc = *db_acc_opt;
@ -1140,7 +1144,9 @@ TYPED_TEST(InterpreterTest, AllowLoadCsvConfig) {
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext csv_interpreter_context{{.query = {.allow_load_csv = allow_load_csv}}, nullptr};
memgraph::replication::ReplicationState repl_state{std::nullopt};
memgraph::query::InterpreterContext csv_interpreter_context{
{.query = {.allow_load_csv = allow_load_csv}}, nullptr, &repl_state};
InterpreterFaker interpreter_faker{&csv_interpreter_context, db_acc};
for (const auto &query : queries) {
if (allow_load_csv) {

View File

@ -25,6 +25,7 @@
#include "query/interpreter_context.hpp"
#include "query/stream/streams.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/inmemory/storage.hpp"
@ -282,7 +283,7 @@ class DumpTest : public ::testing::Test {
const std::string testSuite = "query_dump";
std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_dump_class"};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
@ -295,6 +296,8 @@ class DumpTest : public ::testing::Test {
}() // iile
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
@ -308,7 +311,7 @@ class DumpTest : public ::testing::Test {
}() // iile
};
memgraph::query::InterpreterContext context{memgraph::query::InterpreterConfig{}, nullptr};
memgraph::query::InterpreterContext context{memgraph::query::InterpreterConfig{}, nullptr, &repl_state};
void TearDown() override {
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
@ -697,8 +700,9 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s1").disk;
config.force_on_disk = true;
}
memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(config));
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config);
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config, repl_state);
auto db_acc_opt = db_gk.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db";
auto &db_acc = *db_acc_opt;
@ -707,7 +711,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) {
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr, &repl_state);
{
ResultStreamFaker stream(this->db->storage());
@ -811,7 +815,8 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
config.force_on_disk = true;
}
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config);
memgraph::replication::ReplicationState repl_state{ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
auto db_acc_opt = db_gk.access();
ASSERT_TRUE(db_acc_opt) << "Failed to access db";
auto &db_acc = *db_acc_opt;
@ -820,7 +825,7 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) {
: memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL))
<< "Wrong storage mode!";
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr);
memgraph::query::InterpreterContext interpreter_context(memgraph::query::InterpreterConfig{}, nullptr, &repl_state);
{
ResultStreamFaker stream(this->db->storage());
memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource());

View File

@ -40,20 +40,23 @@ class QueryExecution : public testing::Test {
std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_plan_edge_cases"};
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}() // iile
};
std::optional<memgraph::replication::ReplicationState> repl_state;
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk;
void SetUp() {
auto config = [&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
config.disk.main_storage_directory = config.durability.storage_directory / "disk";
if constexpr (std::is_same_v<StorageType, memgraph::storage::DiskStorage>) {
config.disk = disk_test_utils::GenerateOnDiskConfig(testSuite).disk;
config.force_on_disk = true;
}
return config;
}(); // iile
repl_state.emplace(memgraph::storage::ReplicationStateRootPath(config));
db_gk.emplace(config, *repl_state);
auto db_acc_opt = db_gk->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
auto &db_acc = *db_acc_opt;
@ -63,7 +66,7 @@ class QueryExecution : public testing::Test {
"Wrong storage mode!");
db_acc_ = std::move(db_acc);
interpreter_context_.emplace(memgraph::query::InterpreterConfig{}, nullptr);
interpreter_context_.emplace(memgraph::query::InterpreterConfig{}, nullptr, &repl_state.value());
interpreter_.emplace(&*interpreter_context_, *db_acc_);
}
@ -72,6 +75,7 @@ class QueryExecution : public testing::Test {
interpreter_context_ = std::nullopt;
db_acc_.reset();
db_gk.reset();
repl_state.reset();
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
}

View File

@ -24,6 +24,7 @@
#include "query/interpreter.hpp"
#include "query/interpreter_context.hpp"
#include "query/stream/streams.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "test_utils.hpp"
@ -76,7 +77,7 @@ class StreamsTestFixture : public ::testing::Test {
// InterpreterContext::auth_checker_ is used in the Streams object, but only in the message processing part. Because
// these tests don't send any messages, the auth_checker_ pointer can be left as nullptr.
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory_;
@ -88,6 +89,9 @@ class StreamsTestFixture : public ::testing::Test {
return config;
}() // iile
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db_{
[&]() {
auto db_acc_opt = db_gk.access();
@ -100,7 +104,7 @@ class StreamsTestFixture : public ::testing::Test {
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context_{memgraph::query::InterpreterConfig{}, nullptr};
memgraph::query::InterpreterContext interpreter_context_{memgraph::query::InterpreterConfig{}, nullptr, &repl_state};
std::filesystem::path streams_data_directory_{data_directory_ / "separate-dir-for-test"};
std::optional<StreamsTest> proxyStreams_;

View File

@ -20,83 +20,95 @@
#include <optional>
#include <string>
using namespace memgraph::storage::replication;
using namespace memgraph::replication::durability;
using namespace memgraph::replication;
class ReplicationPersistanceHelperTest : public testing::Test {
protected:
void SetUp() override {}
static_assert(sizeof(ReplicationRoleEntry) == 168,
"Most likely you modified ReplicationRoleEntry without updating the tests. ");
void TearDown() override {}
static_assert(sizeof(ReplicationReplicaEntry) == 160,
"Most likely you modified ReplicationReplicaEntry without updating the tests.");
ReplicationStatus CreateReplicationStatus(std::string name, std::string ip_address, uint16_t port,
ReplicationMode sync_mode, std::chrono::seconds replica_check_frequency,
std::optional<ReplicationClientConfig::SSL> ssl,
std::optional<ReplicationRole> role) const {
return ReplicationStatus{.name = name,
.ip_address = ip_address,
.port = port,
.sync_mode = sync_mode,
.replica_check_frequency = replica_check_frequency,
.ssl = ssl,
.role = role};
}
static_assert(
sizeof(ReplicationStatus) == 160,
"Most likely you modified ReplicationStatus without updating the tests. Please modify CreateReplicationStatus. ");
};
TEST_F(ReplicationPersistanceHelperTest, BasicTestAllAttributesInitialized) {
auto replicas_status = CreateReplicationStatus(
"name", "ip_address", 0, ReplicationMode::SYNC, std::chrono::seconds(1),
ReplicationClientConfig::SSL{.key_file = "key_file", .cert_file = "cert_file"}, ReplicationRole::REPLICA);
auto json_status = ReplicationStatusToJSON(ReplicationStatus(replicas_status));
auto replicas_status_converted = JSONToReplicationStatus(std::move(json_status));
ASSERT_EQ(replicas_status, *replicas_status_converted);
TEST(ReplicationDurability, V1Main) {
auto const role_entry = ReplicationRoleEntry{.version = DurabilityVersion::V1,
.role = MainRole{
.epoch = ReplicationEpoch{"TEST_STRING"},
}};
nlohmann::json j;
to_json(j, role_entry);
ReplicationRoleEntry deser;
from_json(j, deser);
ASSERT_EQ(role_entry, deser);
}
TEST_F(ReplicationPersistanceHelperTest, BasicTestOnlyMandatoryAttributesInitialized) {
auto replicas_status = CreateReplicationStatus("name", "ip_address", 0, ReplicationMode::SYNC,
std::chrono::seconds(1), std::nullopt, std::nullopt);
auto json_status = ReplicationStatusToJSON(ReplicationStatus(replicas_status));
auto replicas_status_converted = JSONToReplicationStatus(std::move(json_status));
ASSERT_EQ(replicas_status, *replicas_status_converted);
TEST(ReplicationDurability, V2Main) {
auto const role_entry = ReplicationRoleEntry{.version = DurabilityVersion::V2,
.role = MainRole{
.epoch = ReplicationEpoch{"TEST_STRING"},
}};
nlohmann::json j;
to_json(j, role_entry);
ReplicationRoleEntry deser;
from_json(j, deser);
ASSERT_EQ(role_entry, deser);
}
TEST_F(ReplicationPersistanceHelperTest, BasicTestAllAttributesButSSLInitialized) {
auto replicas_status = CreateReplicationStatus("name", "ip_address", 0, ReplicationMode::SYNC,
std::chrono::seconds(1), std::nullopt, ReplicationRole::MAIN);
auto json_status = ReplicationStatusToJSON(ReplicationStatus(replicas_status));
auto replicas_status_converted = JSONToReplicationStatus(std::move(json_status));
ASSERT_EQ(replicas_status, *replicas_status_converted);
TEST(ReplicationDurability, V1Replica) {
auto const role_entry =
ReplicationRoleEntry{.version = DurabilityVersion::V1,
.role = ReplicaRole{
.config = ReplicationServerConfig{.ip_address = "000.123.456.789", .port = 2023},
}};
nlohmann::json j;
to_json(j, role_entry);
ReplicationRoleEntry deser;
from_json(j, deser);
ASSERT_EQ(role_entry, deser);
}
TEST_F(ReplicationPersistanceHelperTest, BasicTestAllAttributesButTimeoutInitialized) {
auto replicas_status = CreateReplicationStatus(
"name", "ip_address", 0, ReplicationMode::SYNC, std::chrono::seconds(1),
ReplicationClientConfig::SSL{.key_file = "key_file", .cert_file = "cert_file"}, ReplicationRole::REPLICA);
auto json_status = ReplicationStatusToJSON(ReplicationStatus(replicas_status));
auto replicas_status_converted = JSONToReplicationStatus(std::move(json_status));
ASSERT_EQ(replicas_status, *replicas_status_converted);
TEST(ReplicationDurability, V2Replica) {
auto const role_entry =
ReplicationRoleEntry{.version = DurabilityVersion::V2,
.role = ReplicaRole{
.config = ReplicationServerConfig{.ip_address = "000.123.456.789", .port = 2023},
}};
nlohmann::json j;
to_json(j, role_entry);
ReplicationRoleEntry deser;
from_json(j, deser);
ASSERT_EQ(role_entry, deser);
}
TEST_F(ReplicationPersistanceHelperTest, BasicTestAllAttributesButReplicationRoleInitialized) {
// this one is importand for backwards compatibility
auto replicas_status = CreateReplicationStatus(
"name", "ip_address", 0, ReplicationMode::SYNC, std::chrono::seconds(1),
ReplicationClientConfig::SSL{.key_file = "key_file", .cert_file = "cert_file"}, std::nullopt);
auto json_status = ReplicationStatusToJSON(ReplicationStatus(replicas_status));
auto replicas_status_converted = JSONToReplicationStatus(std::move(json_status));
ASSERT_EQ(replicas_status, *replicas_status_converted);
TEST(ReplicationDurability, ReplicaEntrySync) {
using namespace std::chrono_literals;
using namespace std::string_literals;
auto const replica_entry = ReplicationReplicaEntry{.config = ReplicationClientConfig{
.name = "TEST_NAME"s,
.mode = ReplicationMode::SYNC,
.ip_address = "000.123.456.789"s,
.port = 2023,
.replica_check_frequency = 3s,
}};
nlohmann::json j;
to_json(j, replica_entry);
ReplicationReplicaEntry deser;
from_json(j, deser);
ASSERT_EQ(replica_entry, deser);
}
TEST(ReplicationDurability, ReplicaEntryAsync) {
using namespace std::chrono_literals;
using namespace std::string_literals;
auto const replica_entry = ReplicationReplicaEntry{.config = ReplicationClientConfig{
.name = "TEST_NAME"s,
.mode = ReplicationMode::ASYNC,
.ip_address = "000.123.456.789"s,
.port = 2023,
.replica_check_frequency = 3s,
}};
nlohmann::json j;
to_json(j, replica_entry);
ReplicationReplicaEntry deser;
from_json(j, deser);
ASSERT_EQ(replica_entry, deser);
}

View File

@ -41,7 +41,8 @@ class ConstraintsTest : public testing::Test {
/// TODO: andi How to make this better? Because currentlly for every test changed you need to create a configuration
config_ = disk_test_utils::GenerateOnDiskConfig(testSuite);
config_.force_on_disk = std::is_same_v<StorageType, memgraph::storage::DiskStorage>;
db_gk_.emplace(config_);
repl_state_.emplace(memgraph::storage::ReplicationStateRootPath(config_));
db_gk_.emplace(config_, *repl_state_);
auto db_acc_opt = db_gk_->access();
MG_ASSERT(db_acc_opt, "Failed to access db");
db_acc_ = *db_acc_opt;
@ -56,6 +57,7 @@ class ConstraintsTest : public testing::Test {
storage = nullptr;
db_acc_.reset();
db_gk_.reset();
repl_state_.reset();
if (std::is_same<StorageType, memgraph::storage::DiskStorage>::value) {
disk_test_utils::RemoveRocksDbDirs(testSuite);
@ -64,6 +66,7 @@ class ConstraintsTest : public testing::Test {
Storage *storage;
memgraph::storage::Config config_;
std::optional<memgraph::replication::ReplicationState> repl_state_;
std::optional<memgraph::dbms::DatabaseAccess> db_acc_;
std::optional<memgraph::utils::Gatekeeper<memgraph::dbms::Database>> db_gk_;
PropertyId prop1;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -75,9 +75,11 @@ class StorageModeMultiTxTest : public ::testing::Test {
return tmp;
}(); // iile
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{memgraph::storage::Config{
.durability.storage_directory = data_directory, .disk.main_storage_directory = data_directory / "disk"}};
memgraph::storage::Config config{.durability.storage_directory = data_directory,
.disk.main_storage_directory = data_directory / "disk"};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
@ -86,8 +88,7 @@ class StorageModeMultiTxTest : public ::testing::Test {
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr, &repl_state};
InterpreterFaker running_interpreter{&interpreter_context, db}, main_interpreter{&interpreter_context, db};
};

View File

@ -31,7 +31,8 @@ class TransactionQueueSimpleTest : public ::testing::Test {
protected:
const std::string testSuite = "transactin_queue";
std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_transaction_queue_intr"};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
@ -44,6 +45,8 @@ class TransactionQueueSimpleTest : public ::testing::Test {
}() // iile
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
@ -56,7 +59,7 @@ class TransactionQueueSimpleTest : public ::testing::Test {
return db_acc;
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr, &repl_state};
InterpreterFaker running_interpreter{&interpreter_context, db}, main_interpreter{&interpreter_context, db};
void TearDown() override {

View File

@ -39,7 +39,8 @@ class TransactionQueueMultipleTest : public ::testing::Test {
const std::string testSuite = "transactin_queue_multiple";
std::filesystem::path data_directory{std::filesystem::temp_directory_path() /
"MG_tests_unit_transaction_queue_multiple_intr"};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{
memgraph::storage::Config config{
[&]() {
memgraph::storage::Config config{};
config.durability.storage_directory = data_directory;
@ -52,6 +53,8 @@ class TransactionQueueMultipleTest : public ::testing::Test {
}() // iile
};
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state};
memgraph::dbms::DatabaseAccess db{
[&]() {
auto db_acc_opt = db_gk.access();
@ -65,7 +68,7 @@ class TransactionQueueMultipleTest : public ::testing::Test {
}() // iile
};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr};
memgraph::query::InterpreterContext interpreter_context{{}, nullptr, &repl_state};
InterpreterFaker main_interpreter{&interpreter_context, db};
std::vector<InterpreterFaker *> running_interpreters;