Replication refactor (part 5) (#1378)
This commit is contained in:
parent
16b8c7b27c
commit
dbc6054689
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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
|
49
src/dbms/inmemory/replication_handlers.hpp
Normal file
49
src/dbms/inmemory/replication_handlers.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "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
|
68
src/dbms/inmemory/storage_helper.hpp
Normal file
68
src/dbms/inmemory/storage_helper.hpp
Normal 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
|
234
src/dbms/replication_handler.cpp
Normal file
234
src/dbms/replication_handler.cpp
Normal 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 ®istered_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
|
@ -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
|
@ -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)
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::glue {
|
||||
extern const std::string run_id_;
|
||||
} // namespace memgraph::glue
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ¶meters) {
|
||||
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 ¶meters, storage::Storage *storage,
|
||||
const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
|
||||
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters,
|
||||
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 ¤t_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 ¤t_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, ¤t_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();
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
96
src/replication/replication_server.cpp
Normal file
96
src/replication/replication_server.cpp
Normal 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
|
@ -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 ®istered_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 ®istered_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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
27
src/rpc/version.hpp
Normal 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
|
@ -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)
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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};
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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>) {
|
||||
|
@ -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
|
@ -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_);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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(); });
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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__)
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
106
tests/unit/dbms_handler_community.cpp
Normal file
106
tests/unit/dbms_handler_community.cpp
Normal 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();
|
||||
}
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
@ -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};
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user