Compare commits
57 Commits
MG_auth_re
...
af-set-reg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68ec01fa72 | ||
|
|
71e196485f | ||
|
|
dc35999c90 | ||
|
|
fabfcbae4a | ||
|
|
2fa2d59514 | ||
|
|
940a312418 | ||
|
|
0ea1e91bc1 | ||
|
|
122a3956ef | ||
|
|
bd86685367 | ||
|
|
465623c0e4 | ||
|
|
4bc7a8bd9d | ||
|
|
d5d151938a | ||
|
|
de3f43f497 | ||
|
|
6e4994b7db | ||
|
|
c46a3044b2 | ||
|
|
390e81447a | ||
|
|
99beaef037 | ||
|
|
5e5f215be4 | ||
|
|
44b1a5b9fc | ||
|
|
8497a2bc6d | ||
|
|
5b93ae16fb | ||
|
|
873a1343ad | ||
|
|
c119f3d825 | ||
|
|
af22392769 | ||
|
|
eaa7c9d39e | ||
|
|
8ba12c7cca | ||
|
|
da030e6219 | ||
|
|
600ffd1443 | ||
|
|
96f96256e4 | ||
|
|
5d06b4a01d | ||
|
|
bdd1d8b24f | ||
|
|
d24dea81e2 | ||
|
|
a891adc0b5 | ||
|
|
d6682d8de1 | ||
|
|
302aafff45 | ||
|
|
6cde9a357c | ||
|
|
6c48366709 | ||
|
|
fd29736b7b | ||
|
|
403994bf5e | ||
|
|
16779f5003 | ||
|
|
b81c335ec4 | ||
|
|
c578233e6a | ||
|
|
84d0b7a01d | ||
|
|
73b955c9f1 | ||
|
|
cb45938109 | ||
|
|
48b3b452c1 | ||
|
|
2c04a64ddb | ||
|
|
69478a6273 | ||
|
|
9d441c4669 | ||
|
|
76638c2595 | ||
|
|
b7109579af | ||
|
|
5517c88893 | ||
|
|
0a45d5bc24 | ||
|
|
1b3d254e70 | ||
|
|
86a85e8401 | ||
|
|
1c14b8d632 | ||
|
|
964a57462e |
28
.github/workflows/diff.yaml
vendored
28
.github/workflows/diff.yaml
vendored
@@ -338,7 +338,7 @@ jobs:
|
||||
build/logs
|
||||
|
||||
experimental_build:
|
||||
name: "MultiTenancy replication build"
|
||||
name: "High availability build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
@@ -353,45 +353,27 @@ jobs:
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build MT replication experimental binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -D MG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_HIGH_AVAILABILITY=ON ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
cd tests
|
||||
./setup.sh /opt/toolchain-v4/activate
|
||||
source ve3/bin/activate_e2e
|
||||
cd e2e
|
||||
|
||||
# Just the replication based e2e tests
|
||||
./run.sh "Replicate multitenancy"
|
||||
./run.sh "Show"
|
||||
./run.sh "Show while creating invalid state"
|
||||
./run.sh "Delete edge replication"
|
||||
./run.sh "Read-write benchmark"
|
||||
./run.sh "Index replication"
|
||||
./run.sh "Constraints"
|
||||
|
||||
./run.sh "Coordinator"
|
||||
./run.sh "Client initiated failover"
|
||||
./run.sh "Uninitialized cluster"
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
|
||||
15
.github/workflows/release_debian10.yaml
vendored
15
.github/workflows/release_debian10.yaml
vendored
@@ -281,6 +281,13 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
cd ../pulsar
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
cd tests
|
||||
@@ -289,6 +296,14 @@ jobs:
|
||||
cd e2e
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: always()
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
|
||||
15
.github/workflows/release_ubuntu2004.yaml
vendored
15
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -281,6 +281,13 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Ensure Kafka and Pulsar are up
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose up -d
|
||||
cd ../pulsar
|
||||
docker-compose up -d
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
cd tests
|
||||
@@ -289,6 +296,14 @@ jobs:
|
||||
cd e2e
|
||||
./run.sh
|
||||
|
||||
- name: Ensure Kafka and Pulsar are down
|
||||
if: always()
|
||||
run: |
|
||||
cd tests/e2e/streams/kafka
|
||||
docker-compose down
|
||||
cd ../pulsar
|
||||
docker-compose down
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
|
||||
@@ -271,6 +271,17 @@ endif()
|
||||
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
||||
add_subdirectory(libs EXCLUDE_FROM_ALL)
|
||||
|
||||
option(MG_EXPERIMENTAL_HIGH_AVAILABILITY "Feature flag for experimental high availability" OFF)
|
||||
|
||||
if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||
set(MG_EXPERIMENTAL_HIGH_AVAILABILITY OFF)
|
||||
message(FATAL_ERROR "MG_EXPERIMENTAL_HIGH_AVAILABILITY must be used with enterpise version of the code.")
|
||||
endif ()
|
||||
|
||||
if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||
add_compile_definitions(MG_EXPERIMENTAL_HIGH_AVAILABILITY)
|
||||
endif ()
|
||||
|
||||
# Optional subproject configuration -------------------------------------------
|
||||
option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF)
|
||||
option(TOOLS "Build tools binaries" ON)
|
||||
@@ -279,18 +290,6 @@ option(ASAN "Build with Address Sanitizer. To get a reasonable performance optio
|
||||
option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF)
|
||||
option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF)
|
||||
|
||||
# Build feature flags
|
||||
option(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY "Feature flag for experimental replicaition of multitenacy" OFF)
|
||||
|
||||
if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
set(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY OFF)
|
||||
message(FATAL_ERROR "MG_EXPERIMENTAL_REPLICATION_MULTITENANCY with community edition build isn't possible")
|
||||
endif ()
|
||||
|
||||
if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
add_compile_definitions(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY)
|
||||
endif ()
|
||||
|
||||
if (TEST_COVERAGE)
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
if (NOT lower_build_type STREQUAL "debug")
|
||||
|
||||
@@ -22,6 +22,8 @@ add_subdirectory(dbms)
|
||||
add_subdirectory(flags)
|
||||
add_subdirectory(distributed)
|
||||
add_subdirectory(replication)
|
||||
add_subdirectory(coordination)
|
||||
add_subdirectory(replication_coordination_glue)
|
||||
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -195,7 +195,6 @@ void Auth::SaveUser(const User &user) {
|
||||
if (!success) {
|
||||
throw AuthException("Couldn't save user '{}'!", user.username());
|
||||
}
|
||||
// TODO system delta
|
||||
}
|
||||
|
||||
std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password) {
|
||||
@@ -216,7 +215,6 @@ bool Auth::RemoveUser(const std::string &username_orig) {
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove user '{}'!", username);
|
||||
}
|
||||
// TODO system delta
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -254,12 +252,13 @@ void Auth::SaveRole(const Role &role) {
|
||||
if (!storage_.Put(kRolePrefix + role.rolename(), role.Serialize().dump())) {
|
||||
throw AuthException("Couldn't save role '{}'!", role.rolename());
|
||||
}
|
||||
// TODO system delta
|
||||
}
|
||||
|
||||
std::optional<Role> Auth::AddRole(const std::string &rolename) {
|
||||
if (auto existing_role = GetRole(rolename)) return std::nullopt;
|
||||
if (auto existing_user = GetUser(rolename)) return std::nullopt;
|
||||
auto existing_role = GetRole(rolename);
|
||||
if (existing_role) return std::nullopt;
|
||||
auto existing_user = GetUser(rolename);
|
||||
if (existing_user) return std::nullopt;
|
||||
auto new_role = Role(rolename);
|
||||
SaveRole(new_role);
|
||||
return new_role;
|
||||
@@ -278,7 +277,6 @@ bool Auth::RemoveRole(const std::string &rolename_orig) {
|
||||
if (!storage_.DeleteMultiple(keys)) {
|
||||
throw AuthException("Couldn't remove role '{}'!", rolename);
|
||||
}
|
||||
// TODO system delta
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -287,7 +285,8 @@ std::vector<auth::Role> Auth::AllRoles() const {
|
||||
for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) {
|
||||
auto rolename = it->first.substr(kRolePrefix.size());
|
||||
if (rolename != utils::ToLowerCase(rolename)) continue;
|
||||
if (auto role = GetRole(rolename)) {
|
||||
auto role = GetRole(rolename);
|
||||
if (role) {
|
||||
ret.push_back(*role);
|
||||
} else {
|
||||
throw AuthException("Couldn't load role '{}'!", rolename);
|
||||
@@ -297,14 +296,15 @@ std::vector<auth::Role> Auth::AllRoles() const {
|
||||
}
|
||||
|
||||
std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const {
|
||||
const auto rolename = utils::ToLowerCase(rolename_orig);
|
||||
auto rolename = utils::ToLowerCase(rolename_orig);
|
||||
std::vector<auth::User> ret;
|
||||
for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix); ++it) {
|
||||
auto username = it->first.substr(kLinkPrefix.size());
|
||||
if (username != utils::ToLowerCase(username)) continue;
|
||||
if (it->second != utils::ToLowerCase(it->second)) continue;
|
||||
if (it->second == rolename) {
|
||||
if (auto user = GetUser(username)) {
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(std::move(*user));
|
||||
} else {
|
||||
throw AuthException("Couldn't load user '{}'!", username);
|
||||
@@ -316,7 +316,8 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
auto user = GetUser(name);
|
||||
if (user) {
|
||||
if (db == kAllDatabases) {
|
||||
user->db_access().GrantAll();
|
||||
} else {
|
||||
@@ -329,7 +330,8 @@ bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
|
||||
}
|
||||
|
||||
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
auto user = GetUser(name);
|
||||
if (user) {
|
||||
if (db == kAllDatabases) {
|
||||
user->db_access().DenyAll();
|
||||
} else {
|
||||
@@ -344,15 +346,17 @@ bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name
|
||||
void Auth::DeleteDatabase(const std::string &db) {
|
||||
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
|
||||
auto username = it->first.substr(kUserPrefix.size());
|
||||
if (auto user = GetUser(username)) {
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
user->db_access().Delete(db);
|
||||
SaveUser(*user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Auth::SetMainDatabase(std::string_view db, const std::string &name) {
|
||||
if (auto user = GetUser(name)) {
|
||||
bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
|
||||
auto user = GetUser(name);
|
||||
if (user) {
|
||||
if (!user->db_access().SetDefault(db)) {
|
||||
throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ class Auth final {
|
||||
* @return true on success
|
||||
* @throw AuthException if unable to find or update the user
|
||||
*/
|
||||
bool SetMainDatabase(std::string_view db, const std::string &name);
|
||||
bool SetMainDatabase(const std::string &db, const std::string &name);
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -62,7 +62,8 @@ const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
|
||||
Permission::TRANSACTION_MANAGEMENT,
|
||||
Permission::STORAGE_MODE,
|
||||
Permission::MULTI_DATABASE_EDIT,
|
||||
Permission::MULTI_DATABASE_USE};
|
||||
Permission::MULTI_DATABASE_USE,
|
||||
Permission::COORDINATOR};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -118,6 +119,8 @@ std::string PermissionToString(Permission permission) {
|
||||
return "MULTI_DATABASE_EDIT";
|
||||
case Permission::MULTI_DATABASE_USE:
|
||||
return "MULTI_DATABASE_USE";
|
||||
case Permission::COORDINATOR:
|
||||
return "COORDINATOR";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,13 +489,13 @@ bool operator==(const Role &first, const Role &second) {
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
void Databases::Add(std::string_view db) {
|
||||
void Databases::Add(const std::string &db) {
|
||||
if (allow_all_) {
|
||||
grants_dbs_.clear();
|
||||
allow_all_ = false;
|
||||
}
|
||||
grants_dbs_.emplace(db);
|
||||
denies_dbs_.erase(std::string{db}); // TODO: C++23 use transparent key compare
|
||||
denies_dbs_.erase(db);
|
||||
}
|
||||
|
||||
void Databases::Remove(const std::string &db) {
|
||||
@@ -523,13 +526,13 @@ void Databases::DenyAll() {
|
||||
denies_dbs_.clear();
|
||||
}
|
||||
|
||||
bool Databases::SetDefault(std::string_view db) {
|
||||
bool Databases::SetDefault(const std::string &db) {
|
||||
if (!Contains(db)) return false;
|
||||
default_db_ = db;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Databases::Contains(std::string_view db) const {
|
||||
[[nodiscard]] bool Databases::Contains(const std::string &db) const {
|
||||
return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
//
|
||||
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
|
||||
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
|
||||
@@ -48,6 +48,7 @@ enum class Permission : uint64_t {
|
||||
STORAGE_MODE = 1U << 22U,
|
||||
MULTI_DATABASE_EDIT = 1U << 23U,
|
||||
MULTI_DATABASE_USE = 1U << 24U,
|
||||
COORDINATOR = 1U << 25U,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -246,7 +247,7 @@ bool operator==(const Role &first, const Role &second);
|
||||
#ifdef MG_ENTERPRISE
|
||||
class Databases final {
|
||||
public:
|
||||
Databases() : grants_dbs_{std::string{dbms::kDefaultDB}}, allow_all_(false), default_db_(dbms::kDefaultDB) {}
|
||||
Databases() : grants_dbs_({dbms::kDefaultDB}), allow_all_(false), default_db_(dbms::kDefaultDB) {}
|
||||
|
||||
Databases(const Databases &) = default;
|
||||
Databases &operator=(const Databases &) = default;
|
||||
@@ -259,7 +260,7 @@ class Databases final {
|
||||
*
|
||||
* @param db name of the database to grant access to
|
||||
*/
|
||||
void Add(std::string_view db);
|
||||
void Add(const std::string &db);
|
||||
|
||||
/**
|
||||
* @brief Remove database to the list of granted access.
|
||||
@@ -291,7 +292,7 @@ class Databases final {
|
||||
/**
|
||||
* @brief Set the default database.
|
||||
*/
|
||||
bool SetDefault(std::string_view db);
|
||||
bool SetDefault(const std::string &db);
|
||||
|
||||
/**
|
||||
* @brief Checks if access is grated to the database.
|
||||
@@ -299,7 +300,7 @@ class Databases final {
|
||||
* @param db name of the database
|
||||
* @return true if allow_all and not denied or granted
|
||||
*/
|
||||
bool Contains(std::string_view db) const;
|
||||
bool Contains(const std::string &db) const;
|
||||
|
||||
bool GetAllowAll() const { return allow_all_; }
|
||||
const std::set<std::string, std::less<>> &GetGrants() const { return grants_dbs_; }
|
||||
@@ -312,7 +313,7 @@ class Databases final {
|
||||
|
||||
private:
|
||||
Databases(bool allow_all, std::set<std::string, std::less<>> grant, std::set<std::string, std::less<>> deny,
|
||||
std::string default_db = std::string{dbms::kDefaultDB})
|
||||
std::string default_db = dbms::kDefaultDB)
|
||||
: grants_dbs_(std::move(grant)),
|
||||
denies_dbs_(std::move(deny)),
|
||||
allow_all_(allow_all),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -170,7 +170,6 @@ inline State HandleFailure(TSession &session, const std::exception &e) {
|
||||
spdlog::trace("Error trace: {}", p->trace());
|
||||
}
|
||||
session.encoder_buffer_.Clear();
|
||||
|
||||
auto code_message = ExceptionToErrorMessage(e);
|
||||
bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
|
||||
if (!fail_sent) {
|
||||
|
||||
@@ -44,7 +44,7 @@ class ResultStreamFaker {
|
||||
std::vector<memgraph::communication::bolt::Value> bvalues;
|
||||
bvalues.reserve(values.size());
|
||||
for (const auto &value : values) {
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(value, store_, memgraph::storage::View::NEW);
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(value, *store_, memgraph::storage::View::NEW);
|
||||
MG_ASSERT(maybe_value.HasValue());
|
||||
bvalues.push_back(std::move(*maybe_value));
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class ResultStreamFaker {
|
||||
void Summary(const std::map<std::string, memgraph::query::TypedValue> &summary) {
|
||||
std::map<std::string, memgraph::communication::bolt::Value> bsummary;
|
||||
for (const auto &item : summary) {
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(item.second, store_, memgraph::storage::View::NEW);
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(item.second, *store_, memgraph::storage::View::NEW);
|
||||
MG_ASSERT(maybe_value.HasValue());
|
||||
bsummary.insert({item.first, std::move(*maybe_value)});
|
||||
}
|
||||
|
||||
28
src/coordination/CMakeLists.txt
Normal file
28
src/coordination/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
add_library(mg-coordination STATIC)
|
||||
add_library(mg::coordination ALIAS mg-coordination)
|
||||
target_sources(mg-coordination
|
||||
PUBLIC
|
||||
include/coordination/coordinator_client.hpp
|
||||
include/coordination/coordinator_state.hpp
|
||||
include/coordination/coordinator_rpc.hpp
|
||||
include/coordination/coordinator_server.hpp
|
||||
include/coordination/coordinator_config.hpp
|
||||
include/coordination/coordinator_exceptions.hpp
|
||||
include/coordination/coordinator_slk.hpp
|
||||
include/coordination/coordinator_data.hpp
|
||||
include/coordination/constants.hpp
|
||||
include/coordination/failover_status.hpp
|
||||
include/coordination/coordinator_client_info.hpp
|
||||
include/coordination/coordinator_cluster_config.hpp
|
||||
|
||||
PRIVATE
|
||||
coordinator_client.cpp
|
||||
coordinator_state.cpp
|
||||
coordinator_rpc.cpp
|
||||
coordinator_server.cpp
|
||||
)
|
||||
target_include_directories(mg-coordination PUBLIC include)
|
||||
|
||||
target_link_libraries(mg-coordination
|
||||
PUBLIC mg::utils mg::rpc mg::slk mg::io mg::repl_coord_glue
|
||||
)
|
||||
126
src/coordination/coordinator_client.cpp
Normal file
126
src/coordination/coordinator_client.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "coordination/coordinator_rpc.hpp"
|
||||
#include "replication_coordination_glue/messages.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace {
|
||||
auto CreateClientContext(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> communication::ClientContext {
|
||||
return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file}
|
||||
: communication::ClientContext{};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CoordinatorClient::CoordinatorClient(CoordinatorState *coord_state, CoordinatorClientConfig config,
|
||||
HealthCheckCallback succ_cb, HealthCheckCallback fail_cb)
|
||||
: rpc_context_{CreateClientContext(config)},
|
||||
rpc_client_{io::network::Endpoint(io::network::Endpoint::needs_resolving, config.ip_address, config.port),
|
||||
&rpc_context_},
|
||||
config_{std::move(config)},
|
||||
coord_state_{coord_state},
|
||||
succ_cb_{std::move(succ_cb)},
|
||||
fail_cb_{std::move(fail_cb)} {}
|
||||
|
||||
CoordinatorClient::~CoordinatorClient() {
|
||||
auto exit_job = utils::OnScopeExit([&] {
|
||||
replica_checker_.Stop();
|
||||
thread_pool_.Shutdown();
|
||||
});
|
||||
const auto endpoint = rpc_client_.Endpoint();
|
||||
// Logging can throw
|
||||
spdlog::trace("Closing coordinator client on {}:{}", endpoint.address, endpoint.port);
|
||||
}
|
||||
|
||||
void CoordinatorClient::StartFrequentCheck() {
|
||||
MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0),
|
||||
"Health check frequency must be greater than 0");
|
||||
|
||||
std::string_view instance_name = config_.instance_name;
|
||||
replica_checker_.Run("Coord checker", config_.health_check_frequency_sec, [this, instance_name] {
|
||||
try {
|
||||
spdlog::trace("Sending frequent heartbeat to machine {} on {}:{}", instance_name, rpc_client_.Endpoint().address,
|
||||
rpc_client_.Endpoint().port);
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
if (stream.AwaitResponse().success) {
|
||||
succ_cb_(coord_state_, instance_name);
|
||||
} else {
|
||||
fail_cb_(coord_state_, instance_name);
|
||||
}
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
fail_cb_(coord_state_, instance_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorClient::StopFrequentCheck() { replica_checker_.Stop(); }
|
||||
|
||||
void CoordinatorClient::PauseFrequentCheck() { replica_checker_.Pause(); }
|
||||
void CoordinatorClient::ResumeFrequentCheck() { replica_checker_.Resume(); }
|
||||
|
||||
auto CoordinatorClient::InstanceName() const -> std::string_view { return config_.instance_name; }
|
||||
auto CoordinatorClient::SocketAddress() const -> std::string { return rpc_client_.Endpoint().SocketAddress(); }
|
||||
auto CoordinatorClient::Config() const -> CoordinatorClientConfig const & { return config_; }
|
||||
auto CoordinatorClient::SuccCallback() const -> HealthCheckCallback const & { return succ_cb_; }
|
||||
auto CoordinatorClient::FailCallback() const -> HealthCheckCallback const & { return fail_cb_; }
|
||||
|
||||
////// AF design choice
|
||||
auto CoordinatorClient::ReplicationClientInfo() const -> CoordinatorClientConfig::ReplicationClientInfo const & {
|
||||
MG_ASSERT(config_.replication_client_info.has_value(), "No ReplicationClientInfo for MAIN instance!");
|
||||
return *config_.replication_client_info;
|
||||
}
|
||||
|
||||
////// AF design choice
|
||||
auto CoordinatorClient::ReplicationClientInfo() -> std::optional<CoordinatorClientConfig::ReplicationClientInfo> & {
|
||||
MG_ASSERT(config_.replication_client_info.has_value(), "No ReplicationClientInfo for MAIN instance!");
|
||||
return config_.replication_client_info;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::SendPromoteReplicaToMainRpc(
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<PromoteReplicaToMainRpc>(std::move(replication_clients_info))};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to perform failover!");
|
||||
return false;
|
||||
}
|
||||
spdlog::info("Sent failover RPC from coordinator to new main!");
|
||||
return true;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error("Failed to send failover RPC from coordinator to new main!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::SendSetToReplicaRpc(CoordinatorClient::ReplClientInfo replication_client_info) const -> bool {
|
||||
try {
|
||||
auto stream{rpc_client_.Stream<SetMainToReplicaRpc>(std::move(replication_client_info))};
|
||||
if (!stream.AwaitResponse().success) {
|
||||
spdlog::error("Failed to set main to replica!");
|
||||
return false;
|
||||
}
|
||||
spdlog::info("Sent request RPC from coordinator to instance to set it as replica!");
|
||||
return true;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error("Failed to send failover RPC from coordinator to new main!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
107
src/coordination/coordinator_rpc.cpp
Normal file
107
src/coordination/coordinator_rpc.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_rpc.hpp"
|
||||
|
||||
#include "coordination/coordinator_slk.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
namespace memgraph {
|
||||
|
||||
namespace coordination {
|
||||
|
||||
void PromoteReplicaToMainReq::Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void PromoteReplicaToMainReq::Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void PromoteReplicaToMainRes::Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void PromoteReplicaToMainRes::Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void SetMainToReplicaReq::Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void SetMainToReplicaReq::Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void SetMainToReplicaRes::Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
|
||||
void SetMainToReplicaRes::Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
} // namespace coordination
|
||||
|
||||
constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::TypeId::COORD_FAILOVER_REQ,
|
||||
"CoordPromoteReplicaToMainReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::PromoteReplicaToMainRes::kType{utils::TypeId::COORD_FAILOVER_RES,
|
||||
"CoordPromoteReplicaToMainRes", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::SetMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ,
|
||||
"CoordSetReplMainReq", nullptr};
|
||||
|
||||
constexpr utils::TypeInfo coordination::SetMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES,
|
||||
"CoordSetReplMainRes", nullptr};
|
||||
|
||||
namespace slk {
|
||||
|
||||
void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.replication_clients_info, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->replication_clients_info, reader);
|
||||
}
|
||||
|
||||
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.replication_client_info, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->replication_client_info, reader);
|
||||
}
|
||||
|
||||
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.success, builder);
|
||||
}
|
||||
|
||||
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->success, reader);
|
||||
}
|
||||
|
||||
} // namespace slk
|
||||
|
||||
} // namespace memgraph
|
||||
|
||||
#endif
|
||||
57
src/coordination/coordinator_server.cpp
Normal file
57
src/coordination/coordinator_server.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "replication_coordination_glue/messages.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace {
|
||||
|
||||
auto CreateServerContext(const memgraph::coordination::CoordinatorServerConfig &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 coordinator server doesn't more than 1 processing thread - each replica can
|
||||
// have only a single coordinator server. Also, the single-threaded guarantee
|
||||
// simplifies the rest of the implementation.
|
||||
constexpr auto kCoordinatorServerThreads = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
CoordinatorServer::CoordinatorServer(const CoordinatorServerConfig &config)
|
||||
: rpc_server_context_{CreateServerContext(config)},
|
||||
rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
|
||||
kCoordinatorServerThreads} {
|
||||
rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received FrequentHeartbeatRpc on coordinator server");
|
||||
replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
CoordinatorServer::~CoordinatorServer() {
|
||||
if (rpc_server_.IsRunning()) {
|
||||
auto const &endpoint = rpc_server_.endpoint();
|
||||
spdlog::trace("Closing coordinator server on {}:{}", endpoint.address, endpoint.port);
|
||||
rpc_server_.Shutdown();
|
||||
}
|
||||
rpc_server_.AwaitShutdown();
|
||||
}
|
||||
|
||||
bool CoordinatorServer::Start() { return rpc_server_.Start(); }
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
474
src/coordination/coordinator_state.cpp
Normal file
474
src/coordination/coordinator_state.cpp
Normal file
@@ -0,0 +1,474 @@
|
||||
// Copyright 2024 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 "coordination/coordinator_state.hpp"
|
||||
#include <algorithm>
|
||||
#include "coordination/coordinator_client_info.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
#include "coordination/coordinator_cluster_config.hpp"
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace {
|
||||
|
||||
bool CheckName(const std::list<CoordinatorClient> &replicas, const CoordinatorClientConfig &config) {
|
||||
auto name_matches = [&instance_name = config.instance_name](auto const &replica) {
|
||||
return replica.InstanceName() == instance_name;
|
||||
};
|
||||
return std::any_of(replicas.begin(), replicas.end(), name_matches);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CoordinatorState::CoordinatorState() {
|
||||
MG_ASSERT(!(FLAGS_coordinator && FLAGS_coordinator_server_port),
|
||||
"Instance cannot be a coordinator and have registered coordinator server.");
|
||||
|
||||
spdlog::info("Executing coordinator constructor");
|
||||
if (FLAGS_coordinator_server_port) {
|
||||
spdlog::info("Coordinator server port set");
|
||||
auto const config = CoordinatorServerConfig{
|
||||
.ip_address = kDefaultReplicationServerIp,
|
||||
.port = static_cast<uint16_t>(FLAGS_coordinator_server_port),
|
||||
};
|
||||
spdlog::info("Executing coordinator constructor main replica");
|
||||
|
||||
data_ = CoordinatorMainReplicaData{.coordinator_server_ = std::make_unique<CoordinatorServer>(config)};
|
||||
}
|
||||
}
|
||||
|
||||
auto CoordinatorState::RegisterInstanceOnCoordinator(CoordinatorClientConfig config)
|
||||
-> RegisterInstanceCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||
|
||||
const auto name_endpoint_status =
|
||||
std::visit(memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return RegisterInstanceCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[&config](const CoordinatorData &coordinator_data) {
|
||||
if (memgraph::coordination::CheckName(
|
||||
coordinator_data.registered_replicas_, config)) {
|
||||
return RegisterInstanceCoordinatorStatus::NAME_EXISTS;
|
||||
}
|
||||
return RegisterInstanceCoordinatorStatus::SUCCESS;
|
||||
}},
|
||||
data_);
|
||||
|
||||
if (name_endpoint_status != RegisterInstanceCoordinatorStatus::SUCCESS) {
|
||||
return name_endpoint_status;
|
||||
}
|
||||
|
||||
auto find_client_info = [](CoordinatorState *coord_state, std::string_view instance_name) -> CoordinatorClientInfo & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(coord_state->data_),
|
||||
"Can't execute CoordinatorClient's callback since variant holds wrong alternative");
|
||||
auto &coord_data = std::get<CoordinatorData>(coord_state->data_);
|
||||
std::shared_lock<utils::RWLock> lock{coord_data.coord_data_lock_};
|
||||
|
||||
auto replica_client_info = std::ranges::find_if(
|
||||
coord_data.registered_replicas_info_,
|
||||
[instance_name](const CoordinatorClientInfo &replica) { return replica.InstanceName() == instance_name; });
|
||||
|
||||
if (replica_client_info != coord_data.registered_replicas_info_.end()) {
|
||||
return *replica_client_info;
|
||||
}
|
||||
|
||||
MG_ASSERT(coord_data.registered_main_info_->InstanceName() == instance_name,
|
||||
"Instance is neither a replica nor main...");
|
||||
return *coord_data.registered_main_info_;
|
||||
};
|
||||
|
||||
// TODO MERGE WITH ANDI's WORK
|
||||
auto repl_succ_cb = [find_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto &client_info = find_client_info(coord_state, instance_name);
|
||||
client_info.UpdateLastResponseTime();
|
||||
};
|
||||
|
||||
auto repl_fail_cb = [find_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto &client_info = find_client_info(coord_state, instance_name);
|
||||
client_info.UpdateInstanceStatus();
|
||||
};
|
||||
CoordinatorClientConfig::ReplicationClientInfo replication_client_info = *config.replication_client_info;
|
||||
auto *coord_client = &std::get<CoordinatorData>(data_).registered_replicas_.emplace_back(
|
||||
this, std::move(config), std::move(repl_succ_cb), std::move(repl_fail_cb));
|
||||
|
||||
coord_client->SendSetToReplicaRpc(replication_client_info);
|
||||
|
||||
std::get<CoordinatorData>(data_).registered_replicas_info_.emplace_back(coord_client->InstanceName(),
|
||||
coord_client->SocketAddress());
|
||||
coord_client->StartFrequentCheck();
|
||||
|
||||
return RegisterInstanceCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorState::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||
|
||||
// TODO: (andi) How does the situation change when restoration of main is implemented regarding callbacks?
|
||||
// We should probably at that point search for main also in instances as for replicas...
|
||||
auto get_client_info = [](CoordinatorState *coord_state, std::string_view instance_name) -> CoordinatorClientInfo & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(coord_state->data_),
|
||||
"Can't execute CoordinatorClient's callback since variant holds wrong alternative");
|
||||
MG_ASSERT(std::get<CoordinatorData>(coord_state->data_).registered_main_info_.has_value(),
|
||||
"Main info is not set, but callback is called");
|
||||
auto &coord_data = std::get<CoordinatorData>(coord_state->data_);
|
||||
std::shared_lock<utils::RWLock> lock{coord_data.coord_data_lock_};
|
||||
|
||||
// TODO When we will support restoration of main, we have to assert that the instance is main or replica, not at
|
||||
// this point....
|
||||
auto ®istered_main_info = coord_data.registered_main_info_;
|
||||
MG_ASSERT(registered_main_info->InstanceName() == instance_name,
|
||||
"Callback called for wrong instance name: {}, expected: {}", instance_name,
|
||||
registered_main_info->InstanceName());
|
||||
return *registered_main_info;
|
||||
};
|
||||
|
||||
auto succ_cb = [get_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto ®istered_main_info = get_client_info(coord_state, instance_name);
|
||||
registered_main_info.UpdateLastResponseTime();
|
||||
};
|
||||
|
||||
auto fail_cb = [this, get_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto ®istered_main_info = get_client_info(coord_state, instance_name);
|
||||
if (bool main_alive = registered_main_info.UpdateInstanceStatus(); !main_alive) {
|
||||
spdlog::warn("Main is not alive, starting failover");
|
||||
switch (auto failover_status = DoFailover(); failover_status) {
|
||||
using enum DoFailoverStatus;
|
||||
case ALL_REPLICAS_DOWN:
|
||||
spdlog::warn("Failover aborted since all replicas are down!");
|
||||
case MAIN_ALIVE:
|
||||
spdlog::warn("Failover aborted since main is alive!");
|
||||
case CLUSTER_UNINITIALIZED:
|
||||
spdlog::warn("Failover aborted since cluster is uninitialized!");
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto ®istered_replicas = std::get<CoordinatorData>(data_).registered_replicas_;
|
||||
// Find replica we already registered
|
||||
auto registered_replica =
|
||||
std::find_if(registered_replicas.begin(), registered_replicas.end(), [instance_name](const auto &replica_client) {
|
||||
std::cout << "replica name: " << replica_client.InstanceName() << ", instance name: " << instance_name
|
||||
<< std::endl;
|
||||
return replica_client.InstanceName() == instance_name;
|
||||
});
|
||||
|
||||
std::for_each(registered_replicas.begin(), registered_replicas.end(),
|
||||
[](const auto &client) { std::cout << "replica names: " << client.InstanceName() << std::endl; });
|
||||
// if replica not found...
|
||||
if (registered_replica == registered_replicas.end()) {
|
||||
spdlog::error("You didn't register instance with given name {}", instance_name);
|
||||
return SetInstanceToMainCoordinatorStatus::NO_INSTANCE_WITH_NAME;
|
||||
}
|
||||
registered_replica->StopFrequentCheck();
|
||||
// Set instance as MAIN
|
||||
// THIS WILL SHUT DOWN CLIENT
|
||||
auto ®istered_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
registered_main =
|
||||
std::make_unique<CoordinatorClient>(this, registered_replica->Config(), std::move(succ_cb), std::move(fail_cb));
|
||||
|
||||
std::get<CoordinatorData>(data_).registered_main_info_.emplace(registered_main->InstanceName(),
|
||||
registered_main->SocketAddress());
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(registered_replicas.size() - 1);
|
||||
std::ranges::for_each(registered_replicas,
|
||||
[registered_replica, &repl_clients_info](const CoordinatorClient &replica) {
|
||||
if (replica != *registered_replica) {
|
||||
repl_clients_info.emplace_back(replica.ReplicationClientInfo());
|
||||
}
|
||||
});
|
||||
|
||||
// PROMOTE REPLICA TO MAIN
|
||||
// THIS SHOULD FAIL HERE IF IT IS DOWN
|
||||
if (auto result = registered_main->SendPromoteReplicaToMainRpc(std::move(repl_clients_info)); !result) {
|
||||
registered_replica->StartFrequentCheck();
|
||||
registered_main.reset();
|
||||
return SetInstanceToMainCoordinatorStatus::COULD_NOT_PROMOTE_TO_MAIN;
|
||||
}
|
||||
|
||||
registered_main->StartFrequentCheck();
|
||||
registered_replicas.erase(registered_replica);
|
||||
return SetInstanceToMainCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorState::RegisterReplica(CoordinatorClientConfig config) -> RegisterMainReplicaCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Coordinator cannot register replica since variant holds wrong alternative");
|
||||
|
||||
const auto name_endpoint_status =
|
||||
std::visit(memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[&config](const CoordinatorData &coordinator_data) {
|
||||
if (memgraph::coordination::CheckName(
|
||||
coordinator_data.registered_replicas_, config)) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NAME_EXISTS;
|
||||
}
|
||||
return RegisterMainReplicaCoordinatorStatus::SUCCESS;
|
||||
}},
|
||||
data_);
|
||||
|
||||
if (name_endpoint_status != RegisterMainReplicaCoordinatorStatus::SUCCESS) {
|
||||
return name_endpoint_status;
|
||||
}
|
||||
|
||||
// TODO: Refactor so that we just know about instances, nothing more. Design struct Instance which will know about
|
||||
// role... From CoordinatorState perspective there should just be instances, no specific handling of main vs. replica.
|
||||
// Here after failover it can happen that replica
|
||||
// has become main MG_ASSERT(replica_client_info != registered_replicas_info.end(), "Replica {} not found in
|
||||
// registered replicas info",
|
||||
// instance_name);
|
||||
auto find_client_info = [](CoordinatorState *coord_state, std::string_view instance_name) -> CoordinatorClientInfo & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(coord_state->data_),
|
||||
"Can't execute CoordinatorClient's callback since variant holds wrong alternative");
|
||||
auto &coord_data = std::get<CoordinatorData>(coord_state->data_);
|
||||
std::shared_lock<utils::RWLock> lock{coord_data.coord_data_lock_};
|
||||
|
||||
auto replica_client_info = std::ranges::find_if(
|
||||
coord_data.registered_replicas_info_,
|
||||
[instance_name](const CoordinatorClientInfo &replica) { return replica.InstanceName() == instance_name; });
|
||||
|
||||
if (replica_client_info != coord_data.registered_replicas_info_.end()) {
|
||||
return *replica_client_info;
|
||||
}
|
||||
|
||||
MG_ASSERT(coord_data.registered_main_info_->InstanceName() == instance_name,
|
||||
"Instance is neither a replica nor main...");
|
||||
return *coord_data.registered_main_info_;
|
||||
};
|
||||
|
||||
auto repl_succ_cb = [find_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto &client_info = find_client_info(coord_state, instance_name);
|
||||
client_info.UpdateLastResponseTime();
|
||||
};
|
||||
|
||||
auto repl_fail_cb = [find_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto &client_info = find_client_info(coord_state, instance_name);
|
||||
client_info.UpdateInstanceStatus();
|
||||
};
|
||||
|
||||
auto *coord_client = &std::get<CoordinatorData>(data_).registered_replicas_.emplace_back(
|
||||
this, std::move(config), std::move(repl_succ_cb), std::move(repl_fail_cb));
|
||||
|
||||
std::get<CoordinatorData>(data_).registered_replicas_info_.emplace_back(coord_client->InstanceName(),
|
||||
coord_client->SocketAddress());
|
||||
coord_client->StartFrequentCheck();
|
||||
|
||||
return RegisterMainReplicaCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorState::RegisterMain(CoordinatorClientConfig config) -> RegisterMainReplicaCoordinatorStatus {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Coordinator cannot register main since variant holds wrong alternative");
|
||||
|
||||
const auto endpoint_status = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[](const CoordinatorData & /*coordinator_data*/) { return RegisterMainReplicaCoordinatorStatus::SUCCESS; }},
|
||||
data_);
|
||||
|
||||
if (endpoint_status != RegisterMainReplicaCoordinatorStatus::SUCCESS) {
|
||||
return endpoint_status;
|
||||
}
|
||||
|
||||
// TODO: (andi) How does the situation change when restoration of main is implemented regarding callbacks?
|
||||
// We should probably at that point search for main also in instances as for replicas...
|
||||
auto get_client_info = [](CoordinatorState *coord_state, std::string_view instance_name) -> CoordinatorClientInfo & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(coord_state->data_),
|
||||
"Can't execute CoordinatorClient's callback since variant holds wrong alternative");
|
||||
MG_ASSERT(std::get<CoordinatorData>(coord_state->data_).registered_main_info_.has_value(),
|
||||
"Main info is not set, but callback is called");
|
||||
auto &coord_data = std::get<CoordinatorData>(coord_state->data_);
|
||||
std::shared_lock<utils::RWLock> lock{coord_data.coord_data_lock_};
|
||||
|
||||
// TODO When we will support restoration of main, we have to assert that the instance is main or replica, not at
|
||||
// this point....
|
||||
auto ®istered_main_info = coord_data.registered_main_info_;
|
||||
MG_ASSERT(registered_main_info->InstanceName() == instance_name,
|
||||
"Callback called for wrong instance name: {}, expected: {}", instance_name,
|
||||
registered_main_info->InstanceName());
|
||||
return *registered_main_info;
|
||||
};
|
||||
|
||||
auto succ_cb = [get_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto ®istered_main_info = get_client_info(coord_state, instance_name);
|
||||
registered_main_info.UpdateLastResponseTime();
|
||||
};
|
||||
|
||||
auto fail_cb = [this, get_client_info](CoordinatorState *coord_state, std::string_view instance_name) -> void {
|
||||
auto ®istered_main_info = get_client_info(coord_state, instance_name);
|
||||
if (bool main_alive = registered_main_info.UpdateInstanceStatus(); !main_alive) {
|
||||
spdlog::warn("Main is not alive, starting failover");
|
||||
switch (auto failover_status = DoFailover(); failover_status) {
|
||||
using enum DoFailoverStatus;
|
||||
case ALL_REPLICAS_DOWN:
|
||||
spdlog::warn("Failover aborted since all replicas are down!");
|
||||
case MAIN_ALIVE:
|
||||
spdlog::warn("Failover aborted since main is alive!");
|
||||
case CLUSTER_UNINITIALIZED:
|
||||
spdlog::warn("Failover aborted since cluster is uninitialized!");
|
||||
case SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto ®istered_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
registered_main =
|
||||
std::make_unique<CoordinatorClient>(this, std::move(config), std::move(succ_cb), std::move(fail_cb));
|
||||
|
||||
std::get<CoordinatorData>(data_).registered_main_info_.emplace(registered_main->InstanceName(),
|
||||
registered_main->SocketAddress());
|
||||
registered_main->StartFrequentCheck();
|
||||
|
||||
return RegisterMainReplicaCoordinatorStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorState::ShowReplicas() const -> std::vector<CoordinatorInstanceStatus> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call show replicas on data_, as variant holds wrong alternative");
|
||||
const auto ®istered_replicas_info = std::get<CoordinatorData>(data_).registered_replicas_info_;
|
||||
std::vector<CoordinatorInstanceStatus> instances_status;
|
||||
instances_status.reserve(registered_replicas_info.size());
|
||||
|
||||
std::ranges::transform(registered_replicas_info, std::back_inserter(instances_status),
|
||||
[](const CoordinatorClientInfo &coord_client_info) {
|
||||
return CoordinatorInstanceStatus{.instance_name = coord_client_info.InstanceName(),
|
||||
.socket_address = coord_client_info.SocketAddress(),
|
||||
.is_alive = coord_client_info.IsAlive()};
|
||||
});
|
||||
return instances_status;
|
||||
}
|
||||
|
||||
auto CoordinatorState::ShowMain() const -> std::optional<CoordinatorInstanceStatus> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call show main on data_, as variant holds wrong alternative");
|
||||
const auto &main = std::get<CoordinatorData>(data_).registered_main_info_;
|
||||
if (!main.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return CoordinatorInstanceStatus{
|
||||
.instance_name = main->InstanceName(), .socket_address = main->SocketAddress(), .is_alive = main->IsAlive()};
|
||||
};
|
||||
|
||||
[[nodiscard]] auto CoordinatorState::DoFailover() -> DoFailoverStatus {
|
||||
// 1. MAIN is already down, stop sending frequent checks
|
||||
// 2. find new replica (coordinator)
|
||||
// 3. make copy replica's client as potential new main client (coordinator)
|
||||
// 4. send failover RPC to new main (coordinator and new main)
|
||||
// 5. exchange old main to new main (coordinator)
|
||||
// 6. remove replica which was promoted to main from all replicas -> this will shut down RPC frequent check client
|
||||
// (coordinator)
|
||||
// 7. for new main start frequent checks (coordinator)
|
||||
using ReplicationClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), "Cannot do failover since variant holds wrong alternative");
|
||||
auto &coord_state = std::get<CoordinatorData>(data_);
|
||||
|
||||
// std::lock_guard<utils::RWLock> lock{coord_state.coord_data_lock_};
|
||||
|
||||
// 1.
|
||||
auto ¤t_main_info = coord_state.registered_main_info_;
|
||||
|
||||
if (!current_main_info.has_value()) {
|
||||
return DoFailoverStatus::CLUSTER_UNINITIALIZED;
|
||||
}
|
||||
|
||||
if (current_main_info->IsAlive()) {
|
||||
return DoFailoverStatus::MAIN_ALIVE;
|
||||
}
|
||||
|
||||
auto ¤t_main = coord_state.registered_main_;
|
||||
// TODO: stop pinging as soon as you figure out that failover is needed
|
||||
current_main->PauseFrequentCheck();
|
||||
|
||||
// 2.
|
||||
// Get all replicas and find new main
|
||||
auto ®istered_replicas_info = coord_state.registered_replicas_info_;
|
||||
|
||||
const auto chosen_replica_info = std::ranges::find_if(
|
||||
registered_replicas_info, [](const CoordinatorClientInfo &client_info) { return client_info.IsAlive(); });
|
||||
if (chosen_replica_info == registered_replicas_info.end()) {
|
||||
return DoFailoverStatus::ALL_REPLICAS_DOWN;
|
||||
}
|
||||
|
||||
auto ®istered_replicas = coord_state.registered_replicas_;
|
||||
auto chosen_replica =
|
||||
std::ranges::find_if(registered_replicas, [&chosen_replica_info](const CoordinatorClient &replica) {
|
||||
return replica.InstanceName() == chosen_replica_info->InstanceName();
|
||||
});
|
||||
MG_ASSERT(chosen_replica != registered_replicas.end(), "Chosen replica {} not found in registered replicas",
|
||||
chosen_replica_info->InstanceName());
|
||||
|
||||
std::vector<ReplicationClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(registered_replicas.size() - 1);
|
||||
std::ranges::for_each(registered_replicas, [&chosen_replica, &repl_clients_info](const CoordinatorClient &replica) {
|
||||
if (replica != *chosen_replica) {
|
||||
repl_clients_info.emplace_back(replica.ReplicationClientInfo());
|
||||
}
|
||||
});
|
||||
|
||||
// 3.
|
||||
// Set on coordinator data of new main
|
||||
// allocate resources for new main, clear replication info on this replica as main
|
||||
// set last response time
|
||||
auto potential_new_main = std::make_unique<CoordinatorClient>(
|
||||
this, chosen_replica->Config(), chosen_replica->SuccCallback(), chosen_replica->FailCallback());
|
||||
potential_new_main->ReplicationClientInfo().reset();
|
||||
auto potential_new_main_info = *chosen_replica_info;
|
||||
|
||||
// 4.
|
||||
if (!chosen_replica->SendPromoteReplicaToMainRpc(std::move(repl_clients_info))) {
|
||||
spdlog::error("Sent RPC message, but exception was caught, aborting Failover");
|
||||
// TODO: new status and rollback all changes that were done...
|
||||
MG_ASSERT(false, "RPC message failed");
|
||||
}
|
||||
|
||||
// 5.
|
||||
current_main = std::move(potential_new_main);
|
||||
current_main_info.emplace(potential_new_main_info);
|
||||
|
||||
// 6. remove old replica
|
||||
// TODO: Stop pinging chosen_replica before failover.
|
||||
// Check that it doesn't fail when you call StopFrequentCheck if it is already stopped
|
||||
registered_replicas.erase(chosen_replica);
|
||||
registered_replicas_info.erase(chosen_replica_info);
|
||||
|
||||
current_main->ResumeFrequentCheck();
|
||||
|
||||
return DoFailoverStatus::SUCCESS;
|
||||
}
|
||||
|
||||
auto CoordinatorState::GetCoordinatorServer() const -> CoordinatorServer & {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorMainReplicaData>(data_),
|
||||
"Cannot get coordinator server since variant holds wrong alternative");
|
||||
return *std::get<CoordinatorMainReplicaData>(data_).coordinator_server_;
|
||||
}
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
22
src/coordination/include/coordination/constants.hpp
Normal file
22
src/coordination/include/coordination/constants.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
#ifdef MG_EXPERIMENTAL_HIGH_AVAILABILITY
|
||||
constexpr bool allow_ha = true;
|
||||
#else
|
||||
constexpr bool allow_ha = false;
|
||||
#endif
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
82
src/coordination/include/coordination/coordinator_client.hpp
Normal file
82
src/coordination/include/coordination/coordinator_client.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "rpc/client.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
class CoordinatorState;
|
||||
|
||||
class CoordinatorClient {
|
||||
public:
|
||||
using ReplClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
|
||||
using ReplicationClientsInfo = std::vector<ReplClientInfo>;
|
||||
|
||||
using HealthCheckCallback = std::function<void(CoordinatorState *, std::string_view)>;
|
||||
|
||||
explicit CoordinatorClient(CoordinatorState *coord_state_, CoordinatorClientConfig config,
|
||||
HealthCheckCallback succ_cb, HealthCheckCallback fail_cb);
|
||||
|
||||
~CoordinatorClient();
|
||||
|
||||
CoordinatorClient(CoordinatorClient &) = delete;
|
||||
CoordinatorClient &operator=(CoordinatorClient const &) = delete;
|
||||
|
||||
CoordinatorClient(CoordinatorClient &&) noexcept = delete;
|
||||
CoordinatorClient &operator=(CoordinatorClient &&) noexcept = delete;
|
||||
|
||||
void StartFrequentCheck();
|
||||
void StopFrequentCheck();
|
||||
void PauseFrequentCheck();
|
||||
void ResumeFrequentCheck();
|
||||
|
||||
auto SendPromoteReplicaToMainRpc(ReplicationClientsInfo replication_clients_info) const -> bool;
|
||||
|
||||
auto SendSetToReplicaRpc(ReplClientInfo replication_client_info) const -> bool;
|
||||
|
||||
auto InstanceName() const -> std::string_view;
|
||||
auto SocketAddress() const -> std::string;
|
||||
auto Config() const -> CoordinatorClientConfig const &;
|
||||
|
||||
auto ReplicationClientInfo() const -> ReplClientInfo const &;
|
||||
auto ReplicationClientInfo() -> std::optional<ReplClientInfo> &;
|
||||
|
||||
auto SuccCallback() const -> HealthCheckCallback const &;
|
||||
auto FailCallback() const -> HealthCheckCallback const &;
|
||||
|
||||
friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) {
|
||||
return first.config_ == second.config_;
|
||||
}
|
||||
|
||||
private:
|
||||
utils::ThreadPool thread_pool_{1};
|
||||
utils::Scheduler replica_checker_;
|
||||
|
||||
communication::ClientContext rpc_context_;
|
||||
mutable rpc::Client rpc_client_;
|
||||
|
||||
CoordinatorClientConfig config_;
|
||||
CoordinatorState *coord_state_;
|
||||
HealthCheckCallback succ_cb_;
|
||||
HealthCheckCallback fail_cb_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_cluster_config.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
class CoordinatorClientInfo {
|
||||
public:
|
||||
CoordinatorClientInfo(std::string_view instance_name, std::string_view socket_address)
|
||||
: last_response_time_(std::chrono::system_clock::now()),
|
||||
is_alive_(true), // TODO: (andi) Maybe it should be false until the first ping
|
||||
instance_name_(instance_name),
|
||||
socket_address_(socket_address) {}
|
||||
|
||||
~CoordinatorClientInfo() = default;
|
||||
|
||||
CoordinatorClientInfo(const CoordinatorClientInfo &other)
|
||||
: last_response_time_(other.last_response_time_.load()),
|
||||
is_alive_(other.is_alive_.load()),
|
||||
instance_name_(other.instance_name_),
|
||||
socket_address_(other.socket_address_) {}
|
||||
|
||||
CoordinatorClientInfo &operator=(const CoordinatorClientInfo &other) {
|
||||
if (this != &other) {
|
||||
last_response_time_ = other.last_response_time_.load();
|
||||
is_alive_ = other.is_alive_.load();
|
||||
instance_name_ = other.instance_name_;
|
||||
socket_address_ = other.socket_address_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoordinatorClientInfo(CoordinatorClientInfo &&other) noexcept
|
||||
: last_response_time_(other.last_response_time_.load()),
|
||||
is_alive_(other.is_alive_.load()),
|
||||
instance_name_(other.instance_name_),
|
||||
socket_address_(other.socket_address_) {}
|
||||
|
||||
CoordinatorClientInfo &operator=(CoordinatorClientInfo &&other) noexcept {
|
||||
if (this != &other) {
|
||||
last_response_time_.store(other.last_response_time_.load());
|
||||
is_alive_ = other.is_alive_.load();
|
||||
instance_name_ = other.instance_name_;
|
||||
socket_address_ = other.socket_address_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto UpdateInstanceStatus() -> bool {
|
||||
is_alive_ = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() -
|
||||
last_response_time_.load(std::memory_order_acquire))
|
||||
.count() < CoordinatorClusterConfig::alive_response_time_difference_sec_;
|
||||
return is_alive_;
|
||||
}
|
||||
|
||||
auto UpdateLastResponseTime() -> void { last_response_time_ = std::chrono::system_clock::now(); }
|
||||
auto InstanceName() const -> std::string_view { return instance_name_; }
|
||||
auto IsAlive() const -> bool { return is_alive_; }
|
||||
auto SocketAddress() const -> std::string_view { return socket_address_; }
|
||||
|
||||
private:
|
||||
std::atomic<std::chrono::system_clock::time_point> last_response_time_{};
|
||||
std::atomic<bool> is_alive_{false};
|
||||
std::string_view instance_name_;
|
||||
std::string socket_address_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
namespace memgraph::coordination {
|
||||
|
||||
struct CoordinatorClusterConfig {
|
||||
static constexpr int alive_response_time_difference_sec_{5};
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
75
src/coordination/include/coordination/coordinator_config.hpp
Normal file
75
src/coordination/include/coordination/coordinator_config.hpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "replication_coordination_glue/mode.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
|
||||
|
||||
struct CoordinatorClientConfig {
|
||||
std::string instance_name;
|
||||
std::string ip_address;
|
||||
uint16_t port{};
|
||||
std::chrono::seconds health_check_frequency_sec{1};
|
||||
|
||||
// Info which coordinator will send to new main when performing failover
|
||||
struct ReplicationClientInfo {
|
||||
// Must be the same as CoordinatorClientConfig's instance_name
|
||||
std::string instance_name;
|
||||
replication_coordination_glue::ReplicationMode replication_mode{};
|
||||
std::string replication_ip_address;
|
||||
uint16_t replication_port{};
|
||||
|
||||
friend bool operator==(ReplicationClientInfo const &, ReplicationClientInfo const &) = default;
|
||||
};
|
||||
|
||||
std::optional<ReplicationClientInfo> replication_client_info;
|
||||
|
||||
struct SSL {
|
||||
std::string key_file;
|
||||
std::string cert_file;
|
||||
|
||||
friend bool operator==(const SSL &, const SSL &) = default;
|
||||
};
|
||||
|
||||
std::optional<SSL> ssl;
|
||||
|
||||
friend bool operator==(CoordinatorClientConfig const &, CoordinatorClientConfig const &) = default;
|
||||
};
|
||||
|
||||
struct CoordinatorServerConfig {
|
||||
std::string ip_address;
|
||||
uint16_t port{};
|
||||
struct SSL {
|
||||
std::string key_file;
|
||||
std::string cert_file;
|
||||
std::string ca_file;
|
||||
bool verify_peer{};
|
||||
friend bool operator==(SSL const &, SSL const &) = default;
|
||||
};
|
||||
|
||||
std::optional<SSL> ssl;
|
||||
|
||||
friend bool operator==(CoordinatorServerConfig const &, CoordinatorServerConfig const &) = default;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
41
src/coordination/include/coordination/coordinator_data.hpp
Normal file
41
src/coordination/include/coordination/coordinator_data.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
#include "coordination/coordinator_client_info.hpp"
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
struct CoordinatorData {
|
||||
std::list<CoordinatorClient> registered_replicas_;
|
||||
std::list<CoordinatorClientInfo> registered_replicas_info_;
|
||||
std::unique_ptr<CoordinatorClient> registered_main_;
|
||||
std::optional<CoordinatorClientInfo> registered_main_info_;
|
||||
|
||||
mutable utils::RWLock coord_data_lock_{utils::RWLock::Priority::READ};
|
||||
};
|
||||
|
||||
struct CoordinatorMainReplicaData {
|
||||
std::unique_ptr<CoordinatorServer> coordinator_server_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
class CoordinatorFailoverException final : public utils::BasicException {
|
||||
public:
|
||||
explicit CoordinatorFailoverException(const std::string_view what) noexcept
|
||||
: BasicException("Failover didn't complete successfully: " + std::string(what)) {}
|
||||
|
||||
template <class... Args>
|
||||
explicit CoordinatorFailoverException(fmt::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
: CoordinatorFailoverException(fmt::format(fmt, std::forward<Args>(args)...)) {}
|
||||
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorFailoverException)
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -11,15 +11,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
namespace memgraph::storage {
|
||||
#include "io/network/endpoint.hpp"
|
||||
|
||||
/**
|
||||
* @brief We need to protect the database using a DatabaseAccess, and we need to keep the replication/storage/dbms
|
||||
* untied. To achieve that we are using std::any, but beware to pass in the correct type using DatabaseAccess =
|
||||
* memgraph::utils::Gatekeeper<memgraph::dbms::Database>::Accessor;
|
||||
*/
|
||||
using DatabaseAccessProtector = std::any;
|
||||
#include <string_view>
|
||||
|
||||
} // namespace memgraph::storage
|
||||
namespace memgraph::coordination {
|
||||
|
||||
struct CoordinatorInstanceStatus {
|
||||
std::string_view instance_name;
|
||||
std::string_view socket_address;
|
||||
bool is_alive;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
#endif
|
||||
104
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
104
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "rpc/messages.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
struct PromoteReplicaToMainReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit PromoteReplicaToMainReq(std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info)
|
||||
: replication_clients_info(std::move(replication_clients_info)) {}
|
||||
PromoteReplicaToMainReq() = default;
|
||||
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info;
|
||||
};
|
||||
|
||||
struct PromoteReplicaToMainRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit PromoteReplicaToMainRes(bool success) : success(success) {}
|
||||
PromoteReplicaToMainRes() = default;
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using PromoteReplicaToMainRpc = rpc::RequestResponse<PromoteReplicaToMainReq, PromoteReplicaToMainRes>;
|
||||
|
||||
struct SetMainToReplicaReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit SetMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info)
|
||||
: replication_client_info(std::move(replication_client_info)) {}
|
||||
|
||||
SetMainToReplicaReq() = default;
|
||||
|
||||
CoordinatorClientConfig::ReplicationClientInfo replication_client_info;
|
||||
};
|
||||
|
||||
struct SetMainToReplicaRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
explicit SetMainToReplicaRes(bool success) : success(success) {}
|
||||
SetMainToReplicaRes() = default;
|
||||
|
||||
bool success;
|
||||
};
|
||||
|
||||
using SetMainToReplicaRpc = rpc::RequestResponse<SetMainToReplicaReq, SetMainToReplicaRes>;
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
// SLK serialization declarations
|
||||
namespace memgraph::slk {
|
||||
|
||||
void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader);
|
||||
|
||||
void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder);
|
||||
|
||||
void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
#endif
|
||||
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "rpc/server.hpp"
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
class CoordinatorServer {
|
||||
public:
|
||||
explicit CoordinatorServer(const CoordinatorServerConfig &config);
|
||||
CoordinatorServer(const CoordinatorServer &) = delete;
|
||||
CoordinatorServer(CoordinatorServer &&) = delete;
|
||||
CoordinatorServer &operator=(const CoordinatorServer &) = delete;
|
||||
CoordinatorServer &operator=(CoordinatorServer &&) = delete;
|
||||
|
||||
virtual ~CoordinatorServer();
|
||||
|
||||
bool Start();
|
||||
|
||||
template <typename TRequestResponse, typename F>
|
||||
void Register(F &&callback) {
|
||||
rpc_server_.Register<TRequestResponse>(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
private:
|
||||
communication::ServerContext rpc_server_context_;
|
||||
rpc::Server rpc_server_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
#include "slk/streams.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
using ReplicationClientInfo = coordination::CoordinatorClientConfig::ReplicationClientInfo;
|
||||
|
||||
inline void Save(const ReplicationClientInfo &obj, Builder *builder) {
|
||||
Save(obj.instance_name, builder);
|
||||
Save(obj.replication_mode, builder);
|
||||
Save(obj.replication_ip_address, builder);
|
||||
Save(obj.replication_port, builder);
|
||||
}
|
||||
|
||||
inline void Load(ReplicationClientInfo *obj, Reader *reader) {
|
||||
Load(&obj->instance_name, reader);
|
||||
Load(&obj->replication_mode, reader);
|
||||
Load(&obj->replication_ip_address, reader);
|
||||
Load(&obj->replication_port, reader);
|
||||
}
|
||||
} // namespace memgraph::slk
|
||||
#endif
|
||||
66
src/coordination/include/coordination/coordinator_state.hpp
Normal file
66
src/coordination/include/coordination/coordinator_state.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
#include "coordination/coordinator_client_info.hpp"
|
||||
#include "coordination/coordinator_data.hpp"
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "coordination/failover_status.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
#include "rpc/server.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/rw_spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <variant>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
class CoordinatorState {
|
||||
public:
|
||||
CoordinatorState();
|
||||
~CoordinatorState() = default;
|
||||
|
||||
CoordinatorState(const CoordinatorState &) = delete;
|
||||
CoordinatorState &operator=(const CoordinatorState &) = delete;
|
||||
|
||||
CoordinatorState(CoordinatorState &&) noexcept = delete;
|
||||
CoordinatorState &operator=(CoordinatorState &&) noexcept = delete;
|
||||
|
||||
[[nodiscard]] auto RegisterReplica(CoordinatorClientConfig config) -> RegisterMainReplicaCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto RegisterMain(CoordinatorClientConfig config) -> RegisterMainReplicaCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto RegisterInstanceOnCoordinator(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus;
|
||||
|
||||
[[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
auto ShowReplicas() const -> std::vector<CoordinatorInstanceStatus>;
|
||||
|
||||
auto ShowMain() const -> std::optional<CoordinatorInstanceStatus>;
|
||||
|
||||
// The client code must check that the server exists before calling this method.
|
||||
auto GetCoordinatorServer() const -> CoordinatorServer &;
|
||||
|
||||
[[nodiscard]] auto DoFailover() -> DoFailoverStatus;
|
||||
|
||||
private:
|
||||
std::variant<CoordinatorData, CoordinatorMainReplicaData> data_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
21
src/coordination/include/coordination/failover_status.hpp
Normal file
21
src/coordination/include/coordination/failover_status.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, CLUSTER_UNINITIALIZED };
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
enum class RegisterMainReplicaCoordinatorStatus : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
NOT_COORDINATOR,
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum class RegisterInstanceCoordinatorStatus : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
NOT_COORDINATOR,
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum class SetInstanceToMainCoordinatorStatus : uint8_t {
|
||||
NO_INSTANCE_WITH_NAME,
|
||||
NOT_COORDINATOR,
|
||||
SUCCESS,
|
||||
COULD_NOT_PROMOTE_TO_MAIN,
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -1,3 +1,2 @@
|
||||
|
||||
add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp)
|
||||
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query)
|
||||
add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp coordinator_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp coordinator_handlers.cpp)
|
||||
target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-replication mg-coordination)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -13,8 +13,7 @@
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
constexpr std::string_view kDefaultDB = "memgraph"; //!< Name of the default database
|
||||
constexpr std::string_view kMultiTenantDir = "databases"; //!< Name of the multi-tenant directory
|
||||
constexpr static const char *kDefaultDB = "memgraph"; //!< Name of the default database
|
||||
|
||||
#ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
|
||||
constexpr bool allow_mt_repl = true;
|
||||
|
||||
57
src/dbms/coordinator_handler.cpp
Normal file
57
src/dbms/coordinator_handler.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2024 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 "coordination/register_main_replica_coordinator_status.hpp"
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "dbms/coordinator_handler.hpp"
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||
|
||||
auto CoordinatorHandler::RegisterReplicaOnCoordinator(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterMainReplicaCoordinatorStatus {
|
||||
return dbms_handler_.CoordinatorState().RegisterReplica(std::move(config));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::RegisterMainOnCoordinator(memgraph::coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterMainReplicaCoordinatorStatus {
|
||||
return dbms_handler_.CoordinatorState().RegisterMain(std::move(config));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::RegisterInstanceOnCoordinator(memgraph::coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterInstanceCoordinatorStatus {
|
||||
return dbms_handler_.CoordinatorState().RegisterInstanceOnCoordinator(std::move(config));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::SetInstanceToMain(std::string instance_name)
|
||||
-> coordination::SetInstanceToMainCoordinatorStatus {
|
||||
return dbms_handler_.CoordinatorState().SetInstanceToMain(std::move(instance_name));
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::ShowReplicasOnCoordinator() const -> std::vector<coordination::CoordinatorInstanceStatus> {
|
||||
return dbms_handler_.CoordinatorState().ShowReplicas();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::ShowMainOnCoordinator() const -> std::optional<coordination::CoordinatorInstanceStatus> {
|
||||
return dbms_handler_.CoordinatorState().ShowMain();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::DoFailover() const -> coordination::DoFailoverStatus {
|
||||
return dbms_handler_.CoordinatorState().DoFailover();
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
#endif
|
||||
57
src/dbms/coordinator_handler.hpp
Normal file
57
src/dbms/coordinator_handler.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "utils/result.hpp"
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#include "coordination/failover_status.hpp"
|
||||
#include "coordination/register_main_replica_coordinator_status.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
class DbmsHandler;
|
||||
|
||||
class CoordinatorHandler {
|
||||
public:
|
||||
explicit CoordinatorHandler(DbmsHandler &dbms_handler);
|
||||
|
||||
auto RegisterReplicaOnCoordinator(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterMainReplicaCoordinatorStatus;
|
||||
|
||||
auto RegisterMainOnCoordinator(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterMainReplicaCoordinatorStatus;
|
||||
|
||||
auto RegisterInstanceOnCoordinator(coordination::CoordinatorClientConfig config)
|
||||
-> coordination::RegisterInstanceCoordinatorStatus;
|
||||
|
||||
auto SetInstanceToMain(std::string instance_name) -> coordination::SetInstanceToMainCoordinatorStatus;
|
||||
|
||||
auto ShowReplicasOnCoordinator() const -> std::vector<coordination::CoordinatorInstanceStatus>;
|
||||
|
||||
auto ShowMainOnCoordinator() const -> std::optional<coordination::CoordinatorInstanceStatus>;
|
||||
|
||||
auto DoFailover() const -> coordination::DoFailoverStatus;
|
||||
|
||||
private:
|
||||
DbmsHandler &dbms_handler_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
136
src/dbms/coordinator_handlers.cpp
Normal file
136
src/dbms/coordinator_handlers.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "dbms/coordinator_handlers.hpp"
|
||||
#include "dbms/utils.hpp"
|
||||
|
||||
#include "coordination/coordinator_exceptions.hpp"
|
||||
#include "coordination/coordinator_rpc.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
void CoordinatorHandlers::Register(DbmsHandler &dbms_handler) {
|
||||
auto &server = dbms_handler.CoordinatorState().GetCoordinatorServer();
|
||||
|
||||
server.Register<coordination::PromoteReplicaToMainRpc>(
|
||||
[&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received PromoteReplicaToMainRpc from coordinator server");
|
||||
CoordinatorHandlers::PromoteReplicaToMainHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
|
||||
server.Register<coordination::SetMainToReplicaRpc>(
|
||||
[&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void {
|
||||
spdlog::info("Received PromoteReplicaToMainRpc from coordinator server");
|
||||
CoordinatorHandlers::SetMainToReplicaHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
|
||||
if (!repl_state.IsMain()) {
|
||||
spdlog::error("Setting to replica must be performed on main.");
|
||||
slk::Save(coordination::SetMainToReplicaRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::SetMainToReplicaReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
replication::ReplicationServerConfig clients_config{.ip_address = req.replication_client_info.replication_ip_address,
|
||||
.port = req.replication_client_info.replication_port};
|
||||
|
||||
if (bool success = memgraph::dbms::SetReplicationRoleReplica(dbms_handler, clients_config); !success) {
|
||||
spdlog::error("Setting main to replica failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
void CoordinatorHandlers::PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
|
||||
if (!repl_state.IsReplica()) {
|
||||
spdlog::error("Failover must be performed on replica!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bool success = memgraph::dbms::DoReplicaToMainPromotion(dbms_handler); !success) {
|
||||
spdlog::error("Promoting replica to main failed!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
coordination::PromoteReplicaToMainReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
|
||||
std::vector<replication::ReplicationClientConfig> clients_config;
|
||||
clients_config.reserve(req.replication_clients_info.size());
|
||||
std::ranges::transform(req.replication_clients_info, std::back_inserter(clients_config),
|
||||
[](const auto &repl_info_config) {
|
||||
return replication::ReplicationClientConfig{
|
||||
.name = repl_info_config.instance_name,
|
||||
.mode = repl_info_config.replication_mode,
|
||||
.ip_address = repl_info_config.replication_ip_address,
|
||||
.port = repl_info_config.replication_port,
|
||||
};
|
||||
});
|
||||
|
||||
std::ranges::for_each(clients_config, [&dbms_handler, &repl_state, &res_builder](const auto &config) {
|
||||
auto instance_client = repl_state.RegisterReplica(config);
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
spdlog::error("Failover must be performed to main!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
spdlog::error("Replica with the same name already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS:
|
||||
spdlog::error("Replica with the same endpoint already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
spdlog::error("Registered replica could not be persisted!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto &instance_client_ptr = instance_client.GetValue();
|
||||
const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler, *instance_client_ptr);
|
||||
|
||||
if (!all_clients_good) {
|
||||
spdlog::error("Failed to register all databases to the REPLICA \"{}\"", config.name);
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
StartReplicaClient(dbms_handler, *instance_client.GetValue());
|
||||
});
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
34
src/dbms/coordinator_handlers.hpp
Normal file
34
src/dbms/coordinator_handlers.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2024 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
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
class DbmsHandler;
|
||||
|
||||
class CoordinatorHandlers {
|
||||
public:
|
||||
static void Register(DbmsHandler &dbms_handler);
|
||||
|
||||
private:
|
||||
static void PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader,
|
||||
slk::Builder *res_builder);
|
||||
static void SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
#endif
|
||||
@@ -26,7 +26,7 @@ Database::Database(storage::Config config, replication::ReplicationState &repl_s
|
||||
streams_{config.durability.storage_directory / "streams"},
|
||||
plan_cache_{FLAGS_query_plan_cache_max_size},
|
||||
repl_state_(&repl_state) {
|
||||
if (config.salient.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk ||
|
||||
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>(std::move(config));
|
||||
} else {
|
||||
|
||||
@@ -81,14 +81,7 @@ class Database {
|
||||
*
|
||||
* @return const std::string&
|
||||
*/
|
||||
const std::string &name() const { return storage_->name(); }
|
||||
|
||||
/**
|
||||
* @brief Unique storage identified (uuid)
|
||||
*
|
||||
* @return const utils::UUID&
|
||||
*/
|
||||
const utils::UUID &uuid() const { return storage_->uuid(); }
|
||||
const std::string &id() const { return storage_->id(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the storage configuration
|
||||
|
||||
@@ -51,7 +51,7 @@ class DatabaseHandler : public Handler<Database> {
|
||||
* @param config Storage configuration
|
||||
* @return HandlerT::NewResult
|
||||
*/
|
||||
HandlerT::NewResult New(storage::Config config, replication::ReplicationState &repl_state) {
|
||||
HandlerT::NewResult New(std::string_view name, storage::Config config, 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();
|
||||
@@ -61,7 +61,8 @@ class DatabaseHandler : public Handler<Database> {
|
||||
spdlog::info("Tried to generate new storage using a claimed directory.");
|
||||
return NewError::EXISTS;
|
||||
}
|
||||
return HandlerT::New(std::piecewise_construct, config.salient.name, config, repl_state);
|
||||
config.name = name; // Set storage id via config
|
||||
return HandlerT::New(std::piecewise_construct, name, config, repl_state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,204 +9,63 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/uuid.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view kDBPrefix = "database:"; // Key prefix for database durability
|
||||
constexpr std::string_view kLastCommitedSystemTsKey = "last_commited_system_ts"; // Key for timestamp durability
|
||||
} // namespace
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
struct Durability {
|
||||
enum class DurabilityVersion : uint8_t {
|
||||
V0 = 0,
|
||||
V1,
|
||||
};
|
||||
#include "dbms/coordinator_handlers.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
|
||||
struct VersionException : public utils::BasicException {
|
||||
VersionException() : utils::BasicException("Unsupported durability version!") {}
|
||||
};
|
||||
|
||||
struct UnknownVersionException : public utils::BasicException {
|
||||
UnknownVersionException() : utils::BasicException("Unable to parse the durability version!") {}
|
||||
};
|
||||
|
||||
struct MigrationException : public utils::BasicException {
|
||||
MigrationException() : utils::BasicException("Failed to migrate to the current durability version!") {}
|
||||
};
|
||||
|
||||
static DurabilityVersion VersionCheck(std::optional<std::string_view> val) {
|
||||
if (!val) {
|
||||
return DurabilityVersion::V0;
|
||||
}
|
||||
if (val == "V1") {
|
||||
return DurabilityVersion::V1;
|
||||
}
|
||||
throw UnknownVersionException();
|
||||
};
|
||||
|
||||
static auto GenKey(std::string_view name) -> std::string { return fmt::format("{}{}", kDBPrefix, name); }
|
||||
|
||||
static auto GenVal(utils::UUID uuid, std::filesystem::path rel_dir) {
|
||||
nlohmann::json json;
|
||||
json["uuid"] = uuid;
|
||||
json["rel_dir"] = rel_dir;
|
||||
// TODO: Serialize the configuration
|
||||
return json.dump();
|
||||
}
|
||||
|
||||
static void Migrate(kvstore::KVStore *durability, const std::filesystem::path &root) {
|
||||
const auto ver_val = durability->Get("version");
|
||||
const auto ver = VersionCheck(ver_val);
|
||||
|
||||
std::map<std::string, std::string> to_put;
|
||||
std::vector<std::string> to_delete;
|
||||
|
||||
// Update from V0 to V1
|
||||
if (ver == DurabilityVersion::V0) {
|
||||
for (const auto &[key, val] : *durability) {
|
||||
if (key == "version") continue; // Reserved key
|
||||
// Generate a UUID
|
||||
auto const uuid = utils::UUID();
|
||||
// New json values
|
||||
auto new_key = GenKey(key);
|
||||
auto path = root;
|
||||
if (key != kDefaultDB) { // Special case for non-default DBs
|
||||
// Move directory to new UUID dir
|
||||
path = root / kMultiTenantDir / std::string{uuid};
|
||||
std::filesystem::path old_dir(root / kMultiTenantDir / key);
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(old_dir, path, ec);
|
||||
MG_ASSERT(!ec, "Failed to upgrade durability: cannot move default directory.");
|
||||
}
|
||||
// Generate json and update value
|
||||
auto new_data = GenVal(uuid, std::filesystem::relative(path, root));
|
||||
to_put.emplace(std::move(new_key), std::move(new_data));
|
||||
to_delete.emplace_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Set version
|
||||
durability->Put("version", "V1");
|
||||
// Update to the new key-value pairs
|
||||
if (!durability->PutAndDeleteMultiple(to_put, to_delete)) {
|
||||
throw MigrationException();
|
||||
}
|
||||
}
|
||||
};
|
||||
namespace memgraph::dbms {
|
||||
|
||||
DbmsHandler::DbmsHandler(
|
||||
storage::Config config,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
bool recovery_on_startup)
|
||||
: default_config_{std::move(config)}, repl_state_{ReplicationStateRootPath(default_config_)} {
|
||||
bool recovery_on_startup, bool delete_on_drop)
|
||||
: default_config_{std::move(config)},
|
||||
delete_on_drop_(delete_on_drop),
|
||||
repl_state_{ReplicationStateRootPath(default_config_)} {
|
||||
// TODO: Decouple storage config from dbms config
|
||||
// TODO: Save individual db configs inside the kvstore and restore from there
|
||||
|
||||
/*
|
||||
* FILESYSTEM MANIPULATION
|
||||
*/
|
||||
const auto &root = default_config_.durability.storage_directory;
|
||||
storage::UpdatePaths(default_config_, root);
|
||||
const auto &db_dir = default_config_.durability.storage_directory / kMultiTenantDir;
|
||||
// TODO: Unify durability and wal
|
||||
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);
|
||||
durability_ = std::make_unique<kvstore::KVStore>(durability_dir);
|
||||
|
||||
/*
|
||||
* DURABILITY
|
||||
*/
|
||||
// Migrate durability
|
||||
Durability::Migrate(durability_.get(), root);
|
||||
auto directories = std::set{std::string{kDefaultDB}};
|
||||
// Generate the default database
|
||||
MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB.");
|
||||
|
||||
// Recover previous databases
|
||||
if (recovery_on_startup) {
|
||||
auto it = durability_->begin(std::string(kDBPrefix));
|
||||
auto end = durability_->end(std::string(kDBPrefix));
|
||||
for (; it != end; ++it) {
|
||||
const auto &[key, config_json] = *it;
|
||||
const auto name = key.substr(kDBPrefix.size());
|
||||
auto json = nlohmann::json::parse(config_json);
|
||||
const auto uuid = json.at("uuid").get<utils::UUID>();
|
||||
const auto rel_dir = json.at("rel_dir").get<std::filesystem::path>();
|
||||
spdlog::info("Restoring database {} at {}.", name, rel_dir);
|
||||
auto new_db = New_(name, uuid, rel_dir);
|
||||
MG_ASSERT(!new_db.HasError(), "Failed while creating database {}.", name);
|
||||
directories.emplace(rel_dir.filename());
|
||||
for (const auto &[name, _] : *durability_) {
|
||||
if (name == kDefaultDB) continue; // Already set
|
||||
spdlog::info("Restoring database {}.", name);
|
||||
MG_ASSERT(!New_(name).HasError(), "Failed while creating database {}.", name);
|
||||
spdlog::info("Database {} restored.", name);
|
||||
}
|
||||
// Read the last timestamp
|
||||
auto lcst = durability_->Get(kLastCommitedSystemTsKey);
|
||||
if (lcst) {
|
||||
last_commited_system_timestamp_ = std::stoul(*lcst);
|
||||
system_timestamp_ = last_commited_system_timestamp_;
|
||||
}
|
||||
} else { // Clear databases from the durability list and auth
|
||||
auto locked_auth = auth->Lock();
|
||||
auto it = durability_->begin(std::string{kDBPrefix});
|
||||
auto end = durability_->end(std::string{kDBPrefix});
|
||||
for (; it != end; ++it) {
|
||||
const auto &[key, _] = *it;
|
||||
const auto name = key.substr(kDBPrefix.size());
|
||||
for (const auto &[name, _] : *durability_) {
|
||||
if (name == kDefaultDB) continue;
|
||||
locked_auth->DeleteDatabase(name);
|
||||
durability_->Delete(key);
|
||||
}
|
||||
// Delete the last timestamp
|
||||
durability_->Delete(kLastCommitedSystemTsKey);
|
||||
}
|
||||
|
||||
/*
|
||||
* DATABASES CLEAN UP
|
||||
*/
|
||||
// Clean the unused directories
|
||||
for (const auto &entry : std::filesystem::directory_iterator(db_dir)) {
|
||||
const auto &name = entry.path().filename().string();
|
||||
if (entry.is_directory() && !name.empty() && name.front() != '.') {
|
||||
auto itr = directories.find(name);
|
||||
if (itr == directories.end()) {
|
||||
std::error_code dummy;
|
||||
std::filesystem::remove_all(entry, dummy);
|
||||
} else {
|
||||
directories.erase(itr);
|
||||
}
|
||||
durability_->Delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* DEFAULT DB SETUP
|
||||
*/
|
||||
// Setup the default DB
|
||||
SetupDefault_();
|
||||
|
||||
/*
|
||||
* REPLICATION RECOVERY AND STARTUP
|
||||
*/
|
||||
// Startup replication state (if recovered at startup)
|
||||
auto replica = [this](replication::RoleReplicaData const &data) { return StartRpcServer(*this, data); };
|
||||
// Replication recovery and frequent check start
|
||||
auto main = [this](replication::RoleMainData &data) {
|
||||
for (auto &client : data.registered_replicas_) {
|
||||
SystemRestore(client);
|
||||
auto replica = [this](replication::RoleReplicaData const &data) {
|
||||
// Register handlers
|
||||
InMemoryReplicationHandlers::Register(this, *data.server);
|
||||
if (!data.server->Start()) {
|
||||
spdlog::error("Unable to start the replication server.");
|
||||
return false;
|
||||
}
|
||||
ForEach([this](DatabaseAccess db) { RecoverReplication(db); });
|
||||
return true;
|
||||
};
|
||||
// Replication frequent check start
|
||||
auto main = [this](replication::RoleMainData &data) {
|
||||
for (auto &client : data.registered_replicas_) {
|
||||
StartReplicaClient(*this, client);
|
||||
}
|
||||
@@ -216,230 +75,12 @@ DbmsHandler::DbmsHandler(
|
||||
MG_ASSERT(std::visit(memgraph::utils::Overloaded{replica, main}, repl_state_.ReplicationData()),
|
||||
"Replica recovery failure!");
|
||||
|
||||
// Warning
|
||||
if (default_config_.durability.snapshot_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.");
|
||||
// MAIN or REPLICA instance
|
||||
if (FLAGS_coordinator_server_port) {
|
||||
CoordinatorHandlers::Register(*this);
|
||||
MG_ASSERT(coordinator_state_.GetCoordinatorServer().Start(), "Failed to start coordinator server!");
|
||||
}
|
||||
}
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
if (db_name == kDefaultDB) {
|
||||
// MSG cannot delete the default db
|
||||
return DeleteError::DEFAULT_DB;
|
||||
}
|
||||
|
||||
// Get DB config for the UUID and disk clean up
|
||||
const auto conf = db_handler_.GetConfig(db_name);
|
||||
if (!conf) {
|
||||
return DeleteError::NON_EXISTENT;
|
||||
}
|
||||
const auto &storage_path = conf->durability.storage_directory;
|
||||
const auto &uuid = conf->salient.uuid;
|
||||
|
||||
// Check if db exists
|
||||
try {
|
||||
// Low level handlers
|
||||
if (!db_handler_.TryDelete(db_name)) {
|
||||
return DeleteError::USING;
|
||||
}
|
||||
} catch (utils::BasicException &) {
|
||||
return DeleteError::NON_EXISTENT;
|
||||
}
|
||||
|
||||
// Remove from durability list
|
||||
if (durability_) durability_->Delete(Durability::GenKey(db_name));
|
||||
|
||||
// Delete disk storage
|
||||
std::error_code ec;
|
||||
(void)std::filesystem::remove_all(storage_path, ec);
|
||||
if (ec) {
|
||||
spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
|
||||
}
|
||||
|
||||
// Success
|
||||
// Save delta
|
||||
if (system_transaction_) {
|
||||
system_transaction_->delta.emplace(SystemTransaction::Delta::drop_database, uuid);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::Delete(std::string_view db_name) {
|
||||
auto wr = std::lock_guard(lock_);
|
||||
return Delete_(db_name);
|
||||
}
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::Delete(utils::UUID uuid) {
|
||||
auto wr = std::lock_guard(lock_);
|
||||
std::string db_name;
|
||||
try {
|
||||
const auto db = Get_(uuid);
|
||||
db_name = db->name();
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
return DeleteError::NON_EXISTENT;
|
||||
}
|
||||
return Delete_(db_name);
|
||||
}
|
||||
|
||||
DbmsHandler::NewResultT DbmsHandler::New_(storage::Config storage_config) {
|
||||
auto new_db = db_handler_.New(storage_config, repl_state_);
|
||||
|
||||
if (new_db.HasValue()) { // Success
|
||||
// Save delta
|
||||
if (system_transaction_) {
|
||||
system_transaction_->delta.emplace(SystemTransaction::Delta::create_database, storage_config.salient);
|
||||
}
|
||||
UpdateDurability(storage_config);
|
||||
return new_db.GetValue();
|
||||
}
|
||||
return new_db.GetError();
|
||||
}
|
||||
|
||||
DbmsHandler::DeleteResult DbmsHandler::Delete_(std::string_view db_name) {
|
||||
if (db_name == kDefaultDB) {
|
||||
// MSG cannot delete the default db
|
||||
return DeleteError::DEFAULT_DB;
|
||||
}
|
||||
|
||||
const auto storage_path = StorageDir_(db_name);
|
||||
if (!storage_path) return DeleteError::NON_EXISTENT;
|
||||
|
||||
{
|
||||
auto db = db_handler_.Get(db_name);
|
||||
if (!db) return DeleteError::NON_EXISTENT;
|
||||
// TODO: ATM we assume REPLICA won't have streams,
|
||||
// this is a best effort approach just in case they do
|
||||
// there is still subtle data race we stream manipulation
|
||||
// can occur while we are dropping the database
|
||||
db->prepare_for_deletion();
|
||||
auto &database = *db->get();
|
||||
database.streams()->StopAll();
|
||||
database.streams()->DropAll();
|
||||
database.thread_pool()->Shutdown();
|
||||
}
|
||||
|
||||
// Remove from durability list
|
||||
if (durability_) durability_->Delete(Durability::GenKey(db_name));
|
||||
|
||||
// Check if db exists
|
||||
// Low level handlers
|
||||
db_handler_.DeferDelete(db_name, [storage_path = *storage_path, db_name = std::string{db_name}]() {
|
||||
// Delete disk storage
|
||||
std::error_code ec;
|
||||
(void)std::filesystem::remove_all(storage_path, ec);
|
||||
if (ec) {
|
||||
spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path);
|
||||
}
|
||||
});
|
||||
|
||||
return {}; // Success
|
||||
}
|
||||
|
||||
void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir) {
|
||||
if (!durability_) return;
|
||||
// Save database in a list of active databases
|
||||
const auto &key = Durability::GenKey(config.salient.name);
|
||||
if (rel_dir == std::nullopt)
|
||||
rel_dir =
|
||||
std::filesystem::relative(config.durability.storage_directory, default_config_.durability.storage_directory);
|
||||
const auto &val = Durability::GenVal(config.salient.uuid, *rel_dir);
|
||||
durability_->Put(key, val);
|
||||
}
|
||||
|
||||
AllSyncReplicaStatus DbmsHandler::Commit() {
|
||||
if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt)
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit
|
||||
const auto &delta = *system_transaction_->delta;
|
||||
|
||||
auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
// TODO Create a system client that can handle all of this automatically
|
||||
switch (delta.action) { // TODO for
|
||||
using enum SystemTransaction::Delta::Action;
|
||||
case CREATE_DATABASE: {
|
||||
// Replication
|
||||
auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
|
||||
// TODO: data race issue? registered_replicas_ access not protected
|
||||
// This is sync in any case, as this is the startup
|
||||
for (auto &client : main_data.registered_replicas_) {
|
||||
bool completed = SteamAndFinalizeDelta<storage::replication::CreateDatabaseRpc>(
|
||||
client,
|
||||
[](const storage::replication::CreateDatabaseRes &response) {
|
||||
return response.result != storage::replication::CreateDatabaseRes::Result::FAILURE;
|
||||
},
|
||||
std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
|
||||
system_transaction_->system_timestamp, delta.config);
|
||||
// TODO: reduce duplicate code
|
||||
if (!completed && client.mode_ == replication::ReplicationMode::SYNC) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
}
|
||||
// Sync database with REPLICAs
|
||||
RecoverReplication(Get_(delta.config.name));
|
||||
};
|
||||
auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
|
||||
std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
} break;
|
||||
case DROP_DATABASE: {
|
||||
// Replication
|
||||
auto main_handler = [&](memgraph::replication::RoleMainData &main_data) {
|
||||
// TODO: data race issue? registered_replicas_ access not protected
|
||||
// This is sync in any case, as this is the startup
|
||||
for (auto &client : main_data.registered_replicas_) {
|
||||
bool completed = SteamAndFinalizeDelta<storage::replication::DropDatabaseRpc>(
|
||||
client,
|
||||
[](const storage::replication::DropDatabaseRes &response) {
|
||||
return response.result != storage::replication::DropDatabaseRes::Result::FAILURE;
|
||||
},
|
||||
std::string(main_data.epoch_.id()), last_commited_system_timestamp_,
|
||||
system_transaction_->system_timestamp, delta.uuid);
|
||||
// TODO: reduce duplicate code
|
||||
if (!completed && client.mode_ == replication::ReplicationMode::SYNC) {
|
||||
sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed;
|
||||
}
|
||||
}
|
||||
};
|
||||
auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ };
|
||||
std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
|
||||
} break;
|
||||
case UPDATE_AUTH_DATA: // TODO
|
||||
case DROP_AUTH_DATA: // TODO
|
||||
break;
|
||||
}
|
||||
|
||||
durability_->Put(kLastCommitedSystemTsKey, std::to_string(system_transaction_->system_timestamp));
|
||||
last_commited_system_timestamp_ = system_transaction_->system_timestamp;
|
||||
ResetSystemTransaction();
|
||||
return sync_status;
|
||||
}
|
||||
|
||||
#else // not MG_ENTERPRISE
|
||||
|
||||
AllSyncReplicaStatus DbmsHandler::Commit() {
|
||||
if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt) {
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit
|
||||
}
|
||||
const auto &delta = *system_transaction_->delta;
|
||||
|
||||
switch (delta.action) {
|
||||
using enum SystemTransaction::Delta::Action;
|
||||
case CREATE_DATABASE:
|
||||
case DROP_DATABASE:
|
||||
case UPDATE_AUTH_DATA: // TODO Do we want to have auth replication under community?
|
||||
case DROP_AUTH_DATA: // TODO Do we want to have auth replication under community?
|
||||
/* Community edition doesn't support multi-tenant replication */
|
||||
break;
|
||||
}
|
||||
|
||||
last_commited_system_timestamp_ = system_transaction_->system_timestamp;
|
||||
ResetSystemTransaction();
|
||||
return AllSyncReplicaStatus::AllCommitsConfirmed;
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
|
||||
@@ -12,36 +12,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/coordinator_state.hpp"
|
||||
#include "dbms/database_handler.hpp"
|
||||
#endif
|
||||
#include "dbms/transaction.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "global.hpp"
|
||||
#include "query/config.hpp"
|
||||
#include "query/interpreter_context.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v2/durability/durability.hpp"
|
||||
#include "storage/v2/durability/paths.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
@@ -50,11 +49,6 @@
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
enum class AllSyncReplicaStatus {
|
||||
AllCommitsConfirmed,
|
||||
SomeCommitsUnconfirmed,
|
||||
};
|
||||
|
||||
struct Statistics {
|
||||
uint64_t num_vertex; //!< Sum of vertexes in every database
|
||||
uint64_t num_edges; //!< Sum of edges in every database
|
||||
@@ -109,10 +103,11 @@ class DbmsHandler {
|
||||
* @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,
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
bool recovery_on_startup); // TODO If more arguments are added use a config struct
|
||||
bool recovery_on_startup, bool delete_on_drop); // TODO If more arguments are added use a config strut
|
||||
#else
|
||||
/**
|
||||
* @brief Initialize the handler. A single database is supported in community edition.
|
||||
@@ -122,12 +117,10 @@ class DbmsHandler {
|
||||
DbmsHandler(storage::Config config)
|
||||
: repl_state_{ReplicationStateRootPath(config)},
|
||||
db_gatekeeper_{[&] {
|
||||
config.salient.name = kDefaultDB;
|
||||
config.name = kDefaultDB;
|
||||
return std::move(config);
|
||||
}(),
|
||||
repl_state_} {
|
||||
RecoverReplication(Get());
|
||||
}
|
||||
repl_state_} {}
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -139,56 +132,9 @@ class DbmsHandler {
|
||||
*/
|
||||
NewResultT New(const std::string &name) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
const auto uuid = utils::UUID{};
|
||||
return New_(name, uuid);
|
||||
return New_(name, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create new if name/uuid do not match any database. Drop and recreate if database already present.
|
||||
* @note Default database is not dropped, only its UUID is updated and only if the database is clean.
|
||||
*
|
||||
* @param config desired salient config
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT Update(const storage::SalientConfig &config) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
auto new_db = New_(config);
|
||||
if (new_db.HasValue() || new_db.GetError() != NewError::EXISTS) {
|
||||
// NOTE: If db already exists we retry below
|
||||
return new_db;
|
||||
}
|
||||
|
||||
spdlog::debug("Trying to create db '{}' on replica which already exists.", config.name);
|
||||
|
||||
auto db = Get_(config.name);
|
||||
if (db->uuid() == config.uuid) { // Same db
|
||||
return db;
|
||||
}
|
||||
|
||||
spdlog::debug("Different UUIDs");
|
||||
|
||||
// TODO: Fix this hack
|
||||
if (config.name == kDefaultDB) {
|
||||
if (db->storage()->repl_storage_state_.last_commit_timestamp_ != storage::kTimestampInitialId) {
|
||||
spdlog::debug("Default storage is not clean, cannot update UUID...");
|
||||
return NewError::GENERIC; // Update error
|
||||
}
|
||||
spdlog::debug("Update default db's UUID");
|
||||
// Default db cannot be deleted and remade, have to just update the UUID
|
||||
db->storage()->config_.salient.uuid = config.uuid;
|
||||
UpdateDurability(db->storage()->config_, ".");
|
||||
return db;
|
||||
}
|
||||
|
||||
spdlog::debug("Drop database and recreate with the correct UUID");
|
||||
// Defer drop
|
||||
(void)Delete_(db->name());
|
||||
// Second attempt
|
||||
return New_(config);
|
||||
}
|
||||
|
||||
void UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir = {});
|
||||
|
||||
/**
|
||||
* @brief Get the context associated with the "name" database
|
||||
*
|
||||
@@ -200,19 +146,6 @@ class DbmsHandler {
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
return Get_(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the context associated with the UUID database
|
||||
*
|
||||
* @param uuid
|
||||
* @return DatabaseAccess
|
||||
* @throw UnknownDatabaseException if database not found
|
||||
*/
|
||||
DatabaseAccess Get(const utils::UUID &uuid) {
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
return Get_(uuid);
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* @brief Get the context associated with the default database
|
||||
@@ -228,28 +161,50 @@ class DbmsHandler {
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/**
|
||||
* @brief Attempt to delete database.
|
||||
* @brief Delete database.
|
||||
*
|
||||
* @param db_name database name
|
||||
* @return DeleteResult error on failure
|
||||
*/
|
||||
DeleteResult TryDelete(std::string_view db_name);
|
||||
DeleteResult Delete(const std::string &db_name) {
|
||||
std::lock_guard<LockT> wr(lock_);
|
||||
if (db_name == kDefaultDB) {
|
||||
// MSG cannot delete the default db
|
||||
return DeleteError::DEFAULT_DB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delete or defer deletion of database.
|
||||
*
|
||||
* @param db_name database name
|
||||
* @return DeleteResult error on failure
|
||||
*/
|
||||
DeleteResult Delete(std::string_view db_name);
|
||||
const auto storage_path = StorageDir_(db_name);
|
||||
if (!storage_path) return DeleteError::NON_EXISTENT;
|
||||
|
||||
/**
|
||||
* @brief Delete or defer deletion of database.
|
||||
*
|
||||
* @param uuid database UUID
|
||||
* @return DeleteResult error on failure
|
||||
*/
|
||||
DeleteResult Delete(utils::UUID uuid);
|
||||
// Check if db exists
|
||||
try {
|
||||
// Low level handlers
|
||||
if (!db_handler_.Delete(db_name)) {
|
||||
return DeleteError::USING;
|
||||
}
|
||||
} catch (utils::BasicException &) {
|
||||
return DeleteError::NON_EXISTENT;
|
||||
}
|
||||
|
||||
// Remove from durability list
|
||||
if (durability_) durability_->Delete(db_name);
|
||||
|
||||
// Delete disk storage
|
||||
if (delete_on_drop_) {
|
||||
std::error_code ec;
|
||||
(void)std::filesystem::remove_all(*storage_path, ec);
|
||||
if (ec) {
|
||||
spdlog::error("Failed to clean disk while deleting database \"{}\".", db_name);
|
||||
defunct_dbs_.emplace(db_name);
|
||||
return DeleteError::DISK_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from defunct_dbs_ (in case a second delete call was successful)
|
||||
defunct_dbs_.erase(db_name);
|
||||
|
||||
return {}; // Success
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -262,7 +217,7 @@ class DbmsHandler {
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
return db_handler_.All();
|
||||
#else
|
||||
return {db_gatekeeper_.access()->get()->name()};
|
||||
return {db_gatekeeper_.access()->get()->id()};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -272,6 +227,10 @@ class DbmsHandler {
|
||||
bool IsMain() const { return repl_state_.IsMain(); }
|
||||
bool IsReplica() const { return repl_state_.IsReplica(); }
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
coordination::CoordinatorState &CoordinatorState() { return coordinator_state_; }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return the statistics all databases.
|
||||
*
|
||||
@@ -351,7 +310,7 @@ class DbmsHandler {
|
||||
auto db_acc_opt = db_gk.access();
|
||||
if (db_acc_opt) {
|
||||
auto &db_acc = *db_acc_opt;
|
||||
spdlog::debug("Restoring trigger for database \"{}\"", db_acc->name());
|
||||
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);
|
||||
@@ -376,7 +335,7 @@ class DbmsHandler {
|
||||
auto db_acc = db_gk.access();
|
||||
if (db_acc) {
|
||||
auto *db = db_acc->get();
|
||||
spdlog::debug("Restoring streams for database \"{}\"", db->name());
|
||||
spdlog::debug("Restoring streams for database \"{}\"", db->id());
|
||||
db->streams()->RestoreStreams(*db_acc, ic);
|
||||
}
|
||||
}
|
||||
@@ -387,7 +346,7 @@ class DbmsHandler {
|
||||
*
|
||||
* @param f
|
||||
*/
|
||||
void ForEach(std::invocable<DatabaseAccess> auto f) {
|
||||
void ForEach(auto f) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::shared_lock<LockT> rd(lock_);
|
||||
for (auto &[_, db_gk] : db_handler_) {
|
||||
@@ -397,103 +356,33 @@ class DbmsHandler {
|
||||
#endif
|
||||
auto db_acc = db_gk.access();
|
||||
if (db_acc) { // This isn't an error, just a defunct db
|
||||
f(*db_acc);
|
||||
f(db_acc->get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NewSystemTransaction() {
|
||||
DMG_ASSERT(!system_transaction_, "Already running a system transaction");
|
||||
system_transaction_.emplace(++system_timestamp_);
|
||||
}
|
||||
|
||||
void ResetSystemTransaction() { system_transaction_.reset(); }
|
||||
|
||||
//! \tparam RPC An rpc::RequestResponse
|
||||
//! \tparam Args the args type
|
||||
//! \param client the client to use for rpc communication
|
||||
//! \param check predicate to check response is ok
|
||||
//! \param args arguments to forward to the rpc request
|
||||
//! \return If replica stream is completed or enqueued
|
||||
template <typename RPC, typename... Args>
|
||||
bool SteamAndFinalizeDelta(auto &client, auto &&check, Args &&...args) {
|
||||
try {
|
||||
auto stream = client.rpc_client_.template Stream<RPC>(std::forward<Args>(args)...);
|
||||
auto task = [&client, check = std::forward<decltype(check)>(check), stream = std::move(stream)]() mutable {
|
||||
if (stream.IsDefunct()) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (check(stream.AwaitResponse())) {
|
||||
return true;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// swallow error, fallthrough to error handling
|
||||
}
|
||||
// This replica needs SYSTEM recovery
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
};
|
||||
|
||||
if (client.mode_ == memgraph::replication::ReplicationMode::ASYNC) {
|
||||
client.thread_pool_.AddTask([task = utils::CopyMovableFunctionWrapper{std::move(task)}]() mutable { task(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
return task();
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
// This replica needs SYSTEM recovery
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
AllSyncReplicaStatus Commit();
|
||||
|
||||
auto LastCommitedTS() const -> uint64_t { return last_commited_system_timestamp_; }
|
||||
void SetLastCommitedTS(uint64_t new_ts) { last_commited_system_timestamp_.store(new_ts); }
|
||||
|
||||
/**
|
||||
* @brief todo
|
||||
*
|
||||
* @param f
|
||||
*/
|
||||
void ForOne(auto f) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
// When being called by intepreter no need to gain lock, it should already be under a system transaction
|
||||
// But concurrently the FrequentCheck is running and will need to lock before reading last_commited_system_timestamp_
|
||||
template <bool REQUIRE_LOCK = false>
|
||||
void SystemRestore(replication::ReplicationClient &client) {
|
||||
// Check if system is up to date
|
||||
if (client.state_.WithLock(
|
||||
[](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; }))
|
||||
return;
|
||||
|
||||
// Try to recover...
|
||||
{
|
||||
auto [database_configs, last_commited_system_timestamp] = std::invoke([&] {
|
||||
auto sys_guard =
|
||||
std::unique_lock{system_lock_, std::defer_lock}; // ensure no other system transaction in progress
|
||||
if constexpr (REQUIRE_LOCK) {
|
||||
sys_guard.lock();
|
||||
}
|
||||
auto configs = std::vector<storage::SalientConfig>{};
|
||||
ForEach([&configs](DatabaseAccess acc) { configs.emplace_back(acc->config().salient); });
|
||||
return std::pair{configs, last_commited_system_timestamp_.load()};
|
||||
});
|
||||
try {
|
||||
auto stream = client.rpc_client_.Stream<storage::replication::SystemRecoveryRpc>(last_commited_system_timestamp,
|
||||
std::move(database_configs));
|
||||
const auto response = stream.AwaitResponse();
|
||||
if (response.result == storage::replication::SystemRecoveryRes::Result::FAILURE) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
}
|
||||
} catch (memgraph::rpc::GenericRpcFailedException const &e) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
return;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Successfully recovered
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; });
|
||||
}
|
||||
#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
|
||||
@@ -503,7 +392,7 @@ class DbmsHandler {
|
||||
* @param name Database name
|
||||
* @return std::optional<std::filesystem::path>
|
||||
*/
|
||||
std::optional<std::filesystem::path> StorageDir_(std::string_view name) {
|
||||
std::optional<std::filesystem::path> StorageDir_(const std::string &name) {
|
||||
const auto conf = db_handler_.GetConfig(name);
|
||||
if (conf) {
|
||||
return conf->durability.storage_directory;
|
||||
@@ -516,108 +405,105 @@ class DbmsHandler {
|
||||
* @brief Create a new Database associated with the "name" database
|
||||
*
|
||||
* @param name name of the database
|
||||
* @param uuid undelying RocksDB directory
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New_(std::string_view name, utils::UUID uuid, std::optional<std::filesystem::path> rel_dir = {}) {
|
||||
auto config_copy = default_config_;
|
||||
config_copy.salient.name = name;
|
||||
config_copy.salient.uuid = uuid;
|
||||
spdlog::debug("Creating database '{}' - '{}'", name, std::string{uuid});
|
||||
if (rel_dir) {
|
||||
storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / *rel_dir);
|
||||
} else {
|
||||
storage::UpdatePaths(config_copy,
|
||||
default_config_.durability.storage_directory / kMultiTenantDir / std::string{uuid});
|
||||
}
|
||||
return New_(std::move(config_copy));
|
||||
}
|
||||
NewResultT New_(const std::string &name) { return New_(name, name); }
|
||||
|
||||
/**
|
||||
* @brief Create a new Database using the passed configuration
|
||||
* @brief Create a new Database associated with the "name" database
|
||||
*
|
||||
* @param config configuration to be used
|
||||
* @param name name of the database
|
||||
* @param storage_subdir undelying RocksDB directory
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New_(const storage::SalientConfig &config) {
|
||||
NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) {
|
||||
auto config_copy = default_config_;
|
||||
config_copy.salient = config; // name, uuid, mode, etc
|
||||
UpdatePaths(config_copy, config_copy.durability.storage_directory / kMultiTenantDir / std::string{config.uuid});
|
||||
return New_(std::move(config_copy));
|
||||
storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / storage_subdir);
|
||||
return New_(name, config_copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new Database associated with the "name" database
|
||||
*
|
||||
* @param name name of the database
|
||||
* @param storage_config storage configuration
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
NewResultT New_(storage::Config storage_config);
|
||||
NewResultT New_(const std::string &name, storage::Config &storage_config) {
|
||||
if (defunct_dbs_.contains(name)) {
|
||||
spdlog::warn("Failed to generate database due to the unknown state of the previously defunct database \"{}\".",
|
||||
name);
|
||||
return NewError::DEFUNCT;
|
||||
}
|
||||
|
||||
// TODO: new overload of Delete_ with DatabaseAccess
|
||||
DeleteResult Delete_(std::string_view db_name);
|
||||
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?
|
||||
return new_db.GetValue();
|
||||
}
|
||||
return new_db.GetError();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new Database associated with the default database
|
||||
*
|
||||
* @return NewResultT context on success, error on failure
|
||||
*/
|
||||
void SetupDefault_() {
|
||||
try {
|
||||
Get(kDefaultDB);
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
// No default DB restored, create it
|
||||
MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, ".").HasValue(), "Failed while creating the default database");
|
||||
}
|
||||
NewResultT NewDefault_() {
|
||||
// Create the default DB in the root (this is how it was done pre multi-tenancy)
|
||||
auto res = New_(kDefaultDB, "..");
|
||||
if (res.HasValue()) {
|
||||
// For back-compatibility...
|
||||
// Recreate the dbms layout for the default db and symlink to the root
|
||||
const auto dir = StorageDir_(kDefaultDB);
|
||||
MG_ASSERT(dir, "Failed to find storage path.");
|
||||
const auto main_dir = *dir / "databases" / kDefaultDB;
|
||||
|
||||
// For back-compatibility...
|
||||
// Recreate the dbms layout for the default db and symlink to the root
|
||||
const auto dir = StorageDir_(kDefaultDB);
|
||||
MG_ASSERT(dir, "Failed to find storage path.");
|
||||
const auto main_dir = *dir / kMultiTenantDir / kDefaultDB;
|
||||
if (!std::filesystem::exists(main_dir)) {
|
||||
std::filesystem::create_directory(main_dir);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(main_dir)) {
|
||||
std::filesystem::create_directory(main_dir);
|
||||
}
|
||||
// Force link on-disk directories
|
||||
const auto conf = db_handler_.GetConfig(kDefaultDB);
|
||||
MG_ASSERT(conf, "No configuration for the default database.");
|
||||
const auto &tmp_conf = conf->disk;
|
||||
std::vector<std::filesystem::path> to_link{
|
||||
tmp_conf.main_storage_directory, tmp_conf.label_index_directory,
|
||||
tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
|
||||
tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory,
|
||||
tmp_conf.durability_directory, tmp_conf.wal_directory,
|
||||
};
|
||||
|
||||
// Force link on-disk directories
|
||||
const auto conf = db_handler_.GetConfig(kDefaultDB);
|
||||
MG_ASSERT(conf, "No configuration for the default database.");
|
||||
const auto &tmp_conf = conf->disk;
|
||||
std::vector<std::filesystem::path> to_link{
|
||||
tmp_conf.main_storage_directory, tmp_conf.label_index_directory,
|
||||
tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
|
||||
tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory,
|
||||
tmp_conf.durability_directory, tmp_conf.wal_directory,
|
||||
};
|
||||
// Add in-memory paths
|
||||
// Some directories are redundant (skip those)
|
||||
const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
|
||||
for (auto const &item : std::filesystem::directory_iterator{*dir}) {
|
||||
const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
|
||||
if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
|
||||
to_link.push_back(item.path());
|
||||
}
|
||||
|
||||
// Add in-memory paths
|
||||
// Some directories are redundant (skip those)
|
||||
const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
|
||||
for (auto const &item : std::filesystem::directory_iterator{*dir}) {
|
||||
const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
|
||||
if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
|
||||
to_link.push_back(item.path());
|
||||
}
|
||||
|
||||
// Symlink to root dir
|
||||
for (auto const &item : to_link) {
|
||||
const auto dir_name = std::filesystem::relative(item, item.parent_path());
|
||||
const auto link = main_dir / dir_name;
|
||||
const auto to = std::filesystem::relative(item, main_dir);
|
||||
if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
|
||||
std::filesystem::create_directory_symlink(to, link);
|
||||
} else { // Check existing link
|
||||
std::error_code ec;
|
||||
const auto test_link = std::filesystem::read_symlink(link, ec);
|
||||
if (ec || test_link != to) {
|
||||
MG_ASSERT(false,
|
||||
"Memgraph storage directory incompatible with new version.\n"
|
||||
"Please use a clean directory or remove \"{}\" and try again.",
|
||||
link.string());
|
||||
// Symlink to root dir
|
||||
for (auto const &item : to_link) {
|
||||
const auto dir_name = std::filesystem::relative(item, item.parent_path());
|
||||
const auto link = main_dir / dir_name;
|
||||
const auto to = std::filesystem::relative(item, main_dir);
|
||||
if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
|
||||
std::filesystem::create_directory_symlink(to, link);
|
||||
} else { // Check existing link
|
||||
std::error_code ec;
|
||||
const auto test_link = std::filesystem::read_symlink(link, ec);
|
||||
if (ec || test_link != to) {
|
||||
MG_ASSERT(false,
|
||||
"Memgraph storage directory incompatible with new version.\n"
|
||||
"Please use a clean directory or remove \"{}\" and try again.",
|
||||
link.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,56 +521,18 @@ class DbmsHandler {
|
||||
throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the context associated with the UUID database
|
||||
*
|
||||
* @param uuid
|
||||
* @return DatabaseAccess
|
||||
* @throw UnknownDatabaseException if database not found
|
||||
*/
|
||||
DatabaseAccess Get_(const utils::UUID &uuid) {
|
||||
// TODO Speed up
|
||||
for (auto &[_, db_gk] : db_handler_) {
|
||||
auto acc = db_gk.access();
|
||||
if (acc->get()->uuid() == uuid) {
|
||||
return std::move(*acc);
|
||||
}
|
||||
}
|
||||
throw UnknownDatabaseException("Tried to retrieve an unknown database with UUID \"{}\".", std::string{uuid});
|
||||
}
|
||||
#endif
|
||||
|
||||
void RecoverReplication(DatabaseAccess db_acc) {
|
||||
if (allow_mt_repl || db_acc->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_, std::move(db_acc));
|
||||
} 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!");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
mutable LockT lock_{utils::RWLock::Priority::READ}; //!< protective lock
|
||||
storage::Config default_config_; //!< Storage configuration used when creating new databases
|
||||
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
|
||||
coordination::CoordinatorState coordinator_state_; //!< Replication coordinator
|
||||
#endif
|
||||
// TODO: Make an api
|
||||
public:
|
||||
utils::ResourceLock system_lock_{}; //!> Ensure exclusive access for system queries
|
||||
private:
|
||||
std::optional<SystemTransaction> system_transaction_; //!< Current system transaction (only one at a time)
|
||||
uint64_t system_timestamp_{storage::kTimestampInitialId}; //!< System timestamp
|
||||
std::atomic_uint64_t last_commited_system_timestamp_{
|
||||
storage::kTimestampInitialId}; //!< Last commited system timestamp
|
||||
replication::ReplicationState repl_state_; //!< Global replication state
|
||||
#ifndef MG_ENTERPRISE
|
||||
mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper
|
||||
#endif
|
||||
}; // namespace memgraph::dbms
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/gatekeeper.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
@@ -83,7 +82,7 @@ class Handler {
|
||||
* @return true on success
|
||||
* @throw BasicException
|
||||
*/
|
||||
bool TryDelete(std::string_view name) {
|
||||
bool Delete(const std::string &name) {
|
||||
if (auto itr = items_.find(name); itr != items_.end()) {
|
||||
auto db_acc = itr->second.access();
|
||||
if (db_acc && db_acc->try_delete()) {
|
||||
@@ -93,42 +92,9 @@ class Handler {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// TODO: Change to return enum
|
||||
throw utils::BasicException("Unknown item \"{}\".", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delete or defunct the context associated with the name.
|
||||
*
|
||||
* @param name Name associated with the context to delete
|
||||
* @param post_delete_func What to do after deletion has happened
|
||||
*/
|
||||
template <typename Func>
|
||||
void DeferDelete(std::string_view name, Func &&post_delete_func) {
|
||||
auto itr = items_.find(name);
|
||||
if (itr == items_.end()) return;
|
||||
|
||||
auto db_acc = itr->second.access();
|
||||
if (!db_acc) return;
|
||||
|
||||
if (db_acc->try_delete()) {
|
||||
// Delete the database now
|
||||
db_acc->reset();
|
||||
post_delete_func();
|
||||
} else {
|
||||
// Defer deletion
|
||||
db_acc->reset();
|
||||
// TODO: Make sure this shuts down correctly
|
||||
auto task = [gk = std::move(itr->second), post_delete_func = std::forward<Func>(post_delete_func)]() mutable {
|
||||
gk.~Gatekeeper<T>();
|
||||
post_delete_func();
|
||||
};
|
||||
defer_pool_.AddTask(utils::CopyMovableFunctionWrapper{std::move(task)});
|
||||
}
|
||||
// In any case remove from handled map
|
||||
items_.erase(itr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a name is already used.
|
||||
*
|
||||
@@ -154,7 +120,6 @@ class Handler {
|
||||
private:
|
||||
std::unordered_map<std::string, utils::Gatekeeper<T>, string_hash, std::equal_to<>>
|
||||
items_; //!< map to all active items
|
||||
utils::ThreadPool defer_pool_{1};
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
@@ -50,29 +49,29 @@ std::pair<uint64_t, WalDeltaData> ReadDelta(storage::durability::BaseDecoder *de
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, const utils::UUID &uuid) {
|
||||
std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, std::string_view db_name) {
|
||||
try {
|
||||
#ifdef MG_ENTERPRISE
|
||||
auto acc = dbms_handler->Get(uuid);
|
||||
if (!acc) {
|
||||
spdlog::error("Failed to get access to UUID ", std::string{uuid});
|
||||
return std::nullopt;
|
||||
}
|
||||
auto acc = dbms_handler->Get(db_name);
|
||||
#else
|
||||
auto acc = dbms_handler->Get();
|
||||
if (!acc) {
|
||||
spdlog::warn("Failed to get access to the default db.");
|
||||
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.");
|
||||
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 with UUID \"{}\" on replica!", std::string{uuid});
|
||||
spdlog::warn("No database \"{}\" on replica!", db_name);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@@ -110,16 +109,13 @@ void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handl
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::HeartbeatReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::HeartbeatRes res{false, 0, ""};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
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{true, storage->repl_storage_state_.last_commit_timestamp_.load(),
|
||||
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);
|
||||
}
|
||||
@@ -128,12 +124,8 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::AppendDeltasReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::AppendDeltasRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
||||
if (!db_acc) return;
|
||||
|
||||
storage::replication::Decoder decoder(req_reader);
|
||||
|
||||
@@ -173,7 +165,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
||||
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
||||
}
|
||||
|
||||
storage::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;
|
||||
}
|
||||
@@ -182,7 +174,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha
|
||||
storage, &decoder,
|
||||
storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating
|
||||
|
||||
storage::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!");
|
||||
}
|
||||
@@ -191,12 +183,8 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::SnapshotReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::SnapshotRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
||||
if (!db_acc) return;
|
||||
|
||||
storage::replication::Decoder decoder(req_reader);
|
||||
|
||||
@@ -244,7 +232,8 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle
|
||||
}
|
||||
storage_guard.unlock();
|
||||
|
||||
storage::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.");
|
||||
@@ -274,12 +263,8 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::WalFilesReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::WalFilesRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
@@ -293,7 +278,8 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle
|
||||
LoadWal(storage, &decoder);
|
||||
}
|
||||
|
||||
storage::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!");
|
||||
}
|
||||
@@ -302,12 +288,8 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::CurrentWalReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::CurrentWalRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name);
|
||||
if (!db_acc) return;
|
||||
|
||||
storage::replication::Decoder decoder(req_reader);
|
||||
|
||||
@@ -316,7 +298,8 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand
|
||||
|
||||
LoadWal(storage, &decoder);
|
||||
|
||||
storage::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!");
|
||||
}
|
||||
@@ -335,8 +318,6 @@ void InMemoryReplicationHandlers::LoadWal(storage::InMemoryStorage *storage, sto
|
||||
}
|
||||
auto &replica_epoch = storage->repl_storage_state_.epoch_;
|
||||
if (wal_info.epoch_id != replica_epoch.id()) {
|
||||
// questionable behaviour, we trust that any change in epoch implies change in who is MAIN
|
||||
// when we use high availability, this assumption need to be checked.
|
||||
auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id);
|
||||
storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch);
|
||||
}
|
||||
@@ -374,16 +355,13 @@ void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handl
|
||||
slk::Builder *res_builder) {
|
||||
storage::replication::TimestampReq req;
|
||||
slk::Load(&req, req_reader);
|
||||
auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid);
|
||||
if (!db_acc) {
|
||||
storage::replication::TimestampRes res{false, 0};
|
||||
slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
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::TimestampRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||
storage::replication::TimestampRes res{storage->id(), true,
|
||||
storage->repl_storage_state_.last_commit_timestamp_.load()};
|
||||
slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
@@ -527,7 +505,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
||||
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_.salient.items.properties_on_edges)
|
||||
if (!storage->config_.items.properties_on_edges)
|
||||
throw utils::BasicException(
|
||||
"Can't set properties on edges because properties on edges "
|
||||
"are disabled!");
|
||||
@@ -594,8 +572,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage
|
||||
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(
|
||||
{.desired_commit_timestamp = commit_timestamp_and_accessor->first, .is_main = false});
|
||||
auto ret =
|
||||
commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first, false /* not main */);
|
||||
if (ret.HasError())
|
||||
throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__);
|
||||
commit_timestamp_and_accessor = std::nullopt;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "replication/replication_server.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "storage/v2/replication/serialization.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace memgraph::dbms {
|
||||
|
||||
inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(storage::Config config,
|
||||
::memgraph::replication::ReplicationState &repl_state) {
|
||||
const auto name = config.salient.name;
|
||||
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
|
||||
@@ -33,6 +34,24 @@ inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(storage::Config c
|
||||
return storage->CreateSnapshot(repl_state.GetRole());
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -10,7 +10,6 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
@@ -18,26 +17,18 @@ void StartReplicaClient(DbmsHandler &dbms_handler, replication::ReplicationClien
|
||||
// No client error, start instance level client
|
||||
auto const &endpoint = client.rpc_client_.Endpoint();
|
||||
spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port);
|
||||
client.StartFrequentCheck([&dbms_handler](bool reconnect, replication::ReplicationClient &client) {
|
||||
// Working connection
|
||||
// Check if system needs restoration
|
||||
if (reconnect) {
|
||||
client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; });
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
dbms_handler.SystemRestore<true>(client);
|
||||
#endif
|
||||
// Check if any database has been left behind
|
||||
dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) {
|
||||
client.StartFrequentCheck([&dbms_handler](std::string_view name) {
|
||||
// Working connection, check if any database has been left behind
|
||||
dbms_handler.ForEach([name](dbms::Database *db) {
|
||||
// Specific database <-> replica client
|
||||
db_acc->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
|
||||
if (reconnect || client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
db->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) {
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
// Database <-> replica might be behind, check and recover
|
||||
client->TryCheckReplicaStateAsync(db_acc->storage(), db_acc);
|
||||
client->TryCheckReplicaStateAsync(db->storage());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} // namespace memgraph::dbms
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -11,21 +11,14 @@
|
||||
|
||||
#include "dbms/replication_handler.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "dbms/constants.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "dbms/global.hpp"
|
||||
#include "dbms/inmemory/replication_handlers.hpp"
|
||||
#include "dbms/inmemory/storage_helper.hpp"
|
||||
#include "dbms/replication_client.hpp"
|
||||
#include "dbms/utils.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/replication/rpc.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
|
||||
using memgraph::replication::ReplicationClientConfig;
|
||||
using memgraph::replication::ReplicationState;
|
||||
using memgraph::replication::RoleMainData;
|
||||
using memgraph::replication::RoleReplicaData;
|
||||
|
||||
@@ -51,34 +44,13 @@ std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
|
||||
ReplicationHandler::ReplicationHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||
|
||||
bool ReplicationHandler::SetReplicationRoleMain() {
|
||||
auto const main_handler = [](RoleMainData const &) {
|
||||
auto const main_handler = [](RoleMainData &) {
|
||||
// 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([](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
// Remember old epoch + storage timestamp association
|
||||
storage->PrepareForNewEpoch();
|
||||
});
|
||||
|
||||
// STEP 2) Change to MAIN
|
||||
// TODO: restore replication servers if false?
|
||||
if (!dbms_handler_.ReplicationState().SetReplicationRoleMain()) {
|
||||
// TODO: Handle recovery on failure???
|
||||
return false;
|
||||
}
|
||||
|
||||
// STEP 3) We are now MAIN, update storage local epoch
|
||||
const auto &epoch =
|
||||
std::get<RoleMainData>(std::as_const(dbms_handler_.ReplicationState()).ReplicationData()).epoch_;
|
||||
dbms_handler_.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
storage->repl_storage_state_.epoch_ = epoch;
|
||||
});
|
||||
|
||||
return true;
|
||||
return memgraph::dbms::DoReplicaToMainPromotion(dbms_handler_);
|
||||
};
|
||||
|
||||
// TODO: under lock
|
||||
@@ -95,8 +67,8 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
|
||||
// TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
|
||||
// deleting the replica.
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
dbms_handler_.ForEach([&](Database *db) {
|
||||
auto *storage = db->storage();
|
||||
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
|
||||
});
|
||||
// Remove instance level clients
|
||||
@@ -111,7 +83,15 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[this](RoleReplicaData const &data) { return StartRpcServer(dbms_handler_, data); }},
|
||||
[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;
|
||||
}},
|
||||
dbms_handler_.ReplicationState().ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
@@ -122,61 +102,17 @@ auto ReplicationHandler::RegisterReplica(const memgraph::replication::Replicatio
|
||||
MG_ASSERT(dbms_handler_.ReplicationState().IsMain(), "Only main instance can register a replica!");
|
||||
|
||||
auto instance_client = dbms_handler_.ReplicationState().RegisterReplica(config);
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
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;
|
||||
}
|
||||
|
||||
const auto dbms_error = memgraph::dbms::HandleErrorOnReplicaClient(instance_client);
|
||||
if (dbms_error.has_value()) {
|
||||
return *dbms_error;
|
||||
}
|
||||
|
||||
if (!allow_mt_repl && dbms_handler_.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Update system before enabling individual storage <-> replica clients
|
||||
dbms_handler_.SystemRestore(*instance_client.GetValue());
|
||||
#endif
|
||||
|
||||
bool all_clients_good = true;
|
||||
|
||||
// Add database specific clients (NOTE Currently all databases are connected to each replica)
|
||||
dbms_handler_.ForEach([&](DatabaseAccess db_acc) {
|
||||
auto *storage = db_acc->storage();
|
||||
if (!allow_mt_repl && storage->name() != 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, &instance_client, db_acc = std::move(db_acc)](auto &storage_clients) mutable { // NOLINT
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(*instance_client.GetValue());
|
||||
// All good, start replica client
|
||||
client->Start(storage, std::move(db_acc));
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due an error like branching of MAIN and REPLICA
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
return false;
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
auto &instance_client_ptr = instance_client.GetValue();
|
||||
const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler_, *instance_client_ptr);
|
||||
|
||||
// NOTE Currently if any databases fails, we revert back
|
||||
if (!all_clients_good) {
|
||||
spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name);
|
||||
spdlog::error("Failed to register all databases to the REPLICA \"{}\"", config.name);
|
||||
UnregisterReplica(config.name);
|
||||
return RegisterReplicaError::CONNECTION_FAILED;
|
||||
}
|
||||
@@ -195,8 +131,8 @@ auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterR
|
||||
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
|
||||
}
|
||||
// Remove database specific clients
|
||||
dbms_handler_.ForEach([name](DatabaseAccess db_acc) {
|
||||
db_acc->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
|
||||
dbms_handler_.ForEach([name](Database *db) {
|
||||
db->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) {
|
||||
std::erase_if(clients, [name](const auto &client) { return client->Name() == name; });
|
||||
});
|
||||
});
|
||||
@@ -220,20 +156,20 @@ bool ReplicationHandler::IsReplica() const { return dbms_handler_.ReplicationSta
|
||||
|
||||
// Per storage
|
||||
// NOTE Storage will connect to all replicas. Future work might change this
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc) {
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage) {
|
||||
spdlog::info("Restoring replication role.");
|
||||
|
||||
/// MAIN
|
||||
auto const recover_main = [db_acc = std::move(db_acc)](RoleMainData &mainData) mutable { // NOLINT
|
||||
auto const recover_main = [&storage](RoleMainData &mainData) {
|
||||
// Each individual client has already been restored and started. Here we just go through each database and start its
|
||||
// client
|
||||
for (auto &instance_client : mainData.registered_replicas_) {
|
||||
spdlog::info("Replica {} restoration started for {}.", instance_client.name_, db_acc->name());
|
||||
const auto &ret = db_acc->storage()->repl_storage_state_.replication_clients_.WithLock(
|
||||
[&, db_acc](auto &storage_clients) mutable -> utils::BasicResult<RegisterReplicaError> {
|
||||
spdlog::info("Replica {} restoration started for {}.", instance_client.name_, storage.id());
|
||||
|
||||
const auto &ret = storage.repl_storage_state_.replication_clients_.WithLock(
|
||||
[&](auto &storage_clients) -> utils::BasicResult<RegisterReplicaError> {
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
|
||||
auto *storage = db_acc->storage();
|
||||
client->Start(storage, std::move(db_acc));
|
||||
client->Start(&storage);
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due to branching of MAIN and REPLICA
|
||||
@@ -250,7 +186,7 @@ void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAcces
|
||||
LOG_FATAL("Failure when restoring replica {}: {}.", instance_client.name_,
|
||||
RegisterReplicaErrorToString(ret.GetError()));
|
||||
}
|
||||
spdlog::info("Replica {} restored for {}.", instance_client.name_, db_acc->name());
|
||||
spdlog::info("Replica {} restored for {}.", instance_client.name_, storage.id());
|
||||
}
|
||||
spdlog::info("Replication role restored to MAIN.");
|
||||
};
|
||||
@@ -265,177 +201,4 @@ void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAcces
|
||||
},
|
||||
repl_state.ReplicationData());
|
||||
}
|
||||
|
||||
namespace system_replication {
|
||||
#ifdef MG_ENTERPRISE
|
||||
void SystemHeartbeatHandler(const uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
replication::SystemHeartbeatReq req;
|
||||
replication::SystemHeartbeatReq::Load(&req, req_reader);
|
||||
|
||||
replication::SystemHeartbeatRes res(ts);
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
memgraph::storage::replication::CreateDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::CreateDatabaseRes;
|
||||
CreateDatabaseRes res(CreateDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
|
||||
spdlog::debug("CreateDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
dbms_handler.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new
|
||||
auto new_db = dbms_handler.Update(req.config);
|
||||
if (new_db.HasValue()) {
|
||||
// Successfully create db
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = CreateDatabaseRes(CreateDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("CreateDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void DropDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
memgraph::storage::replication::DropDatabaseReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::DropDatabaseRes;
|
||||
DropDatabaseRes res(DropDatabaseRes::Result::FAILURE);
|
||||
|
||||
// Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot
|
||||
// of the set of databases. Hence no history exists to maintain regarding epoch change.
|
||||
// If MAIN has changed we need to check this new group_timestamp is consistent with
|
||||
// what we have so far.
|
||||
|
||||
if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) {
|
||||
spdlog::debug("DropDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp,
|
||||
dbms_handler.LastCommitedTS());
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// NOTE: Single communication channel can exist at a time, no other database can be deleted/created aat the moment.
|
||||
auto new_db = dbms_handler.Delete(req.uuid);
|
||||
if (new_db.HasError()) {
|
||||
if (new_db.GetError() == DeleteError::NON_EXISTENT) {
|
||||
// Nothing to drop
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::NO_NEED);
|
||||
}
|
||||
} else {
|
||||
// Successfully drop db
|
||||
dbms_handler.SetLastCommitedTS(req.new_group_timestamp);
|
||||
res = DropDatabaseRes(DropDatabaseRes::Result::SUCCESS);
|
||||
spdlog::debug("DropDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp);
|
||||
}
|
||||
} catch (...) {
|
||||
// Failure
|
||||
}
|
||||
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
// TODO Speed up
|
||||
memgraph::storage::replication::SystemRecoveryReq req;
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
|
||||
using memgraph::storage::replication::SystemRecoveryRes;
|
||||
SystemRecoveryRes res(SystemRecoveryRes::Result::FAILURE);
|
||||
|
||||
utils::OnScopeExit send_on_exit([&]() { memgraph::slk::Save(res, res_builder); });
|
||||
|
||||
// Get all current dbs
|
||||
auto old = dbms_handler.All();
|
||||
|
||||
// Check/create the incoming dbs
|
||||
for (const auto &config : req.database_configs) {
|
||||
// Missing db
|
||||
try {
|
||||
if (dbms_handler.Update(config).HasError()) {
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to update database \"{}\".", config.name);
|
||||
return; // Send failure on exit
|
||||
}
|
||||
} catch (const UnknownDatabaseException &) {
|
||||
spdlog::debug("SystemRecoveryHandler: UnknownDatabaseException");
|
||||
return; // Send failure on exit
|
||||
}
|
||||
const auto it = std::find(old.begin(), old.end(), config.name);
|
||||
if (it != old.end()) old.erase(it);
|
||||
}
|
||||
|
||||
// Delete all the leftover old dbs
|
||||
for (const auto &remove_db : old) {
|
||||
const auto del = dbms_handler.Delete(remove_db);
|
||||
if (del.HasError()) {
|
||||
// Some errors are not terminal
|
||||
if (del.GetError() == DeleteError::DEFAULT_DB || del.GetError() == DeleteError::NON_EXISTENT) {
|
||||
spdlog::debug("SystemRecoveryHandler: Dropped database \"{}\".", remove_db);
|
||||
continue;
|
||||
}
|
||||
spdlog::debug("SystemRecoveryHandler: Failed to drop database \"{}\".", remove_db);
|
||||
return; // Send failure on exit
|
||||
}
|
||||
}
|
||||
// Successfully recovered
|
||||
dbms_handler.SetLastCommitedTS(req.forced_group_timestamp);
|
||||
spdlog::debug("SystemRecoveryHandler: SUCCESS updated LCTS to {}", req.forced_group_timestamp);
|
||||
res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemHeartbeatRpc");
|
||||
SystemHeartbeatHandler(dbms_handler.LastCommitedTS(), req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::CreateDatabaseRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received CreateDatabaseRpc");
|
||||
CreateDatabaseHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::DropDatabaseRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received DropDatabaseRpc");
|
||||
DropDatabaseHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
data.server->rpc_server_.Register<storage::replication::SystemRecoveryRpc>(
|
||||
[&dbms_handler](auto *req_reader, auto *res_builder) {
|
||||
spdlog::debug("Received SystemRecoveryRpc");
|
||||
SystemRecoveryHandler(dbms_handler, req_reader, res_builder);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
} // namespace system_replication
|
||||
|
||||
bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data) {
|
||||
// Register handlers
|
||||
InMemoryReplicationHandlers::Register(&dbms_handler, *data.server);
|
||||
system_replication::Register(data, dbms_handler);
|
||||
// Start server
|
||||
if (!data.server->Start()) {
|
||||
spdlog::error("Unable to start the replication server.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dbms/database.hpp"
|
||||
#include "replication/role.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
// BEGIN fwd declares
|
||||
namespace memgraph::replication {
|
||||
struct ReplicationState;
|
||||
struct ReplicationServerConfig;
|
||||
@@ -23,9 +22,11 @@ struct ReplicationClientConfig;
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
class DbmsHandler;
|
||||
|
||||
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
|
||||
|
||||
enum class UnregisterReplicaResult : uint8_t {
|
||||
NOT_MAIN,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
@@ -62,20 +63,6 @@ struct ReplicationHandler {
|
||||
|
||||
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
|
||||
/// TODO: extend to do multiple storages
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc);
|
||||
|
||||
namespace system_replication {
|
||||
// System handlers
|
||||
#ifdef MG_ENTERPRISE
|
||||
void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SystemHeartbeatHandler(uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
#endif
|
||||
|
||||
/// Register all DBMS level RPC handlers
|
||||
void Register(replication::RoleReplicaData const &data, DbmsHandler &dbms_handler);
|
||||
} // namespace system_replication
|
||||
|
||||
bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data);
|
||||
void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage);
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2024 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 <memory>
|
||||
#include <optional>
|
||||
#include "auth/models.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
struct SystemTransaction {
|
||||
struct Delta {
|
||||
enum class Action {
|
||||
CREATE_DATABASE,
|
||||
DROP_DATABASE,
|
||||
UPDATE_AUTH_DATA,
|
||||
DROP_AUTH_DATA,
|
||||
/**
|
||||
*
|
||||
* CREATE USER user_name [IDENTIFIED BY 'password'];
|
||||
* SET PASSWORD FOR user_name TO 'new_password';
|
||||
* ^ SaveUser
|
||||
*
|
||||
* DROP USER user_name;
|
||||
* ^ Directly on KVStore
|
||||
*
|
||||
* CREATE ROLE role_name;
|
||||
* ^ SaveRole
|
||||
*
|
||||
* DROP ROLE
|
||||
* ^ RemoveRole
|
||||
*
|
||||
* SET ROLE FOR user_name TO role_name;
|
||||
* CLEAR ROLE FOR user_name;
|
||||
* ^ Do stuff then do SaveUser
|
||||
*
|
||||
* GRANT privilege_list TO user_or_role;
|
||||
* DENY AUTH, INDEX TO moderator:
|
||||
* REVOKE AUTH, INDEX TO moderator:
|
||||
* GRANT permission_level ON (LABELS | EDGE_TYPES) label_list TO user_or_role;
|
||||
* REVOKE (LABELS | EDGE_TYPES) label_or_edge_type_list FROM user_or_role
|
||||
* DENY (LABELS | EDGE_TYPES) label_or_edge_type_list TO user_or_role
|
||||
* ^ all of these are EditPermissions <-> SaveUser/Role
|
||||
*
|
||||
* Multi-tenant TODO Doc;
|
||||
* ^ Should all call SaveUser
|
||||
*
|
||||
*/
|
||||
};
|
||||
|
||||
static constexpr struct CreateDatabase {
|
||||
} create_database;
|
||||
static constexpr struct DropDatabase {
|
||||
} drop_database;
|
||||
static constexpr struct UpdateAuthData {
|
||||
} update_auth_data;
|
||||
static constexpr struct DropAuthData {
|
||||
} drop_auth_data;
|
||||
|
||||
// Multi-tenancy
|
||||
Delta(CreateDatabase /*tag*/, storage::SalientConfig config)
|
||||
: action(Action::CREATE_DATABASE), config(std::move(config)) {}
|
||||
Delta(DropDatabase /*tag*/, const utils::UUID &uuid) : action(Action::DROP_DATABASE), uuid(uuid) {}
|
||||
|
||||
// Auth
|
||||
Delta(UpdateAuthData /*tag*/, std::optional<auth::User> user)
|
||||
: action(Action::UPDATE_AUTH_DATA), auth_data{std::move(user), std::nullopt} {}
|
||||
Delta(UpdateAuthData /*tag*/, std::optional<auth::Role> role)
|
||||
: action(Action::UPDATE_AUTH_DATA), auth_data{std::nullopt, std::move(role)} {}
|
||||
Delta(DropAuthData /*tag*/, std::string_view auth_data_key)
|
||||
: action(Action::DROP_AUTH_DATA), auth_data_key{auth_data_key} {}
|
||||
|
||||
// Generic
|
||||
Delta(const Delta &) = delete;
|
||||
Delta(Delta &&) = delete;
|
||||
Delta &operator=(const Delta &) = delete;
|
||||
Delta &operator=(Delta &&) = delete;
|
||||
|
||||
~Delta() {
|
||||
switch (action) {
|
||||
case Action::CREATE_DATABASE:
|
||||
std::destroy_at(&config);
|
||||
break;
|
||||
case Action::DROP_DATABASE:
|
||||
std::destroy_at(&uuid);
|
||||
break;
|
||||
case Action::UPDATE_AUTH_DATA:
|
||||
std::destroy_at(&auth_data);
|
||||
break;
|
||||
case Action::DROP_AUTH_DATA:
|
||||
std::destroy_at(&auth_data_key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Action action;
|
||||
union {
|
||||
storage::SalientConfig config;
|
||||
utils::UUID uuid;
|
||||
struct {
|
||||
std::optional<auth::User> user;
|
||||
std::optional<auth::Role> role;
|
||||
} auth_data;
|
||||
std::string auth_data_key;
|
||||
};
|
||||
};
|
||||
|
||||
explicit SystemTransaction(uint64_t timestamp) : system_timestamp(timestamp) {}
|
||||
|
||||
// Currently system transitions support a single delta
|
||||
std::optional<Delta> delta{}; // TODO Vector
|
||||
uint64_t system_timestamp;
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
136
src/dbms/utils.hpp
Normal file
136
src/dbms/utils.hpp
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2024 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 "dbms/dbms_handler.hpp"
|
||||
#include "dbms/replication_handler.hpp"
|
||||
#include "replication/include/replication/state.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
inline bool DoReplicaToMainPromotion(dbms::DbmsHandler &dbms_handler) {
|
||||
auto &repl_state = dbms_handler.ReplicationState();
|
||||
// STEP 1) Prepare for new epoch
|
||||
dbms_handler.ForEach([](Database *database) {
|
||||
auto *storage = database->storage();
|
||||
// Remember old epoch + storage timestamp association
|
||||
storage->PrepareForNewEpoch();
|
||||
});
|
||||
|
||||
// STEP 2) Change to MAIN = Kill replication server
|
||||
if (!repl_state.SetReplicationRoleMain()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// STEP 3) We are now MAIN, update storage local epoch
|
||||
const auto &epoch = std::get<replication::RoleMainData>(std::as_const(repl_state).ReplicationData()).epoch_;
|
||||
dbms_handler.ForEach([&epoch](Database *database) {
|
||||
auto *storage = database->storage();
|
||||
storage->repl_storage_state_.epoch_ = epoch;
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
inline bool SetReplicationRoleReplica(dbms::DbmsHandler &dbms_handler,
|
||||
const memgraph::replication::ReplicationServerConfig &config) {
|
||||
// We don't want to restart the server if we're already a REPLICA
|
||||
if (dbms_handler.ReplicationState().IsReplica()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are
|
||||
// deleting the replica.
|
||||
// Remove database specific clients
|
||||
dbms_handler.ForEach([&](Database *db) {
|
||||
auto *storage = db->storage();
|
||||
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
|
||||
});
|
||||
// Remove instance level clients
|
||||
std::get<replication::RoleMainData>(dbms_handler.ReplicationState().ReplicationData()).registered_replicas_.clear();
|
||||
|
||||
// Creates the server
|
||||
dbms_handler.ReplicationState().SetReplicationRoleReplica(config);
|
||||
|
||||
// Start
|
||||
const auto success =
|
||||
std::visit(utils::Overloaded{[](replication::RoleMainData const &) {
|
||||
// ASSERT
|
||||
return false;
|
||||
},
|
||||
[&dbms_handler](replication::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;
|
||||
}},
|
||||
dbms_handler.ReplicationState().ReplicationData());
|
||||
// TODO Handle error (restore to main?)
|
||||
return success;
|
||||
}
|
||||
|
||||
inline bool RegisterAllDatabasesClients(dbms::DbmsHandler &dbms_handler,
|
||||
replication::ReplicationClient &instance_client) {
|
||||
if (!allow_mt_repl && dbms_handler.All().size() > 1) {
|
||||
spdlog::warn("Multi-tenant replication is currently not supported!");
|
||||
}
|
||||
|
||||
bool all_clients_good = true;
|
||||
|
||||
// Add database specific clients (NOTE Currently all databases are connected to each replica)
|
||||
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, &instance_client](auto &storage_clients) {
|
||||
auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client);
|
||||
client->Start(storage);
|
||||
// After start the storage <-> replica state should be READY or RECOVERING (if correctly started)
|
||||
// MAYBE_BEHIND isn't a statement of the current state, this is the default value
|
||||
// Failed to start due to branching of MAIN and REPLICA
|
||||
if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) {
|
||||
return false;
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return all_clients_good;
|
||||
}
|
||||
|
||||
inline std::optional<RegisterReplicaError> HandleErrorOnReplicaClient(
|
||||
utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client) {
|
||||
if (instance_client.HasError()) switch (instance_client.GetError()) {
|
||||
case replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
case replication::RegisterReplicaError::NAME_EXISTS:
|
||||
return dbms::RegisterReplicaError::NAME_EXISTS;
|
||||
case replication::RegisterReplicaError::END_POINT_EXISTS:
|
||||
return dbms::RegisterReplicaError::END_POINT_EXISTS;
|
||||
case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -6,6 +6,7 @@ add_library(mg-flags STATIC audit.cpp
|
||||
memory_limit.cpp
|
||||
run_time_configurable.cpp
|
||||
storage_mode.cpp
|
||||
query.cpp)
|
||||
query.cpp
|
||||
replication.cpp)
|
||||
target_include_directories(mg-flags PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(mg-flags PUBLIC spdlog::spdlog mg-settings mg-utils)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -17,5 +17,6 @@
|
||||
#include "flags/log_level.hpp"
|
||||
#include "flags/memory_limit.hpp"
|
||||
#include "flags/query.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
#include "flags/run_time_configurable.hpp"
|
||||
#include "flags/storage_mode.hpp"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -131,6 +131,12 @@ DEFINE_uint64(storage_recovery_thread_count,
|
||||
DEFINE_bool(storage_enable_schema_metadata, false,
|
||||
"Controls whether metadata should be collected about the resident labels and edge types.");
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(storage_delete_on_drop, true,
|
||||
"If set to true the query 'DROP DATABASE x' will delete the underlying storage as well.");
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(telemetry_enabled, false,
|
||||
"Set to true to enable telemetry. We collect information about the "
|
||||
@@ -156,13 +162,6 @@ DEFINE_string(pulsar_service_url, "", "Default URL used while connecting to Puls
|
||||
|
||||
// Query flags.
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint64(replication_replica_check_frequency_sec, 1,
|
||||
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: "
|
||||
"The MAIN instance allocates a new thread for each REPLICA.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica");
|
||||
|
||||
DEFINE_VALIDATED_string(query_modules_directory, "",
|
||||
"Directory where modules with custom query procedures are stored. "
|
||||
"NOTE: Multiple comma-separated directories can be defined.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -84,6 +84,10 @@ DECLARE_bool(storage_parallel_schema_recovery);
|
||||
DECLARE_uint64(storage_recovery_thread_count);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(storage_enable_schema_metadata);
|
||||
#ifdef MG_ENTERPRISE
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(storage_delete_on_drop);
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(telemetry_enabled);
|
||||
@@ -112,11 +116,6 @@ namespace memgraph::flags {
|
||||
auto ParseQueryModulesDirectory() -> std::vector<std::filesystem::path>;
|
||||
} // namespace memgraph::flags
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint64(replication_replica_check_frequency_sec);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(replication_restore_state_on_startup);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_string(license_key);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
26
src/flags/replication.cpp
Normal file
26
src/flags/replication.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 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.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(coordinator, false, "Controls whether the instance is a replication coordinator.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(coordinator_server_port, 0, "Port on which coordinator servers will be started.");
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint64(replication_replica_check_frequency_sec, 1,
|
||||
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: "
|
||||
"The MAIN instance allocates a new thread for each REPLICA.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica");
|
||||
26
src/flags/replication.hpp
Normal file
26
src/flags/replication.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 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 "gflags/gflags.h"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(coordinator);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint32(coordinator_server_port);
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_uint64(replication_replica_check_frequency_sec);
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DECLARE_bool(replication_restore_state_on_startup);
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -17,14 +17,20 @@
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
inline constexpr std::array storage_mode_mappings{
|
||||
std::pair{std::string_view{"IN_MEMORY_TRANSACTIONAL"}, memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL},
|
||||
std::pair{std::string_view{"IN_MEMORY_ANALYTICAL"}, memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL},
|
||||
std::pair{std::string_view{"ON_DISK_TRANSACTIONAL"}, memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL}};
|
||||
|
||||
const std::string storage_mode_help_string =
|
||||
fmt::format("Default storage mode Memgraph uses. Allowed values: {}",
|
||||
memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings));
|
||||
memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings));
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_help_string.c_str(), {
|
||||
if (const auto result = memgraph::utils::IsValidEnumValueString(value, memgraph::storage::storage_mode_mappings);
|
||||
result.HasError()) {
|
||||
if (const auto result = memgraph::utils::IsValidEnumValueString(value, storage_mode_mappings); result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case memgraph::utils::ValidationError::EmptyValue: {
|
||||
std::cout << "Storage mode cannot be empty." << std::endl;
|
||||
@@ -32,7 +38,7 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he
|
||||
}
|
||||
case memgraph::utils::ValidationError::InvalidValue: {
|
||||
std::cout << "Invalid value for storage mode. Allowed values: "
|
||||
<< memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings) << std::endl;
|
||||
<< memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings) << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -42,8 +48,8 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he
|
||||
});
|
||||
|
||||
memgraph::storage::StorageMode memgraph::flags::ParseStorageMode() {
|
||||
const auto storage_mode = memgraph::utils::StringToEnum<memgraph::storage::StorageMode>(
|
||||
FLAGS_storage_mode, memgraph::storage::storage_mode_mappings);
|
||||
const auto storage_mode =
|
||||
memgraph::utils::StringToEnum<memgraph::storage::StorageMode>(FLAGS_storage_mode, storage_mode_mappings);
|
||||
MG_ASSERT(storage_mode, "Invalid storage mode");
|
||||
return *storage_mode;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -80,7 +80,7 @@ std::vector<memgraph::communication::bolt::Value> TypedValueResultStreamBase::De
|
||||
std::vector<memgraph::communication::bolt::Value> decoded_values;
|
||||
decoded_values.reserve(values.size());
|
||||
for (const auto &v : values) {
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(v, storage_, memgraph::storage::View::NEW);
|
||||
auto maybe_value = memgraph::glue::ToBoltValue(v, *storage_, memgraph::storage::View::NEW);
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
@@ -112,14 +112,14 @@ std::string SessionHL::GetDefaultDB() {
|
||||
if (user_.has_value()) {
|
||||
return user_->db_access().GetDefault();
|
||||
}
|
||||
return std::string{memgraph::dbms::kDefaultDB};
|
||||
return memgraph::dbms::kDefaultDB;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string SessionHL::GetCurrentDB() const {
|
||||
if (!interpreter_.current_db_.db_acc_) return "";
|
||||
const auto *db = interpreter_.current_db_.db_acc_->get();
|
||||
return db->name();
|
||||
return db->id();
|
||||
}
|
||||
|
||||
std::optional<std::string> SessionHL::GetServerNameForInit() {
|
||||
@@ -167,10 +167,10 @@ std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Discard(s
|
||||
std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Pull(SessionHL::TEncoder *encoder,
|
||||
std::optional<int> n,
|
||||
std::optional<int> qid) {
|
||||
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
|
||||
auto *db = interpreter_.current_db_.db_acc_->get();
|
||||
try {
|
||||
auto &db = interpreter_.current_db_.db_acc_;
|
||||
auto *storage = db ? db->get()->storage() : nullptr;
|
||||
TypedValueResultStream<TEncoder> stream(encoder, storage);
|
||||
TypedValueResultStream<TEncoder> stream(encoder, db->storage());
|
||||
return DecodeSummary(interpreter_.Pull(&stream, n, qid));
|
||||
} catch (const memgraph::query::QueryException &e) {
|
||||
// Count the number of specific exceptions thrown
|
||||
@@ -193,16 +193,18 @@ std::pair<std::vector<std::string>, std::optional<int>> SessionHL::Interpret(
|
||||
for (const auto &[key, bolt_param] : params) {
|
||||
params_pv.emplace(key, ToPropertyValue(bolt_param));
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
const std::string *username{nullptr};
|
||||
if (user_) {
|
||||
username = &user_->username();
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
|
||||
auto *db = interpreter_.current_db_.db_acc_->get();
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
auto &db = interpreter_.current_db_.db_acc_;
|
||||
audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
|
||||
memgraph::storage::PropertyValue(params_pv), db ? db->get()->name() : "no known database");
|
||||
memgraph::storage::PropertyValue(params_pv), db->id());
|
||||
}
|
||||
#endif
|
||||
try {
|
||||
@@ -350,11 +352,11 @@ SessionHL::~SessionHL() {
|
||||
|
||||
std::map<std::string, memgraph::communication::bolt::Value> SessionHL::DecodeSummary(
|
||||
const std::map<std::string, memgraph::query::TypedValue> &summary) {
|
||||
auto &db_acc = interpreter_.current_db_.db_acc_;
|
||||
auto *storage = db_acc ? db_acc->get()->storage() : nullptr;
|
||||
// TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt)
|
||||
auto *db = interpreter_.current_db_.db_acc_->get();
|
||||
std::map<std::string, memgraph::communication::bolt::Value> decoded_summary;
|
||||
for (const auto &kv : summary) {
|
||||
auto maybe_value = ToBoltValue(kv.second, storage, memgraph::storage::View::NEW);
|
||||
auto maybe_value = ToBoltValue(kv.second, *db->storage(), memgraph::storage::View::NEW);
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -66,6 +66,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
||||
return auth::Permission::MULTI_DATABASE_EDIT;
|
||||
case query::AuthQuery::Privilege::MULTI_DATABASE_USE:
|
||||
return auth::Permission::MULTI_DATABASE_USE;
|
||||
case query::AuthQuery::Privilege::COORDINATOR:
|
||||
return auth::Permission::COORDINATOR;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option
|
||||
);
|
||||
#ifdef MG_ENTERPRISE
|
||||
GrantDatabaseToUser(auth::kAllDatabases, username);
|
||||
SetMainDatabase(dbms::kDefaultDB, username);
|
||||
SetMainDatabase(username, dbms::kDefaultDB);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatab
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::SetMainDatabase(std::string_view db, const std::string &username) {
|
||||
bool AuthQueryHandler::SetMainDatabase(const std::string &db, const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string &username) override;
|
||||
|
||||
bool SetMainDatabase(std::string_view db, const std::string &username) override;
|
||||
bool SetMainDatabase(const std::string &db, const std::string &username) override;
|
||||
|
||||
void DeleteDatabase(std::string_view db) override;
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -73,14 +73,8 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const query::EdgeAccessor
|
||||
return ToBoltEdge(edge.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db, storage::View view) {
|
||||
auto check_db = [db]() {
|
||||
if (db == nullptr) [[unlikely]]
|
||||
throw communication::bolt::ValueException("Database needed for TypeValue conversion.");
|
||||
};
|
||||
|
||||
storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db, storage::View view) {
|
||||
switch (value.type()) {
|
||||
// No database needed
|
||||
case query::TypedValue::Type::Null:
|
||||
return Value();
|
||||
case query::TypedValue::Type::Bool:
|
||||
@@ -91,29 +85,7 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
|
||||
return Value(value.ValueDouble());
|
||||
case query::TypedValue::Type::String:
|
||||
return Value(std::string(value.ValueString()));
|
||||
case query::TypedValue::Type::Date:
|
||||
return Value(value.ValueDate());
|
||||
case query::TypedValue::Type::LocalTime:
|
||||
return Value(value.ValueLocalTime());
|
||||
case query::TypedValue::Type::LocalDateTime:
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::TypedValue::Type::Duration:
|
||||
return Value(value.ValueDuration());
|
||||
|
||||
// Database potentially not required
|
||||
case query::TypedValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &kv : value.ValueMap()) {
|
||||
auto maybe_value = ToBoltValue(kv.second, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
map.emplace(kv.first, std::move(*maybe_value));
|
||||
}
|
||||
return Value(std::move(map));
|
||||
}
|
||||
|
||||
// Database is required
|
||||
case query::TypedValue::Type::List: {
|
||||
check_db();
|
||||
std::vector<Value> values;
|
||||
values.reserve(value.ValueList().size());
|
||||
for (const auto &v : value.ValueList()) {
|
||||
@@ -123,35 +95,44 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
|
||||
}
|
||||
return Value(std::move(values));
|
||||
}
|
||||
case query::TypedValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &kv : value.ValueMap()) {
|
||||
auto maybe_value = ToBoltValue(kv.second, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
map.emplace(kv.first, std::move(*maybe_value));
|
||||
}
|
||||
return Value(std::move(map));
|
||||
}
|
||||
case query::TypedValue::Type::Vertex: {
|
||||
check_db();
|
||||
auto maybe_vertex = ToBoltVertex(value.ValueVertex(), *db, view);
|
||||
auto maybe_vertex = ToBoltVertex(value.ValueVertex(), db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
return Value(std::move(*maybe_vertex));
|
||||
}
|
||||
case query::TypedValue::Type::Edge: {
|
||||
check_db();
|
||||
auto maybe_edge = ToBoltEdge(value.ValueEdge(), *db, view);
|
||||
auto maybe_edge = ToBoltEdge(value.ValueEdge(), db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
return Value(std::move(*maybe_edge));
|
||||
}
|
||||
case query::TypedValue::Type::Path: {
|
||||
check_db();
|
||||
auto maybe_path = ToBoltPath(value.ValuePath(), *db, view);
|
||||
auto maybe_path = ToBoltPath(value.ValuePath(), db, view);
|
||||
if (maybe_path.HasError()) return maybe_path.GetError();
|
||||
return Value(std::move(*maybe_path));
|
||||
}
|
||||
case query::TypedValue::Type::Graph: {
|
||||
check_db();
|
||||
auto maybe_graph = ToBoltGraph(value.ValueGraph(), *db, view);
|
||||
case query::TypedValue::Type::Date:
|
||||
return Value(value.ValueDate());
|
||||
case query::TypedValue::Type::LocalTime:
|
||||
return Value(value.ValueLocalTime());
|
||||
case query::TypedValue::Type::LocalDateTime:
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::TypedValue::Type::Duration:
|
||||
return Value(value.ValueDuration());
|
||||
case query::TypedValue::Type::Function:
|
||||
throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value");
|
||||
case query::TypedValue::Type::Graph:
|
||||
auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
|
||||
if (maybe_graph.HasError()) return maybe_graph.GetError();
|
||||
return Value(std::move(*maybe_graph));
|
||||
}
|
||||
|
||||
// Unsupported conversions
|
||||
case query::TypedValue::Type::Function: {
|
||||
throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 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
|
||||
@@ -65,7 +65,7 @@ storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(c
|
||||
/// @param storage::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db,
|
||||
storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db,
|
||||
storage::View view);
|
||||
|
||||
query::TypedValue ToTypedValue(const communication::bolt::Value &value);
|
||||
|
||||
@@ -8,4 +8,5 @@ find_package(fmt REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(mg-io STATIC ${io_src_files})
|
||||
add_library(mg::io ALIAS mg-io)
|
||||
target_link_libraries(mg-io stdc++fs Threads::Threads fmt::fmt mg-utils)
|
||||
|
||||
@@ -166,7 +166,7 @@ bool Endpoint::IsResolvableAddress(const std::string &address, uint16_t port) {
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress(
|
||||
const std::string &address, const std::optional<uint16_t> default_port = {}) {
|
||||
const std::string &address, const std::optional<uint16_t> default_port) {
|
||||
const std::string delimiter = ":";
|
||||
std::vector<std::string> parts = utils::Split(address, delimiter);
|
||||
if (parts.size() == 1) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -51,7 +51,7 @@ KVStore &KVStore::operator=(KVStore &&other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool KVStore::Put(std::string_view key, std::string_view value) {
|
||||
bool KVStore::Put(const std::string &key, const std::string &value) {
|
||||
auto s = pimpl_->db->Put(rocksdb::WriteOptions(), key, value);
|
||||
return s.ok();
|
||||
}
|
||||
@@ -65,7 +65,7 @@ bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) {
|
||||
return s.ok();
|
||||
}
|
||||
|
||||
std::optional<std::string> KVStore::Get(std::string_view key) const noexcept {
|
||||
std::optional<std::string> KVStore::Get(const std::string &key) const noexcept {
|
||||
std::string value;
|
||||
auto s = pimpl_->db->Get(rocksdb::ReadOptions(), key, &value);
|
||||
if (!s.ok()) return std::nullopt;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -61,7 +61,7 @@ class KVStore final {
|
||||
* @return true if the value has been successfully stored.
|
||||
* In case of any error false is going to be returned.
|
||||
*/
|
||||
bool Put(std::string_view key, std::string_view value);
|
||||
bool Put(const std::string &key, const std::string &value);
|
||||
|
||||
/**
|
||||
* Store values under the given keys.
|
||||
@@ -81,7 +81,7 @@ class KVStore final {
|
||||
* @return Value for the given key. std::nullopt in case of any error
|
||||
* OR the value doesn't exist.
|
||||
*/
|
||||
std::optional<std::string> Get(std::string_view key) const noexcept;
|
||||
std::optional<std::string> Get(const std::string &key) const noexcept;
|
||||
|
||||
/**
|
||||
* Deletes the key and corresponding value from storage.
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "audit/log.hpp"
|
||||
#include "communication/metrics.hpp"
|
||||
#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"
|
||||
#include "glue/ServerT.hpp"
|
||||
#include "glue/auth_checker.hpp"
|
||||
@@ -32,9 +32,9 @@
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/procedure/py_module.hpp"
|
||||
#include "requests/requests.hpp"
|
||||
#include "storage/v2/durability/durability.hpp"
|
||||
#include "telemetry/telemetry.hpp"
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/sysinfo/memory.hpp"
|
||||
#include "utils/system_info.hpp"
|
||||
#include "utils/terminate_handler.hpp"
|
||||
@@ -72,7 +72,7 @@ void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, memgraph::dbm
|
||||
spdlog::warn("{} The rest of the init-file will be run.", e.what());
|
||||
}
|
||||
if (audit_log) {
|
||||
audit_log->Record("", "", line, {}, std::string{memgraph::dbms::kDefaultDB});
|
||||
audit_log->Record("", "", line, {}, memgraph::dbms::kDefaultDB);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,7 +297,8 @@ int main(int argc, char **argv) {
|
||||
memgraph::storage::Config db_config{
|
||||
.gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC,
|
||||
.interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
|
||||
|
||||
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
|
||||
.enable_schema_metadata = FLAGS_storage_enable_schema_metadata},
|
||||
.durability = {.storage_directory = FLAGS_data_directory,
|
||||
.recover_on_startup = FLAGS_storage_recover_on_startup || FLAGS_data_recovery_on_startup,
|
||||
.snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
|
||||
@@ -319,9 +320,7 @@ int main(int argc, char **argv) {
|
||||
.id_name_mapper_directory = FLAGS_data_directory + "/rocksdb_id_name_mapper",
|
||||
.durability_directory = FLAGS_data_directory + "/rocksdb_durability",
|
||||
.wal_directory = FLAGS_data_directory + "/rocksdb_wal"},
|
||||
.salient.items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
|
||||
.enable_schema_metadata = FLAGS_storage_enable_schema_metadata},
|
||||
.salient.storage_mode = memgraph::flags::ParseStorageMode()};
|
||||
.storage_mode = memgraph::flags::ParseStorageMode()};
|
||||
|
||||
memgraph::utils::Scheduler jemalloc_purge_scheduler;
|
||||
jemalloc_purge_scheduler.Run("Jemalloc purge", std::chrono::seconds(FLAGS_storage_gc_cycle_sec),
|
||||
@@ -386,7 +385,7 @@ int main(int argc, char **argv) {
|
||||
memgraph::dbms::DbmsHandler dbms_handler(db_config
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
&auth_, FLAGS_data_recovery_on_startup
|
||||
&auth_, FLAGS_data_recovery_on_startup, FLAGS_storage_delete_on_drop
|
||||
#endif
|
||||
);
|
||||
auto db_acc = dbms_handler.Get();
|
||||
|
||||
@@ -707,11 +707,12 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map;
|
||||
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},
|
||||
.salient = {.items = {.properties_on_edges = FLAGS_storage_properties_on_edges}},
|
||||
};
|
||||
memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)};
|
||||
auto store = memgraph::dbms::CreateInMemoryStorage(config, repl_state);
|
||||
|
||||
@@ -57,7 +57,7 @@ class AuthQueryHandler {
|
||||
|
||||
/// Return true if main database set successfully
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual bool SetMainDatabase(std::string_view db, const std::string &username) = 0;
|
||||
virtual bool SetMainDatabase(const std::string &db, const std::string &username) = 0;
|
||||
|
||||
/// Delete database from all users
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -541,10 +541,7 @@ class DbAccessor final {
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
utils::BasicResult<storage::StorageManipulationError, void> Commit(storage::CommitReplArgs reparg = {},
|
||||
storage::DatabaseAccessProtector db_acc = {}) {
|
||||
return accessor_->Commit(std::move(reparg), std::move(db_acc));
|
||||
}
|
||||
utils::BasicResult<storage::StorageManipulationError, void> Commit() { return accessor_->Commit(); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -195,12 +195,6 @@ class DatabaseContextRequiredException : public QueryRuntimeException {
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(DatabaseContextRequiredException)
|
||||
};
|
||||
|
||||
class ConcurrentSystemQueriesException : public QueryRuntimeException {
|
||||
public:
|
||||
using QueryRuntimeException::QueryRuntimeException;
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(ConcurrentSystemQueriesException)
|
||||
};
|
||||
|
||||
class WriteVertexOperationInEdgeImportModeException : public QueryException {
|
||||
public:
|
||||
WriteVertexOperationInEdgeImportModeException()
|
||||
@@ -259,6 +253,13 @@ class ReplicationModificationInMulticommandTxException : public QueryException {
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(ReplicationModificationInMulticommandTxException)
|
||||
};
|
||||
|
||||
class CoordinatorModificationInMulticommandTxException : public QueryException {
|
||||
public:
|
||||
CoordinatorModificationInMulticommandTxException()
|
||||
: QueryException("Coordinator clause not allowed in multicommand transactions.") {}
|
||||
SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorModificationInMulticommandTxException)
|
||||
};
|
||||
|
||||
class ReplicationDisabledOnDiskStorage : public QueryException {
|
||||
public:
|
||||
ReplicationDisabledOnDiskStorage() : QueryException("Replication is not supported while in on-disk storage mode.") {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -239,6 +239,9 @@ constexpr utils::TypeInfo query::DumpQuery::kType{utils::TypeId::AST_DUMP_QUERY,
|
||||
constexpr utils::TypeInfo query::ReplicationQuery::kType{utils::TypeId::AST_REPLICATION_QUERY, "ReplicationQuery",
|
||||
&query::Query::kType};
|
||||
|
||||
constexpr utils::TypeInfo query::CoordinatorQuery::kType{utils::TypeId::AST_COORDINATOR_QUERY, "CoordinatorQuery",
|
||||
&query::Query::kType};
|
||||
|
||||
constexpr utils::TypeInfo query::LockPathQuery::kType{utils::TypeId::AST_LOCK_PATH_QUERY, "LockPathQuery",
|
||||
&query::Query::kType};
|
||||
|
||||
|
||||
@@ -2849,6 +2849,7 @@ class AuthQuery : public memgraph::query::Query {
|
||||
TRANSACTION_MANAGEMENT,
|
||||
MULTI_DATABASE_EDIT,
|
||||
MULTI_DATABASE_USE,
|
||||
COORDINATOR
|
||||
};
|
||||
|
||||
enum class FineGrainedPrivilege { NOTHING, READ, UPDATE, CREATE_DELETE };
|
||||
@@ -2927,7 +2928,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {AuthQuery::Privilege::
|
||||
AuthQuery::Privilege::TRANSACTION_MANAGEMENT,
|
||||
AuthQuery::Privilege::STORAGE_MODE,
|
||||
AuthQuery::Privilege::MULTI_DATABASE_EDIT,
|
||||
AuthQuery::Privilege::MULTI_DATABASE_USE};
|
||||
AuthQuery::Privilege::MULTI_DATABASE_USE,
|
||||
AuthQuery::Privilege::COORDINATOR};
|
||||
|
||||
class DatabaseInfoQuery : public memgraph::query::Query {
|
||||
public:
|
||||
@@ -3039,8 +3041,9 @@ class ReplicationQuery : public memgraph::query::Query {
|
||||
|
||||
memgraph::query::ReplicationQuery::Action action_;
|
||||
memgraph::query::ReplicationQuery::ReplicationRole role_;
|
||||
std::string replica_name_;
|
||||
std::string instance_name_;
|
||||
memgraph::query::Expression *socket_address_{nullptr};
|
||||
memgraph::query::Expression *coordinator_socket_address_{nullptr};
|
||||
memgraph::query::Expression *port_{nullptr};
|
||||
memgraph::query::ReplicationQuery::SyncMode sync_mode_;
|
||||
|
||||
@@ -3048,10 +3051,56 @@ class ReplicationQuery : public memgraph::query::Query {
|
||||
ReplicationQuery *object = storage->Create<ReplicationQuery>();
|
||||
object->action_ = action_;
|
||||
object->role_ = role_;
|
||||
object->replica_name_ = replica_name_;
|
||||
object->instance_name_ = instance_name_;
|
||||
object->socket_address_ = socket_address_ ? socket_address_->Clone(storage) : nullptr;
|
||||
object->port_ = port_ ? port_->Clone(storage) : nullptr;
|
||||
object->sync_mode_ = sync_mode_;
|
||||
object->coordinator_socket_address_ =
|
||||
coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr;
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AstStorage;
|
||||
};
|
||||
|
||||
class CoordinatorQuery : public memgraph::query::Query {
|
||||
public:
|
||||
static const utils::TypeInfo kType;
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
enum class Action {
|
||||
REGISTER_INSTANCE_ON_COORDINATOR,
|
||||
REGISTER_MAIN_COORDINATOR_SERVER,
|
||||
REGISTER_REPLICA_COORDINATOR_SERVER,
|
||||
SET_INSTANCE_TO_MAIN,
|
||||
SHOW_REPLICATION_CLUSTER,
|
||||
DO_FAILOVER
|
||||
};
|
||||
|
||||
enum class SyncMode { SYNC, ASYNC };
|
||||
|
||||
CoordinatorQuery() = default;
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
|
||||
memgraph::query::CoordinatorQuery::Action action_;
|
||||
std::string instance_name_;
|
||||
memgraph::query::Expression *replication_socket_address_{nullptr};
|
||||
memgraph::query::Expression *coordinator_socket_address_{nullptr};
|
||||
memgraph::query::CoordinatorQuery::SyncMode sync_mode_;
|
||||
|
||||
CoordinatorQuery *Clone(AstStorage *storage) const override {
|
||||
auto *object = storage->Create<CoordinatorQuery>();
|
||||
object->action_ = action_;
|
||||
object->instance_name_ = instance_name_;
|
||||
object->replication_socket_address_ =
|
||||
replication_socket_address_ ? replication_socket_address_->Clone(storage) : nullptr;
|
||||
object->sync_mode_ = sync_mode_;
|
||||
object->coordinator_socket_address_ =
|
||||
coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr;
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
@@ -3612,7 +3661,7 @@ class MultiDatabaseQuery : public memgraph::query::Query {
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
|
||||
enum class Action { CREATE, USE, DROP, SHOW };
|
||||
enum class Action { CREATE, USE, DROP };
|
||||
|
||||
memgraph::query::MultiDatabaseQuery::Action action_;
|
||||
std::string db_name_;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -108,6 +108,7 @@ class MultiDatabaseQuery;
|
||||
class ShowDatabasesQuery;
|
||||
class EdgeImportModeQuery;
|
||||
class PatternComprehension;
|
||||
class CoordinatorQuery;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@@ -146,6 +147,7 @@ class QueryVisitor
|
||||
SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery,
|
||||
SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery, StorageModeQuery,
|
||||
AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery> {};
|
||||
AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery,
|
||||
CoordinatorQuery> {};
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -302,6 +302,13 @@ antlrcpp::Any CypherMainVisitor::visitReplicationQuery(MemgraphCypher::Replicati
|
||||
return replication_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "CoordinatorQuery should have exactly one child!");
|
||||
auto *coordinator_query = std::any_cast<CoordinatorQuery *>(ctx->children[0]->accept(this));
|
||||
query_ = coordinator_query;
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeImportModeQueryContext *ctx) {
|
||||
auto *edge_import_mode_query = storage_->Create<EdgeImportModeQuery>();
|
||||
if (ctx->ACTIVE()) {
|
||||
@@ -316,24 +323,34 @@ antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeIm
|
||||
antlrcpp::Any CypherMainVisitor::visitSetReplicationRole(MemgraphCypher::SetReplicationRoleContext *ctx) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::SET_REPLICATION_ROLE;
|
||||
|
||||
auto set_replication_port = [replication_query, ctx, this]() -> void {
|
||||
if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) {
|
||||
replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this));
|
||||
} else {
|
||||
throw SyntaxException("Port must be an integer literal!");
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx->MAIN()) {
|
||||
replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN;
|
||||
if (ctx->WITH() || ctx->PORT()) {
|
||||
throw SemanticException("Main can't set a port!");
|
||||
}
|
||||
replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN;
|
||||
|
||||
} else if (ctx->REPLICA()) {
|
||||
replication_query->role_ = ReplicationQuery::ReplicationRole::REPLICA;
|
||||
if (ctx->WITH() && ctx->PORT()) {
|
||||
if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) {
|
||||
replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this));
|
||||
} else {
|
||||
throw SyntaxException("Port must be an integer literal!");
|
||||
}
|
||||
set_replication_port();
|
||||
} else {
|
||||
throw SemanticException("Replica must set a port!");
|
||||
}
|
||||
}
|
||||
|
||||
return replication_query;
|
||||
}
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext *ctx) {
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext * /*ctx*/) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICATION_ROLE;
|
||||
return replication_query;
|
||||
@@ -342,7 +359,7 @@ antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowRe
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterReplicaContext *ctx) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::REGISTER_REPLICA;
|
||||
replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this));
|
||||
replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
|
||||
if (ctx->SYNC()) {
|
||||
replication_query->sync_mode_ = memgraph::query::ReplicationQuery::SyncMode::SYNC;
|
||||
} else if (ctx->ASYNC()) {
|
||||
@@ -351,26 +368,75 @@ antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterRe
|
||||
|
||||
if (!ctx->socketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Socket address should be a string literal!");
|
||||
} else {
|
||||
replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this));
|
||||
}
|
||||
replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this));
|
||||
|
||||
return replication_query;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter.
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterInstanceOnCoordinator(
|
||||
MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
if (!ctx->replicationSocketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Replication socket address should be a string literal!");
|
||||
}
|
||||
|
||||
if (!ctx->coordinatorSocketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Coordinator socket address should be a string literal!");
|
||||
}
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::REGISTER_INSTANCE_ON_COORDINATOR;
|
||||
coordinator_query->replication_socket_address_ =
|
||||
std::any_cast<Expression *>(ctx->replicationSocketAddress()->accept(this));
|
||||
coordinator_query->coordinator_socket_address_ =
|
||||
std::any_cast<Expression *>(ctx->coordinatorSocketAddress()->accept(this));
|
||||
coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
|
||||
if (ctx->ASYNC()) {
|
||||
coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::ASYNC;
|
||||
} else {
|
||||
coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::SYNC;
|
||||
}
|
||||
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext * /*ctx*/) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER;
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitDropReplica(MemgraphCypher::DropReplicaContext *ctx) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::DROP_REPLICA;
|
||||
replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this));
|
||||
replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
|
||||
return replication_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) {
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext * /*ctx*/) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICAS;
|
||||
return replication_query;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitDoFailover(MemgraphCypher::DoFailoverContext * /*ctx*/) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::DO_FAILOVER;
|
||||
query_ = coordinator_query;
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN;
|
||||
coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this));
|
||||
query_ = coordinator_query;
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) {
|
||||
auto *lock_query = storage_->Create<LockPathQuery>();
|
||||
if (ctx->STATUS()) {
|
||||
@@ -1638,6 +1704,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
|
||||
if (ctx->STORAGE_MODE()) return AuthQuery::Privilege::STORAGE_MODE;
|
||||
if (ctx->MULTI_DATABASE_EDIT()) return AuthQuery::Privilege::MULTI_DATABASE_EDIT;
|
||||
if (ctx->MULTI_DATABASE_USE()) return AuthQuery::Privilege::MULTI_DATABASE_USE;
|
||||
if (ctx->COORDINATOR()) return AuthQuery::Privilege::COORDINATOR;
|
||||
LOG_FATAL("Should not get here - unknown privilege!");
|
||||
}
|
||||
|
||||
@@ -2888,14 +2955,6 @@ antlrcpp::Any CypherMainVisitor::visitDropDatabase(MemgraphCypher::DropDatabaseC
|
||||
return mdb_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowDatabase(MemgraphCypher::ShowDatabaseContext * /*ctx*/) {
|
||||
auto *mdb_query = storage_->Create<MultiDatabaseQuery>();
|
||||
mdb_query->db_name_ = "";
|
||||
mdb_query->action_ = MultiDatabaseQuery::Action::SHOW;
|
||||
query_ = mdb_query;
|
||||
return mdb_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowDatabases(MemgraphCypher::ShowDatabasesContext * /*ctx*/) {
|
||||
query_ = storage_->Create<ShowDatabasesQuery>();
|
||||
return query_;
|
||||
|
||||
@@ -233,6 +233,31 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterInstanceOnCoordinator(MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDoFailover(MemgraphCypher::DoFailoverContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return LockPathQuery*
|
||||
*/
|
||||
@@ -997,11 +1022,6 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitDropDatabase(MemgraphCypher::DropDatabaseContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return MultiDatabaseQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowDatabase(MemgraphCypher::ShowDatabaseContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowDatabasesQuery*
|
||||
*/
|
||||
|
||||
@@ -102,6 +102,7 @@ FILTER : F I L T E R ;
|
||||
IN : I N ;
|
||||
INDEX : I N D E X ;
|
||||
INFO : I N F O ;
|
||||
INSTANCE : I N S T A N C E ;
|
||||
IS : I S ;
|
||||
KB : K B ;
|
||||
KEY : K E Y ;
|
||||
@@ -122,6 +123,7 @@ PROCEDURE : P R O C E D U R E ;
|
||||
PROFILE : P R O F I L E ;
|
||||
QUERY : Q U E R Y ;
|
||||
REDUCE : R E D U C E ;
|
||||
REGISTER : R E G I S T E R;
|
||||
REMOVE : R E M O V E ;
|
||||
RETURN : R E T U R N ;
|
||||
SET : S E T ;
|
||||
|
||||
@@ -48,10 +48,12 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| DATABASE
|
||||
| DENY
|
||||
| DROP
|
||||
| DO
|
||||
| DUMP
|
||||
| EDGE
|
||||
| EDGE_TYPES
|
||||
| EXECUTE
|
||||
| FAILOVER
|
||||
| FOR
|
||||
| FOREACH
|
||||
| FREE
|
||||
@@ -61,6 +63,7 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| GRANT
|
||||
| HEADER
|
||||
| IDENTIFIED
|
||||
| INSTANCE
|
||||
| NODE_LABELS
|
||||
| NULLIF
|
||||
| IMPORT
|
||||
@@ -151,6 +154,7 @@ query : cypherQuery
|
||||
| multiDatabaseQuery
|
||||
| showDatabases
|
||||
| edgeImportModeQuery
|
||||
| coordinatorQuery
|
||||
;
|
||||
|
||||
cypherQuery : ( indexHints )? singleQuery ( cypherUnion )* ( queryMemoryLimit )? ;
|
||||
@@ -183,6 +187,12 @@ replicationQuery : setReplicationRole
|
||||
| showReplicas
|
||||
;
|
||||
|
||||
coordinatorQuery : registerInstanceOnCoordinator
|
||||
| setInstanceToMain
|
||||
| showReplicationCluster
|
||||
| doFailover
|
||||
;
|
||||
|
||||
triggerQuery : createTrigger
|
||||
| dropTrigger
|
||||
| showTriggers
|
||||
@@ -244,6 +254,8 @@ transactionQueueQuery : showTransactions
|
||||
|
||||
showTransactions : SHOW TRANSACTIONS ;
|
||||
|
||||
doFailover : DO FAILOVER ;
|
||||
|
||||
terminateTransactions : TERMINATE TRANSACTIONS transactionIdList;
|
||||
|
||||
loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
@@ -323,6 +335,7 @@ privilege : CREATE
|
||||
| STORAGE_MODE
|
||||
| MULTI_DATABASE_EDIT
|
||||
| MULTI_DATABASE_USE
|
||||
| COORDINATOR
|
||||
;
|
||||
|
||||
granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ;
|
||||
@@ -364,14 +377,23 @@ setReplicationRole : SET REPLICATION ROLE TO ( MAIN | REPLICA )
|
||||
|
||||
showReplicationRole : SHOW REPLICATION ROLE ;
|
||||
|
||||
replicaName : symbolicName ;
|
||||
showReplicationCluster : SHOW REPLICATION CLUSTER ;
|
||||
|
||||
instanceName : symbolicName ;
|
||||
|
||||
socketAddress : literal ;
|
||||
|
||||
registerReplica : REGISTER REPLICA replicaName ( SYNC | ASYNC )
|
||||
coordinatorSocketAddress : literal ;
|
||||
replicationSocketAddress : literal ;
|
||||
|
||||
registerReplica : REGISTER REPLICA instanceName ( SYNC | ASYNC )
|
||||
TO socketAddress ;
|
||||
|
||||
dropReplica : DROP REPLICA replicaName ;
|
||||
registerInstanceOnCoordinator : REGISTER INSTANCE instanceName ON coordinatorSocketAddress ( AS ASYNC ) ? WITH replicationSocketAddress ;
|
||||
|
||||
setInstanceToMain : SET INSTANCE instanceName TO MAIN ;
|
||||
|
||||
dropReplica : DROP REPLICA instanceName ;
|
||||
|
||||
showReplicas : SHOW REPLICAS ;
|
||||
|
||||
@@ -480,7 +502,6 @@ transactionId : literal ;
|
||||
multiDatabaseQuery : createDatabase
|
||||
| useDatabase
|
||||
| dropDatabase
|
||||
| showDatabase
|
||||
;
|
||||
|
||||
createDatabase : CREATE DATABASE databaseName ;
|
||||
@@ -489,8 +510,6 @@ useDatabase : USE DATABASE databaseName ;
|
||||
|
||||
dropDatabase : DROP DATABASE databaseName ;
|
||||
|
||||
showDatabase : SHOW DATABASE ;
|
||||
|
||||
showDatabases : SHOW DATABASES ;
|
||||
|
||||
edgeImportModeQuery : EDGE IMPORT MODE ( ACTIVE | INACTIVE ) ;
|
||||
|
||||
@@ -39,15 +39,18 @@ BOOTSTRAP_SERVERS : B O O T S T R A P UNDERSCORE S E R V E R S ;
|
||||
CALL : C A L L ;
|
||||
CHECK : C H E C K ;
|
||||
CLEAR : C L E A R ;
|
||||
CLUSTER : C L U S T E R ;
|
||||
COMMIT : C O M M I T ;
|
||||
COMMITTED : C O M M I T T E D ;
|
||||
CONFIG : C O N F I G ;
|
||||
CONFIGS : C O N F I G S;
|
||||
CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ;
|
||||
COORDINATOR : C O O R D I N A T O R ;
|
||||
CREATE_DELETE : C R E A T E UNDERSCORE D E L E T E ;
|
||||
CREDENTIALS : C R E D E N T I A L S ;
|
||||
CSV : C S V ;
|
||||
DATA : D A T A ;
|
||||
DO : D O ;
|
||||
DELIMITER : D E L I M I T E R ;
|
||||
DATABASE : D A T A B A S E ;
|
||||
DATABASES : D A T A B A S E S ;
|
||||
@@ -59,6 +62,7 @@ DURABILITY : D U R A B I L I T Y ;
|
||||
EDGE : E D G E ;
|
||||
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;
|
||||
EXECUTE : E X E C U T E ;
|
||||
FAILOVER : F A I L O V E R ;
|
||||
FOR : F O R ;
|
||||
FOREACH : F O R E A C H;
|
||||
FREE : F R E E ;
|
||||
@@ -75,6 +79,7 @@ IMPORT : I M P O R T ;
|
||||
INACTIVE : I N A C T I V E ;
|
||||
IN_MEMORY_ANALYTICAL : I N UNDERSCORE M E M O R Y UNDERSCORE A N A L Y T I C A L ;
|
||||
IN_MEMORY_TRANSACTIONAL : I N UNDERSCORE M E M O R Y UNDERSCORE T R A N S A C T I O N A L ;
|
||||
INSTANCE : I N S T A N C E ;
|
||||
ISOLATION : I S O L A T I O N ;
|
||||
KAFKA : K A F K A ;
|
||||
LABELS : L A B E L S ;
|
||||
@@ -107,6 +112,7 @@ REVOKE : R E V O K E ;
|
||||
ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SERVER : S E R V E R ;
|
||||
SERVICE_URL : S E R V I C E UNDERSCORE U R L ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
|
||||
@@ -106,7 +106,6 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_EDIT);
|
||||
break;
|
||||
case MultiDatabaseQuery::Action::USE:
|
||||
case MultiDatabaseQuery::Action::SHOW:
|
||||
AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE);
|
||||
break;
|
||||
}
|
||||
@@ -116,6 +115,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE); /* OR EDIT */
|
||||
}
|
||||
|
||||
void Visit(CoordinatorQuery & /*coordinator_query*/) override { AddPrivilege(AuthQuery::Privilege::COORDINATOR); }
|
||||
|
||||
bool PreVisit(Create & /*unused*/) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -218,7 +218,8 @@ const trie::Trie kKeywords = {"union",
|
||||
"directory",
|
||||
"lock",
|
||||
"unlock",
|
||||
"build"};
|
||||
"build",
|
||||
"instance"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,6 @@
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "dbms/database.hpp"
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
#include "memory/query_memory_control.hpp"
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/auth_query_handler.hpp"
|
||||
@@ -52,6 +51,10 @@
|
||||
#include "utils/timer.hpp"
|
||||
#include "utils/tsc.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "coordination/coordinator_instance_status.hpp"
|
||||
#endif
|
||||
|
||||
namespace memgraph::metrics {
|
||||
extern const Event FailedQuery;
|
||||
extern const Event FailedPrepare;
|
||||
@@ -68,6 +71,114 @@ inline constexpr size_t kExecutionPoolMaxBlockSize = 1024UL; // 2 ^ 10
|
||||
|
||||
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
|
||||
|
||||
class ReplicationQueryHandler {
|
||||
public:
|
||||
ReplicationQueryHandler() = default;
|
||||
virtual ~ReplicationQueryHandler() = default;
|
||||
|
||||
ReplicationQueryHandler(const ReplicationQueryHandler &) = default;
|
||||
ReplicationQueryHandler &operator=(const ReplicationQueryHandler &) = default;
|
||||
|
||||
ReplicationQueryHandler(ReplicationQueryHandler &&) = default;
|
||||
ReplicationQueryHandler &operator=(ReplicationQueryHandler &&) = default;
|
||||
|
||||
struct Replica {
|
||||
std::string name;
|
||||
std::string socket_address;
|
||||
ReplicationQuery::SyncMode sync_mode;
|
||||
std::optional<double> timeout;
|
||||
uint64_t current_timestamp_of_replica;
|
||||
uint64_t current_number_of_timestamp_behind_master;
|
||||
ReplicationQuery::ReplicaState state;
|
||||
};
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual ReplicationQuery::ReplicationRole ShowReplicationRole() const = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RegisterReplica(const std::string &name, const std::string &socket_address,
|
||||
ReplicationQuery::SyncMode sync_mode,
|
||||
const std::chrono::seconds replica_check_frequency) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void DropReplica(std::string_view replica_name) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::vector<Replica> ShowReplicas() const = 0;
|
||||
};
|
||||
|
||||
class CoordinatorQueryHandler {
|
||||
public:
|
||||
CoordinatorQueryHandler() = default;
|
||||
virtual ~CoordinatorQueryHandler() = default;
|
||||
|
||||
CoordinatorQueryHandler(const CoordinatorQueryHandler &) = default;
|
||||
CoordinatorQueryHandler &operator=(const CoordinatorQueryHandler &) = default;
|
||||
|
||||
CoordinatorQueryHandler(CoordinatorQueryHandler &&) = default;
|
||||
CoordinatorQueryHandler &operator=(CoordinatorQueryHandler &&) = default;
|
||||
|
||||
struct Replica {
|
||||
std::string name;
|
||||
std::string socket_address;
|
||||
ReplicationQuery::SyncMode sync_mode;
|
||||
std::optional<double> timeout;
|
||||
uint64_t current_timestamp_of_replica;
|
||||
uint64_t current_number_of_timestamp_behind_master;
|
||||
ReplicationQuery::ReplicaState state;
|
||||
};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
struct MainReplicaStatus {
|
||||
std::string_view name;
|
||||
std::string_view socket_address;
|
||||
bool alive;
|
||||
bool is_main;
|
||||
|
||||
MainReplicaStatus(std::string_view name, std::string_view socket_address, bool alive, bool is_main)
|
||||
: name{name}, socket_address{socket_address}, alive{alive}, is_main{is_main} {}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void RegisterReplicaCoordinatorServer(const std::string &replication_socket_address,
|
||||
const std::string &coordinator_socket_address,
|
||||
const std::chrono::seconds instance_check_frequency,
|
||||
const std::string &instance_name,
|
||||
CoordinatorQuery::SyncMode sync_mode) = 0;
|
||||
virtual void RegisterMainCoordinatorServer(const std::string &socket_address,
|
||||
const std::chrono::seconds instance_check_frequency,
|
||||
const std::string &instance_name) = 0;
|
||||
|
||||
virtual void RegisterInstanceOnCoordinator(const std::string &coordinator_socket_address,
|
||||
const std::string &replication_socket_address,
|
||||
const std::chrono::seconds instance_check_frequency,
|
||||
const std::string &instance_name,
|
||||
CoordinatorQuery::SyncMode sync_mode) = 0;
|
||||
|
||||
virtual void SetInstanceToMain(const std::string &instance_name) = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::vector<coordination::CoordinatorInstanceStatus> ShowReplicasOnCoordinator() const = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::optional<coordination::CoordinatorInstanceStatus> ShowMainOnCoordinator() const = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual void DoFailover() const = 0;
|
||||
|
||||
/// @throw QueryRuntimeException if an error ocurred.
|
||||
virtual std::vector<MainReplicaStatus> ShowMainReplicaStatus(
|
||||
const std::vector<coordination::CoordinatorInstanceStatus> &replicas,
|
||||
const std::optional<coordination::CoordinatorInstanceStatus> &main) const = 0;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
class AnalyzeGraphQueryHandler {
|
||||
public:
|
||||
AnalyzeGraphQueryHandler() = default;
|
||||
@@ -243,34 +354,7 @@ class Interpreter final {
|
||||
|
||||
void SetUser(std::string_view username);
|
||||
|
||||
struct SystemTransaction {
|
||||
explicit SystemTransaction(std::unique_lock<utils::ResourceLock> guard, dbms::DbmsHandler &dbms_handler)
|
||||
: system_guard_(std::move(guard)), dbms_handler_{&dbms_handler} {
|
||||
dbms_handler_->NewSystemTransaction();
|
||||
}
|
||||
|
||||
~SystemTransaction() { dbms_handler_->ResetSystemTransaction(); }
|
||||
|
||||
dbms::AllSyncReplicaStatus Commit() { return dbms_handler_->Commit(); }
|
||||
|
||||
private:
|
||||
std::unique_lock<utils::ResourceLock> system_guard_;
|
||||
dbms::DbmsHandler *dbms_handler_;
|
||||
};
|
||||
|
||||
std::optional<SystemTransaction> system_transaction_{};
|
||||
|
||||
private:
|
||||
void ResetInterpreter() {
|
||||
query_executions_.clear();
|
||||
system_guard.reset();
|
||||
system_transaction_.reset();
|
||||
transaction_queries_->clear();
|
||||
if (current_db_.db_acc_ && current_db_.db_acc_->is_deleting()) {
|
||||
current_db_.db_acc_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
struct QueryExecution {
|
||||
std::variant<utils::MonotonicBufferResource, utils::PoolResource> execution_memory;
|
||||
utils::ResourceWithOutOfMemoryException execution_memory_with_exception;
|
||||
@@ -329,9 +413,6 @@ class Interpreter final {
|
||||
// TODO Figure out how this would work for multi-database
|
||||
// Exists only during a single transaction (for now should be okay as is)
|
||||
std::vector<std::unique_ptr<QueryExecution>> query_executions_;
|
||||
// TODO: our upgradable lock guard for system
|
||||
std::optional<utils::ResourceLockGuard> system_guard;
|
||||
|
||||
// all queries that are run as part of the current transaction
|
||||
utils::Synchronized<std::vector<std::string>, utils::SpinLock> transaction_queries_;
|
||||
|
||||
@@ -427,7 +508,8 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std:
|
||||
// NOTE: we cannot clear query_execution inside the Abort and Commit
|
||||
// methods as we will delete summary contained in them which we need
|
||||
// after our query finished executing.
|
||||
ResetInterpreter();
|
||||
query_executions_.clear();
|
||||
transaction_queries_->clear();
|
||||
} else {
|
||||
// We can only clear this execution as some of the queries
|
||||
// in the transaction can be in unfinished state
|
||||
|
||||
@@ -56,7 +56,7 @@ std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions(
|
||||
std::iter_swap(it, not_found_midpoint);
|
||||
auto get_interpreter_db_name = [&]() -> std::string const & {
|
||||
static std::string all;
|
||||
return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->name() : all;
|
||||
return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all;
|
||||
};
|
||||
if (interpreter->username_ == username || privilege_checker(get_interpreter_db_name())) {
|
||||
killed = true; // Note: this is used by the above `clean_status` (OnScopeExit)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -22,8 +22,6 @@
|
||||
#include "query/cypher_query_interpreter.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "replication/state.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "utils/gatekeeper.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
@@ -59,7 +57,6 @@ struct InterpreterContext {
|
||||
|
||||
// GLOBAL
|
||||
memgraph::replication::ReplicationState *repl_state;
|
||||
|
||||
AuthQueryHandler *auth;
|
||||
AuthChecker *auth_checker;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -66,6 +66,12 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "PlanHinting"sv;
|
||||
case NotificationCode::REGISTER_REPLICA:
|
||||
return "RegisterReplica"sv;
|
||||
#ifdef MG_ENTERPRISE
|
||||
case NotificationCode::REGISTER_COORDINATOR_SERVER:
|
||||
return "RegisterCoordinatorServer"sv;
|
||||
case NotificationCode::DO_FAILOVER:
|
||||
return "DoFailover"sv;
|
||||
#endif
|
||||
case NotificationCode::REPLICA_PORT_WARNING:
|
||||
return "ReplicaPortWarning"sv;
|
||||
case NotificationCode::SET_REPLICA:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -42,6 +42,10 @@ enum class NotificationCode : uint8_t {
|
||||
PLAN_HINTING,
|
||||
REPLICA_PORT_WARNING,
|
||||
REGISTER_REPLICA,
|
||||
#ifdef MG_ENTERPRISE
|
||||
REGISTER_COORDINATOR_SERVER,
|
||||
DO_FAILOVER,
|
||||
#endif
|
||||
SET_REPLICA,
|
||||
START_STREAM,
|
||||
START_ALL_STREAMS,
|
||||
|
||||
@@ -916,11 +916,11 @@ struct ExpansionLambda {
|
||||
/// Currently expanded node symbol.
|
||||
Symbol inner_node_symbol;
|
||||
/// Expression used in lambda during expansion.
|
||||
Expression *expression = nullptr;
|
||||
Expression *expression;
|
||||
/// Currently expanded accumulated path symbol.
|
||||
std::optional<Symbol> accumulated_path_symbol = std::nullopt;
|
||||
std::optional<Symbol> accumulated_path_symbol;
|
||||
/// Currently expanded accumulated weight symbol.
|
||||
std::optional<Symbol> accumulated_weight_symbol = std::nullopt;
|
||||
std::optional<Symbol> accumulated_weight_symbol;
|
||||
|
||||
ExpansionLambda Clone(AstStorage *storage) const {
|
||||
ExpansionLambda object;
|
||||
|
||||
@@ -644,25 +644,6 @@ void Streams::Drop(const std::string &stream_name) {
|
||||
// TODO(antaljanosbenjamin) Release the transformation
|
||||
}
|
||||
|
||||
void Streams::DropAll() {
|
||||
streams_.WithLock([this](StreamsMap &streams) {
|
||||
bool durability_ok = true;
|
||||
for (auto &[name, stream] : streams) {
|
||||
// streams_ is write locked, which means there is no access to it outside of this function, thus only the Test
|
||||
// function can be executing with the consumer, nothing else.
|
||||
// By acquiring the write lock here for the consumer, we make sure there is
|
||||
// no running Test function for this consumer, therefore it can be erased.
|
||||
std::visit([&](const auto &stream_data) { stream_data.stream_source->Lock(); }, stream);
|
||||
if (!storage_.Delete(name)) {
|
||||
durability_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
streams.clear();
|
||||
return durability_ok; // TODO: do we need special case for this cleanup if false
|
||||
});
|
||||
}
|
||||
|
||||
void Streams::Start(const std::string &stream_name) {
|
||||
auto locked_streams = streams_.Lock();
|
||||
auto it = GetStream(*locked_streams, stream_name);
|
||||
|
||||
@@ -110,11 +110,6 @@ class Streams final {
|
||||
/// @throws StreamsException if the stream doesn't exist or if the persisted metadata can't be deleted.
|
||||
void Drop(const std::string &stream_name);
|
||||
|
||||
/// Deletes all existing streams and all the data that was persisted.
|
||||
///
|
||||
/// @throws StreamsException if the persisted metadata can't be deleted.
|
||||
void DropAll();
|
||||
|
||||
/// Start consuming from a stream.
|
||||
///
|
||||
/// @param stream_name name of the stream that needs to be started
|
||||
|
||||
@@ -5,8 +5,6 @@ target_sources(mg-replication
|
||||
include/replication/state.hpp
|
||||
include/replication/epoch.hpp
|
||||
include/replication/config.hpp
|
||||
include/replication/mode.hpp
|
||||
include/replication/messages.hpp
|
||||
include/replication/role.hpp
|
||||
include/replication/status.hpp
|
||||
include/replication/replication_client.hpp
|
||||
@@ -17,7 +15,6 @@ target_sources(mg-replication
|
||||
epoch.cpp
|
||||
config.cpp
|
||||
status.cpp
|
||||
messages.cpp
|
||||
replication_client.cpp
|
||||
replication_server.cpp
|
||||
)
|
||||
@@ -25,6 +22,6 @@ target_include_directories(mg-replication PUBLIC include)
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
target_link_libraries(mg-replication
|
||||
PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk
|
||||
PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk mg::io mg::repl_coord_glue
|
||||
PRIVATE fmt::fmt
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "replication/mode.hpp"
|
||||
#include "replication_coordination_glue/mode.hpp"
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
@@ -24,7 +24,7 @@ inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
|
||||
|
||||
struct ReplicationClientConfig {
|
||||
std::string name;
|
||||
ReplicationMode mode{};
|
||||
replication_coordination_glue::ReplicationMode mode{};
|
||||
std::string ip_address;
|
||||
uint16_t port{};
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2024 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 "rpc/messages.hpp"
|
||||
#include "slk/serialization.hpp"
|
||||
|
||||
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() = default;
|
||||
};
|
||||
|
||||
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() = default;
|
||||
};
|
||||
|
||||
using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>;
|
||||
|
||||
struct SystemHeartbeatReq {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatReq() = default;
|
||||
};
|
||||
|
||||
struct SystemHeartbeatRes {
|
||||
static const utils::TypeInfo kType;
|
||||
static const utils::TypeInfo &GetTypeInfo() { return kType; }
|
||||
|
||||
static void Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
static void Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
SystemHeartbeatRes() = default;
|
||||
explicit SystemHeartbeatRes(uint64_t system_timestamp) : system_timestamp(system_timestamp) {}
|
||||
|
||||
uint64_t system_timestamp;
|
||||
};
|
||||
|
||||
using SystemHeartbeatRpc = rpc::RequestResponse<SystemHeartbeatReq, SystemHeartbeatRes>;
|
||||
|
||||
void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder);
|
||||
|
||||
} // namespace memgraph::replication
|
||||
|
||||
namespace memgraph::slk {
|
||||
void Save(const memgraph::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::FrequentHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder);
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader);
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/);
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/);
|
||||
} // namespace memgraph::slk
|
||||
@@ -12,10 +12,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "replication/config.hpp"
|
||||
#include "replication/messages.hpp"
|
||||
#include "replication_coordination_glue/messages.hpp"
|
||||
#include "rpc/client.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
|
||||
#include <concepts>
|
||||
@@ -23,10 +22,8 @@
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
struct ReplicationClient;
|
||||
|
||||
template <typename F>
|
||||
concept FrequentCheckCB = std::invocable<F, bool, ReplicationClient &>;
|
||||
concept InvocableWithStringView = std::invocable<F, std::string_view>;
|
||||
|
||||
struct ReplicationClient {
|
||||
explicit ReplicationClient(const memgraph::replication::ReplicationClientConfig &config);
|
||||
@@ -37,27 +34,24 @@ struct ReplicationClient {
|
||||
ReplicationClient(ReplicationClient &&) noexcept = delete;
|
||||
ReplicationClient &operator=(ReplicationClient &&) noexcept = delete;
|
||||
|
||||
template <FrequentCheckCB F>
|
||||
template <InvocableWithStringView F>
|
||||
void StartFrequentCheck(F &&callback) {
|
||||
// Help the user to get the most accurate replica state possible.
|
||||
if (replica_check_frequency_ > std::chrono::seconds(0)) {
|
||||
replica_checker_.Run("Replica Checker", replica_check_frequency_,
|
||||
[this, cb = std::forward<F>(callback), reconnect = false]() mutable {
|
||||
try {
|
||||
{
|
||||
auto stream{rpc_client_.Stream<memgraph::replication::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
}
|
||||
cb(reconnect, *this);
|
||||
reconnect = false;
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
// Nothing to do...wait for a reconnect
|
||||
// NOTE: Here we are communicating with the instance connection.
|
||||
// We don't have access to the undelying client; so the only thing we can do it
|
||||
// tell the callback that this is a reconnection and to check the state
|
||||
reconnect = true;
|
||||
}
|
||||
});
|
||||
replica_checker_.Run("Replica Checker", replica_check_frequency_, [this, cb = std::forward<F>(callback)] {
|
||||
try {
|
||||
bool success = false;
|
||||
{
|
||||
auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
success = stream.AwaitResponse().success;
|
||||
}
|
||||
if (success) {
|
||||
cb(name_);
|
||||
}
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
// Nothing to do...wait for a reconnect
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,14 +60,7 @@ struct ReplicationClient {
|
||||
rpc::Client rpc_client_;
|
||||
std::chrono::seconds replica_check_frequency_;
|
||||
|
||||
// TODO: Better, this was the easiest place to put this
|
||||
enum class State {
|
||||
BEHIND,
|
||||
READY,
|
||||
};
|
||||
utils::Synchronized<State> state_{State::BEHIND};
|
||||
|
||||
memgraph::replication::ReplicationMode mode_{memgraph::replication::ReplicationMode::SYNC};
|
||||
replication_coordination_glue::ReplicationMode mode_{replication_coordination_glue::ReplicationMode::SYNC};
|
||||
// This thread pool is used for background tasks so we don't
|
||||
// block the main storage thread
|
||||
// We use only 1 thread for 2 reasons:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -14,9 +14,9 @@
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "replication/config.hpp"
|
||||
#include "replication/epoch.hpp"
|
||||
#include "replication/mode.hpp"
|
||||
#include "replication/replication_client.hpp"
|
||||
#include "replication/role.hpp"
|
||||
#include "replication_coordination_glue/mode.hpp"
|
||||
#include "replication_server.hpp"
|
||||
#include "status.hpp"
|
||||
#include "utils/result.hpp"
|
||||
@@ -45,7 +45,7 @@ struct RoleMainData {
|
||||
RoleMainData &operator=(RoleMainData &&) = default;
|
||||
|
||||
ReplicationEpoch epoch_;
|
||||
std::list<ReplicationClient> registered_replicas_{}; // TODO: data race issues
|
||||
std::list<ReplicationClient> registered_replicas_{};
|
||||
};
|
||||
|
||||
struct RoleReplicaData {
|
||||
@@ -79,8 +79,7 @@ struct ReplicationState {
|
||||
bool IsMain() const { return GetRole() == ReplicationRole::MAIN; }
|
||||
bool IsReplica() const { return GetRole() == ReplicationRole::REPLICA; }
|
||||
|
||||
bool HasDurability() const { return nullptr != durability_; }
|
||||
|
||||
bool ShouldPersist() const { return nullptr != durability_; }
|
||||
bool TryPersistRoleMain(std::string new_epoch);
|
||||
bool TryPersistRoleReplica(const ReplicationServerConfig &config);
|
||||
bool TryPersistUnregisterReplica(std::string_view name);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2024 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
|
||||
@@ -42,7 +42,7 @@ struct MainRole {
|
||||
|
||||
// fragment of key: "__replication_role"
|
||||
struct ReplicaRole {
|
||||
ReplicationServerConfig config;
|
||||
ReplicationServerConfig config{};
|
||||
friend bool operator==(ReplicaRole const &, ReplicaRole const &) = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2024 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/messages.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) {}
|
||||
void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {}
|
||||
|
||||
// Serialize code for FrequentHeartbeatReq
|
||||
void Save(const memgraph::replication::FrequentHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
void Load(memgraph::replication::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
|
||||
// Serialize code for SystemHeartbeatRes
|
||||
void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self.system_timestamp, builder);
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(&self->system_timestamp, reader);
|
||||
}
|
||||
|
||||
// Serialize code for SystemHeartbeatReq
|
||||
void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) {
|
||||
/* Nothing to serialize */
|
||||
}
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
namespace memgraph::replication {
|
||||
|
||||
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};
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatReq::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_REQ, "SystemHeartbeatReq",
|
||||
nullptr};
|
||||
|
||||
constexpr utils::TypeInfo SystemHeartbeatRes::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_RES, "SystemHeartbeatRes",
|
||||
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);
|
||||
}
|
||||
|
||||
void SystemHeartbeatReq::Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatReq::Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
void SystemHeartbeatRes::Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder) {
|
||||
memgraph::slk::Save(self, builder);
|
||||
}
|
||||
void SystemHeartbeatRes::Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader) {
|
||||
memgraph::slk::Load(self, reader);
|
||||
}
|
||||
|
||||
void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
|
||||
FrequentHeartbeatReq req;
|
||||
FrequentHeartbeatReq::Load(&req, req_reader);
|
||||
memgraph::slk::Load(&req, req_reader);
|
||||
FrequentHeartbeatRes res{};
|
||||
memgraph::slk::Save(res, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::replication
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user