Compare commits

..

30 Commits

Author SHA1 Message Date
Josip Mrden
e3ad2af0fb Print storage info memory 2023-11-13 17:44:03 +01:00
Marko Barišić
9cc060c4b0 Fix error in upload-to-s3 job (#1504) 2023-11-13 13:01:01 +01:00
Marko Barišić
e671a0737e Fix package specific workflow file (#1503) 2023-11-13 12:54:19 +01:00
Marko Barišić
11be3972c4 Add workflow for packaging memgraph for specific target OS (#1502) 2023-11-13 12:20:49 +01:00
DavIvek
fdab42a023 Use static linking on c++ query modules for glibcxx (#1490) 2023-11-13 12:08:48 +01:00
Andi
e5b2c19ea2 Empty Collect() returns nothing (#1482) 2023-11-13 11:45:09 +01:00
Josipmrden
e907817854 Fix for in list segmentation fault (#1494) 2023-11-13 05:17:10 +01:00
Josipmrden
0756cd6898 Add fix indexed join crash (#1478) 2023-11-12 22:12:25 -05:00
Josipmrden
38ad5e2146 Fix parallel index loading (#1479) 2023-11-12 23:51:00 +01:00
Josipmrden
3c413a7e50 Fix hash join expression matching (#1496) 2023-11-12 14:45:02 -05:00
Antonio Filipovic
17915578f8 Fix race condition and arena tracking bug (#1468) 2023-11-09 18:56:36 +01:00
Marko Barišić
4e9a036881 Fix v2.12 release pipeline (#1445) 2023-11-08 07:09:02 -05:00
DavIvek
c8fe9ee7d9 Fix accessing a variable bound to a list within BFS function (#1380) 2023-11-07 20:34:50 +01:00
Ante Javor
e4afddf518 Fix compare results in mgbench (#1319) 2023-11-07 17:04:37 +01:00
Antonio Filipovic
4d5ea03dfa Use extent hooks for memory procedure limit (#1443) 2023-11-07 16:04:29 +01:00
DavIvek
ece4b0dba8 Fix cached plan not getting invalidated (#1348) 2023-11-07 13:34:03 +01:00
Andi
66487a6dce Durability improvements (#1385) 2023-11-07 11:37:54 +01:00
Andi
f4b97fc03d Fix missing statistics for SetPropertiesCursor (#1460) 2023-11-07 09:11:20 +01:00
Antonio Filipovic
58648d1a70 Revert license sender info (#1461) 2023-11-06 16:57:09 +01:00
Antonio Filipovic
1ab7f6ac78 Add notification for user on max map count (#1408) 2023-11-06 15:44:26 +01:00
andrejtonev
dbc6054689 Replication refactor (part 5) (#1378) 2023-11-06 11:50:49 +00:00
Aidar Samerkhanov
16b8c7b27c Fix Kafka flaky unit test (#1409) 2023-11-05 20:51:56 +01:00
Antonio Filipovic
48631d1e37 Rename memory usage and memory allocated (#1426) 2023-11-03 14:40:45 +01:00
Antonio Filipovic
93e6d058d2 Remove all_shortest paths unnecessary logs (#1425) 2023-11-03 12:46:06 +01:00
Andi
3e9f25b8e4 Support creating date and localtime from localdatetime (#1381) 2023-11-03 10:54:01 +01:00
Andi
c94201621a Support deleting paths (#1383) 2023-11-02 14:07:48 +01:00
Andi
fdbc390d53 Throw when reduce inside exists (#1392) 2023-11-02 12:18:15 +01:00
Andi
5e6c5618f5 Make FrameChangeCollector owning the memory resource (#1398) 2023-11-02 09:54:39 +01:00
Andi
4aacd45640 Throw when exists() combined with CASE (#1382) 2023-11-02 08:25:34 +01:00
Gareth Andrew Lloyd
157b36162b Speedup socket unit test (#1444)
Was testing a setup that wasn't used in production, it would
unnecessarily thrash small buffers.
2023-11-01 17:24:24 +00:00
160 changed files with 5349 additions and 3016 deletions

294
.github/workflows/package_specific.yaml vendored Normal file
View File

@@ -0,0 +1,294 @@
name: Package Specific
# TODO(gitbuda): Cleanup docker container if GHA job was canceled.
on:
workflow_dispatch:
inputs:
memgraph_version:
description: "Memgraph version to upload as. Leave this field empty if you don't want to upload binaries to S3. Format: 'X.Y.Z'"
required: false
build_type:
type: choice
description: "Memgraph Build type. Default value is Release."
default: 'Release'
options:
- Release
- RelWithDebInfo
target_os:
type: choice
description: "Target OS for which memgraph will be packaged. Default is Ubuntu 22.04"
default: 'ubuntu-22_04'
options:
- amzn-2
- centos-7
- centos-9
- debian-10
- debian-11
- debian-11-arm
- debian-11-platform
- docker
- fedora-36
- ubuntu-18_04
- ubuntu-20_04
- ubuntu-22_04
- ubuntu-22_04-arm
jobs:
amzn-2:
if: ${{ github.event.inputs.target_os == 'amzn-2' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: amzn-2
path: build/output/amzn-2/memgraph*.rpm
centos-7:
if: ${{ github.event.inputs.target_os == 'centos-7' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: centos-7
path: build/output/centos-7/memgraph*.rpm
centos-9:
if: ${{ github.event.inputs.target_os == 'centos-9' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: centos-9
path: build/output/centos-9/memgraph*.rpm
debian-10:
if: ${{ github.event.inputs.target_os == 'debian-10' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: debian-10
path: build/output/debian-10/memgraph*.deb
debian-11:
if: ${{ github.event.inputs.target_os == 'debian-11' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: debian-11
path: build/output/debian-11/memgraph*.deb
debian-11-arm:
if: ${{ github.event.inputs.target_os == 'debian-11-arm' }}
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
timeout-minutes: 120
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: debian-11-aarch64
path: build/output/debian-11-arm/memgraph*.deb
debian-11-platform:
if: ${{ github.event.inputs.target_os == 'debian-11-platform' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: debian-11-platform
path: build/output/debian-11/memgraph*.deb
docker:
if: ${{ github.event.inputs.target_os == 'docker' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
cd release/package
./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker
./run.sh docker
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: docker
path: build/output/docker/memgraph*.tar.gz
fedora-36:
if: ${{ github.event.inputs.target_os == 'fedora-36' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: fedora-36
path: build/output/fedora-36/memgraph*.rpm
ubuntu-18_04:
if: ${{ github.event.inputs.target_os == 'ubuntu-18_04' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: ubuntu-18.04
path: build/output/ubuntu-18.04/memgraph*.deb
ubuntu-20_04:
if: ${{ github.event.inputs.target_os == 'ubuntu-20_04' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: ubuntu-20.04
path: build/output/ubuntu-20.04/memgraph*.deb
ubuntu-22_04:
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04' }}
runs-on: [self-hosted, DockerMgBuild, X64]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: ubuntu-22.04
path: build/output/ubuntu-22.04/memgraph*.deb
ubuntu-22_04-arm:
if: ${{ github.event.inputs.target_os == 'ubuntu-22_04-arm' }}
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
timeout-minutes: 120
steps:
- name: "Set up repository"
uses: actions/checkout@v3
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }}
- name: "Upload package"
uses: actions/upload-artifact@v3
with:
name: ubuntu-22.04-aarch64
path: build/output/ubuntu-22.04-arm/memgraph*.deb
upload-to-s3:
# only run upload if we specified version. Allows for runs without upload
if: "${{ github.event.inputs.memgraph_version != '' }}"
needs: [amzn-2, centos-7, centos-9, debian-10, debian-11, debian-11-arm, debian-11-platform, docker, fedora-36, ubuntu-18_04, ubuntu-20_04, ubuntu-22_04, ubuntu-22_04-arm]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
# name: # if name input parameter is not provided, all artifacts are downloaded
# and put in directories named after each one.
path: build/output/release
- name: Upload to S3
uses: jakejarvis/s3-sync-action@v0.5.1
env:
AWS_S3_BUCKET: "download.memgraph.com"
AWS_ACCESS_KEY_ID: ${{ secrets.S3_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: "eu-west-1"
SOURCE_DIR: "build/output/release"
DEST_DIR: "memgraph/v${{ github.event.inputs.memgraph_version }}/"

View File

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

View File

@@ -13,6 +13,7 @@ string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
add_library(example_c SHARED example.c)
target_include_directories(example_c PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(example_c PRIVATE -Wall)
target_link_libraries(example_c PRIVATE -static-libgcc -static-libstdc++)
# Strip C example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET example_c POST_BUILD
@@ -28,6 +29,7 @@ install(FILES example.c DESTINATION lib/memgraph/query_modules/src)
add_library(example_cpp SHARED example.cpp)
target_include_directories(example_cpp PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(example_cpp PRIVATE -Wall)
target_link_libraries(example_cpp PRIVATE -static-libgcc -static-libstdc++)
# Strip C++ example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET example_cpp POST_BUILD
@@ -43,6 +45,7 @@ install(FILES example.cpp DESTINATION lib/memgraph/query_modules/src)
add_library(schema SHARED schema.cpp)
target_include_directories(schema PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(schema PRIVATE -Wall)
target_link_libraries(schema PRIVATE -static-libgcc -static-libstdc++)
# Strip C++ example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET schema POST_BUILD

View File

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

View File

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

View File

@@ -209,7 +209,11 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
#if MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
// Increment number of queries in the metrics
IncrementQueryMetrics(session);
@@ -276,7 +280,11 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
return HandleFailure(session, e);
}
#if MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetCurrentDB(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
// Increment number of queries in the metrics
IncrementQueryMetrics(session);

View File

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

View File

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

View File

@@ -24,6 +24,8 @@
#include "query/trigger.hpp"
#include "storage/v2/storage.hpp"
#include "utils/gatekeeper.hpp"
#include "utils/lru_cache.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::dbms {
@@ -46,7 +48,7 @@ class Database {
*
* @param config storage configuration
*/
explicit Database(const storage::Config &config);
explicit Database(storage::Config config, const replication::ReplicationState &repl_state);
/**
* @brief Returns the raw storage pointer.
@@ -56,6 +58,7 @@ class Database {
* @return storage::Storage*
*/
storage::Storage *storage() { return storage_.get(); }
storage::Storage const *storage() const { return storage_.get(); }
/**
* @brief Storage's Accessor
@@ -65,12 +68,12 @@ class Database {
*/
std::unique_ptr<storage::Storage::Accessor> Access(
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
return storage_->Access(override_isolation_level);
return storage_->Access(override_isolation_level, repl_state_->IsMain());
}
std::unique_ptr<storage::Storage::Accessor> UniqueAccess(
std::optional<storage::IsolationLevel> override_isolation_level = {}) {
return storage_->UniqueAccess(override_isolation_level);
return storage_->UniqueAccess(override_isolation_level, repl_state_->IsMain());
}
/**
@@ -145,9 +148,9 @@ class Database {
/**
* @brief Returns the PlanCache vector raw pointer
*
* @return utils::SkipList<query::PlanCacheEntry>*
* @return utils::Synchronized<utils::LRUCache<uint64_t, std::shared_ptr<PlanWrapper>>, utils::RWSpinLock>
*/
utils::SkipList<query::PlanCacheEntry> *plan_cache() { return &plan_cache_; }
query::PlanCacheLRU *plan_cache() { return &plan_cache_; }
private:
std::unique_ptr<storage::Storage> storage_; //!< Underlying storage
@@ -156,7 +159,9 @@ class Database {
query::stream::Streams streams_; //!< Streams associated with the storage
// TODO: Move to a better place
utils::SkipList<query::PlanCacheEntry> plan_cache_; //!< Plan cache associated with the storage
query::PlanCacheLRU plan_cache_; //!< Plan cache associated with the storage
const replication::ReplicationState *repl_state_;
};
} // namespace memgraph::dbms

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,67 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <variant>
#include "dbms/constants.hpp"
#include "dbms/replication_handler.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/storage.hpp"
namespace memgraph::dbms {
#ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY
constexpr bool allow_mt_repl = true;
#else
constexpr bool allow_mt_repl = false;
#endif
inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(
storage::Config config, const ::memgraph::replication::ReplicationState &repl_state) {
const auto wal_mode = config.durability.snapshot_wal_mode;
const auto name = config.name;
auto storage = std::make_unique<storage::InMemoryStorage>(std::move(config));
// Connect replication state and storage
storage->CreateSnapshotHandler(
[storage = storage.get(), &repl_state]() -> utils::BasicResult<storage::InMemoryStorage::CreateSnapshotError> {
if (repl_state.IsReplica()) {
return storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica;
}
return storage->CreateSnapshot();
});
if (allow_mt_repl || name == dbms::kDefaultDB) {
// Handle global replication state
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
// RECOVER REPLICA CONNECTIONS
memgraph::dbms::RestoreReplication(repl_state, *storage);
} else if (const ::memgraph::replication::RoleMainData *data =
std::get_if<::memgraph::replication::RoleMainData>(&repl_state.ReplicationData());
data && !data->registered_replicas_.empty()) {
spdlog::warn("Multi-tenant replication is currently not supported!");
}
if (wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED && repl_state.IsMain()) {
spdlog::warn(
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider "
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
"without write-ahead logs this instance is not replicating any data.");
}
return std::move(storage);
}
} // namespace memgraph::dbms

View File

@@ -0,0 +1,234 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "dbms/replication_handler.hpp"
#include "dbms/constants.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/inmemory/replication_handlers.hpp"
#include "dbms/inmemory/storage_helper.hpp"
#include "replication/state.hpp"
using memgraph::replication::ReplicationClientConfig;
using memgraph::replication::ReplicationState;
using memgraph::replication::RoleMainData;
using memgraph::replication::RoleReplicaData;
namespace memgraph::dbms {
namespace {
std::string RegisterReplicaErrorToString(RegisterReplicaError error) {
switch (error) {
using enum RegisterReplicaError;
case NAME_EXISTS:
return "NAME_EXISTS";
case END_POINT_EXISTS:
return "END_POINT_EXISTS";
case CONNECTION_FAILED:
return "CONNECTION_FAILED";
case COULD_NOT_BE_PERSISTED:
return "COULD_NOT_BE_PERSISTED";
}
}
} // namespace
bool ReplicationHandler::SetReplicationRoleMain() {
auto const main_handler = [](RoleMainData const &) {
// If we are already MAIN, we don't want to change anything
return false;
};
auto const replica_handler = [this](RoleReplicaData const &) {
// STEP 1) bring down all REPLICA servers
dbms_handler_.ForEach([](Database *db) {
auto *storage = db->storage();
// Remember old epoch + storage timestamp association
storage->PrepareForNewEpoch();
});
// STEP 2) Change to MAIN
// TODO: restore replication servers if false?
if (!repl_state_.SetReplicationRoleMain()) {
// TODO: Handle recovery on failure???
return false;
}
// STEP 3) We are now MAIN, update storage local epoch
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
storage->repl_storage_state_.epoch_ = std::get<RoleMainData>(std::as_const(repl_state_).ReplicationData()).epoch_;
});
return true;
};
// TODO: under lock
return std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
}
bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config) {
// We don't want to restart the server if we're already a REPLICA
if (repl_state_.IsReplica()) {
return false;
}
// Remove registered replicas
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); });
});
// Creates the server
repl_state_.SetReplicationRoleReplica(config);
// Start
const auto success =
std::visit(utils::Overloaded{[](auto) {
// ASSERT
return false;
},
[this](RoleReplicaData const &data) {
// Register handlers
InMemoryReplicationHandlers::Register(&dbms_handler_, *data.server);
if (!data.server->Start()) {
spdlog::error("Unable to start the replication server.");
return false;
}
return true;
}},
repl_state_.ReplicationData());
// TODO Handle error (restore to main?)
return success;
}
auto ReplicationHandler::RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
-> memgraph::utils::BasicResult<RegisterReplicaError> {
MG_ASSERT(repl_state_.IsMain(), "Only main instance can register a replica!");
auto res = repl_state_.RegisterReplica(config);
switch (res) {
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
MG_ASSERT(false, "Only main instance can register a replica!");
return {};
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
return memgraph::dbms::RegisterReplicaError::NAME_EXISTS;
case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS:
return memgraph::dbms::RegisterReplicaError::END_POINT_EXISTS;
case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
return memgraph::dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
case memgraph::replication::RegisterReplicaError::SUCCESS:
break;
}
bool all_clients_good = true;
if (!allow_mt_repl && dbms_handler_.All().size() > 1) {
spdlog::warn("Multi-tenant replication is currently not supported!");
}
dbms_handler_.ForEach([&](Database *db) {
auto *storage = db->storage();
if (!allow_mt_repl && storage->id() != kDefaultDB) {
return;
}
// TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes
if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return;
all_clients_good &=
storage->repl_storage_state_.replication_clients_.WithLock([storage, &config](auto &clients) -> bool {
auto client = storage->CreateReplicationClient(config, &storage->repl_storage_state_.epoch_);
client->Start();
if (client->State() == storage::replication::ReplicaState::INVALID) {
return false;
}
clients.push_back(std::move(client));
return true;
});
});
if (!all_clients_good) return RegisterReplicaError::CONNECTION_FAILED; // TODO: this happen to 1 or many...what to do
return {};
}
auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterReplicaResult {
auto const replica_handler = [](RoleReplicaData const &) -> UnregisterReplicaResult {
return UnregisterReplicaResult::NOT_MAIN;
};
auto const main_handler = [this, name](RoleMainData &mainData) -> UnregisterReplicaResult {
if (!repl_state_.TryPersistUnregisterReplica(name)) {
return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED;
}
auto const n_unregistered =
std::erase_if(mainData.registered_replicas_,
[&](ReplicationClientConfig const &registered_config) { return registered_config.name == name; });
dbms_handler_.ForEach([&](Database *db) {
db->storage()->repl_storage_state_.replication_clients_.WithLock(
[&](auto &clients) { std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); });
});
return n_unregistered != 0 ? UnregisterReplicaResult::SUCCESS : UnregisterReplicaResult::CAN_NOT_UNREGISTER;
};
return std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData());
}
auto ReplicationHandler::GetRole() const -> memgraph::replication::ReplicationRole { return repl_state_.GetRole(); }
bool ReplicationHandler::IsMain() const { return repl_state_.IsMain(); }
bool ReplicationHandler::IsReplica() const { return repl_state_.IsReplica(); }
void RestoreReplication(const replication::ReplicationState &repl_state, storage::Storage &storage) {
spdlog::info("Restoring replication role.");
/// MAIN
auto const recover_main = [&storage](RoleMainData const &mainData) {
for (const auto &config : mainData.registered_replicas_) {
spdlog::info("Replica {} restoration started for {}.", config.name, storage.id());
auto register_replica = [&storage](const memgraph::replication::ReplicationClientConfig &config)
-> memgraph::utils::BasicResult<RegisterReplicaError> {
return storage.repl_storage_state_.replication_clients_.WithLock(
[&storage, &config](auto &clients) -> utils::BasicResult<RegisterReplicaError> {
auto client = storage.CreateReplicationClient(config, &storage.repl_storage_state_.epoch_);
client->Start();
if (client->State() == storage::replication::ReplicaState::INVALID) {
spdlog::warn("Connection failed when registering replica {}. Replica will still be registered.",
client->Name());
}
clients.push_back(std::move(client));
return {};
});
};
auto ret = register_replica(config);
if (ret.HasError()) {
MG_ASSERT(RegisterReplicaError::CONNECTION_FAILED != ret.GetError());
LOG_FATAL("Failure when restoring replica {}: {}.", config.name, RegisterReplicaErrorToString(ret.GetError()));
}
spdlog::info("Replica {} restored for {}.", config.name, storage.id());
}
spdlog::info("Replication role restored to MAIN.");
};
/// REPLICA
auto const recover_replica = [](RoleReplicaData const &data) { /*nothing to do*/ };
std::visit(
utils::Overloaded{
recover_main,
recover_replica,
},
std::as_const(repl_state).ReplicationData());
}
} // namespace memgraph::dbms

View File

@@ -12,6 +12,7 @@
#pragma once
#include "replication/role.hpp"
#include "storage/v2/storage.hpp"
#include "utils/result.hpp"
// BEGIN fwd declares
@@ -20,14 +21,10 @@ struct ReplicationState;
struct ReplicationServerConfig;
struct ReplicationClientConfig;
} // namespace memgraph::replication
namespace memgraph::storage {
class Storage;
}
// END fwd declares
namespace memgraph::storage {
namespace memgraph::dbms {
class DbmsHandler;
enum class RegistrationMode : std::uint8_t { MUST_BE_INSTANTLY_VALID, RESTORE };
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED };
enum class UnregisterReplicaResult : uint8_t {
NOT_MAIN,
@@ -39,8 +36,8 @@ enum class UnregisterReplicaResult : uint8_t {
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
/// TODO: extend to do multiple storages
struct ReplicationHandler {
ReplicationHandler(memgraph::replication::ReplicationState &replState, Storage &storage)
: repl_state_(replState), storage_(storage) {}
ReplicationHandler(memgraph::replication::ReplicationState &replState, DbmsHandler &dbms_handler)
: repl_state_(replState), dbms_handler_(dbms_handler) {}
// as REPLICA, become MAIN
bool SetReplicationRoleMain();
@@ -49,16 +46,12 @@ struct ReplicationHandler {
bool SetReplicationRoleReplica(const memgraph::replication::ReplicationServerConfig &config);
// as MAIN, define and connect to REPLICAs
auto RegisterReplica(RegistrationMode registration_mode, const memgraph::replication::ReplicationClientConfig &config)
auto RegisterReplica(const memgraph::replication::ReplicationClientConfig &config)
-> utils::BasicResult<RegisterReplicaError>;
// as MAIN, remove a REPLICA connection
auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult;
// Generic restoration
// TODO: decouple storage restoration from epoch restoration
void RestoreReplication();
// Helper pass-through (TODO: remove)
auto GetRole() const -> memgraph::replication::ReplicationRole;
bool IsMain() const;
@@ -66,6 +59,11 @@ struct ReplicationHandler {
private:
memgraph::replication::ReplicationState &repl_state_;
Storage &storage_;
DbmsHandler &dbms_handler_;
};
} // namespace memgraph::storage
/// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage
/// TODO: extend to do multiple storages
void RestoreReplication(const replication::ReplicationState &repl_state, storage::Storage &storage);
} // namespace memgraph::dbms

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ class MetricsService {
return MetricsResponse{.vertex_count = info.vertex_count,
.edge_count = info.edge_count,
.average_degree = info.average_degree,
.memory_usage = info.memory_usage,
.memory_usage = info.memory_res,
.disk_usage = info.disk_usage,
.event_counters = GetEventCounters(),
.event_gauges = GetEventGauges(),

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source

View File

@@ -216,7 +216,7 @@ bool Socket::Write(const uint8_t *data, size_t len, bool have_more) {
return true;
}
bool Socket::Write(const std::string &s, bool have_more) {
bool Socket::Write(std::string_view s, bool have_more) {
return Write(reinterpret_cast<const uint8_t *>(s.data()), s.size(), have_more);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -149,7 +149,7 @@ class Socket {
* false if write failed
*/
bool Write(const uint8_t *data, size_t len, bool have_more = false);
bool Write(const std::string &s, bool have_more = false);
bool Write(std::string_view s, bool have_more = false);
/**
* Read data from the socket.

View File

@@ -42,7 +42,7 @@ void LicenseInfoSender::SendData() {
license_info_.WithLock([&data, this](const auto &license_info) mutable {
if (license_info && !license_info->organization_name.empty()) {
const auto memory_info = utils::GetMemoryInfo();
const auto memory_usage = utils::GetMemoryUsage();
const auto memory_res = utils::GetMemoryRES();
data = {{"run_id", uuid_},
{"machine_id", machine_id_},
{"type", "license-check"},
@@ -52,7 +52,7 @@ void LicenseInfoSender::SendData() {
{"valid", license_info->is_valid},
{"physical_memory_size", memory_info.memory},
{"swap_memory_size", memory_info.swap},
{"memory_used", memory_usage},
{"memory_used", memory_res},
{"runtime_memory_limit", memory_limit_},
{"license_memory_limit", license_info->license.memory_limit},
{"timestamp", utils::Timestamp::Now().SecWithNsecSinceTheEpoch()}};

View File

@@ -9,11 +9,13 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#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"
@@ -43,9 +45,11 @@
#include "query/auth_query_handler.hpp"
#include "query/interpreter_context.hpp"
namespace {
constexpr const char *kMgUser = "MEMGRAPH_USER";
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
constexpr const char *kMgPassfile = "MEMGRAPH_PASSFILE";
constexpr uint64_t kMgVmMaxMapCount = 262144;
// TODO: move elsewhere so that we can remove need of interpreter.hpp
void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, memgraph::dbms::DatabaseAccess &db_acc,
@@ -107,6 +111,7 @@ void InitSignalHandlers(const std::function<void()> &shutdown_fun) {
block_shutdown_signals),
"Unable to register SIGINT handler!");
}
} // namespace
int main(int argc, char **argv) {
memgraph::memory::SetHooks();
@@ -204,6 +209,17 @@ int main(int argc, char **argv) {
std::cout << "You are running Memgraph v" << gflags::VersionString() << std::endl;
std::cout << "To get started with Memgraph, visit https://memgr.ph/start" << std::endl;
const auto vm_max_map_count = memgraph::utils::GetVmMaxMapCount();
if (vm_max_map_count.has_value()) {
if (vm_max_map_count.value() < kMgVmMaxMapCount) {
std::cout << "Max virtual memory areas vm.max_map_count " << vm_max_map_count.value()
<< " is too low, increase to at least " << kMgVmMaxMapCount << std::endl;
}
} else {
std::cout << "Can't get info on vm.max_map_count, check whether it is too low, vm.max_map_count is at least "
<< kMgVmMaxMapCount << std::endl;
}
auto data_directory = std::filesystem::path(FLAGS_data_directory);
memgraph::utils::EnsureDirOrDie(data_directory);
@@ -352,22 +368,35 @@ int main(int argc, char **argv) {
std::unique_ptr<memgraph::query::AuthChecker> auth_checker;
auth_glue(&auth_, auth_handler, auth_checker);
memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(db_config));
memgraph::dbms::DbmsHandler dbms_handler(db_config, repl_state
#ifdef MG_ENTERPRISE
memgraph::dbms::DbmsHandler new_handler(db_config, &auth_, FLAGS_data_recovery_on_startup,
FLAGS_storage_delete_on_drop);
auto db_acc = new_handler.Get(memgraph::dbms::kDefaultDB);
memgraph::query::InterpreterContext interpreter_context_(interp_config, &new_handler, auth_handler.get(),
auth_checker.get());
#else
memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gatekeeper{db_config};
auto db_acc_opt = db_gatekeeper.access();
MG_ASSERT(db_acc_opt, "Failed to access the main database");
auto &db_acc = *db_acc_opt;
memgraph::query::InterpreterContext interpreter_context_(interp_config, &db_gatekeeper, auth_handler.get(),
auth_checker.get());
,
&auth_, FLAGS_data_recovery_on_startup, FLAGS_storage_delete_on_drop
#endif
);
auto db_acc = dbms_handler.Get();
memgraph::query::InterpreterContext interpreter_context_(interp_config, &dbms_handler, &repl_state,
auth_handler.get(), auth_checker.get());
MG_ASSERT(db_acc, "Failed to access the main database");
// TODO: Move it somewhere better
// Startup replication state (if recovered at startup)
MG_ASSERT(std::visit(memgraph::utils::Overloaded{[](memgraph::replication::RoleMainData const &) { return true; },
[&](memgraph::replication::RoleReplicaData const &data) {
// Register handlers
memgraph::dbms::InMemoryReplicationHandlers::Register(
&dbms_handler, *data.server);
if (!data.server->Start()) {
spdlog::error("Unable to start the replication server.");
return false;
}
return true;
}},
repl_state.ReplicationData()),
"Replica recovery failure!");
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(memgraph::flags::ParseQueryModulesDirectory(),
FLAGS_data_directory);
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
@@ -388,8 +417,8 @@ int main(int argc, char **argv) {
}
#ifdef MG_ENTERPRISE
new_handler.RestoreTriggers(&interpreter_context_);
new_handler.RestoreStreams(&interpreter_context_);
dbms_handler.RestoreTriggers(&interpreter_context_);
dbms_handler.RestoreStreams(&interpreter_context_);
#else
{
// Triggers can execute query procedures, so we need to reload the modules first and then
@@ -432,11 +461,10 @@ int main(int argc, char **argv) {
if (FLAGS_telemetry_enabled) {
telemetry.emplace(telemetry_server, data_directory / "telemetry", memgraph::glue::run_id_, machine_id,
service_name == "BoltS", FLAGS_data_directory, std::chrono::minutes(10));
telemetry->AddStorageCollector(dbms_handler, auth_);
#ifdef MG_ENTERPRISE
telemetry->AddStorageCollector(new_handler, auth_);
telemetry->AddDatabaseCollector(new_handler);
telemetry->AddDatabaseCollector(dbms_handler);
#else
telemetry->AddStorageCollector(db_gatekeeper, auth_);
telemetry->AddDatabaseCollector();
#endif
telemetry->AddClientCollector();
@@ -496,17 +524,16 @@ int main(int argc, char **argv) {
if (!FLAGS_init_data_file.empty()) {
spdlog::info("Running init data file.");
auto db_acc = dbms_handler.Get();
MG_ASSERT(db_acc, "Failed to gain access to the main database");
#ifdef MG_ENTERPRISE
auto db_acc = new_handler.Get(memgraph::dbms::kDefaultDB);
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
}
#else
auto db_acc_2 = db_gatekeeper.access();
MG_ASSERT(db_acc_2, "Failed to gain access to the main database");
InitFromCypherlFile(interpreter_context_, *db_acc_2, FLAGS_init_data_file);
InitFromCypherlFile(interpreter_context_, db_acc, FLAGS_init_data_file);
#endif
}

View File

@@ -61,11 +61,8 @@ void *my_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t
// This needs to be before, to throw exception in case of too big alloc
if (*commit) [[likely]] {
memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(size));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Alloc(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackAllocOnCurrentThread(size);
}
}
@@ -73,11 +70,8 @@ void *my_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, size_t
if (ptr == nullptr) [[unlikely]] {
if (*commit) {
memgraph::utils::total_memory_tracker.Free(static_cast<int64_t>(size));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Free(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
}
return ptr;
@@ -96,11 +90,8 @@ static bool my_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, boo
if (committed) [[likely]] {
memgraph::utils::total_memory_tracker.Free(static_cast<int64_t>(size));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Free(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
}
@@ -110,11 +101,8 @@ static bool my_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, boo
static void my_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, bool committed, unsigned arena_ind) {
if (committed) [[likely]] {
memgraph::utils::total_memory_tracker.Free(static_cast<int64_t>(size));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Free(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
}
@@ -130,11 +118,8 @@ static bool my_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, siz
}
memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(length));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Alloc(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
return false;
@@ -150,11 +135,8 @@ static bool my_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size, s
}
memgraph::utils::total_memory_tracker.Free(static_cast<int64_t>(length));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Free(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
return false;
@@ -170,11 +152,8 @@ static bool my_purge_forced(extent_hooks_t *extent_hooks, void *addr, size_t siz
}
memgraph::utils::total_memory_tracker.Free(static_cast<int64_t>(length));
if (GetQueriesMemoryControl().IsArenaTracked(arena_ind)) [[unlikely]] {
auto *memory_tracker = GetQueriesMemoryControl().GetTrackerCurrentThread();
if (memory_tracker != nullptr) [[likely]] {
memory_tracker->Alloc(static_cast<int64_t>(size));
}
if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] {
GetQueriesMemoryControl().TrackFreeOnCurrentThread(size);
}
return false;
@@ -200,8 +179,11 @@ void SetHooks() {
return;
}
// Needs init due to error we might encounter otherwise
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=13684
[[maybe_unused]] const auto &queries_memory_control = GetQueriesMemoryControl();
for (int i = 0; i < n_arenas; i++) {
GetQueriesMemoryControl().InitializeArenaCounter(i);
std::string func_name = "arena." + std::to_string(i) + ".extent_hooks";
size_t hooks_len = sizeof(old_hooks);
@@ -261,7 +243,6 @@ void UnsetHooks() {
}
for (int i = 0; i < n_arenas; i++) {
GetQueriesMemoryControl().InitializeArenaCounter(i);
std::string func_name = "arena." + std::to_string(i) + ".extent_hooks";
MG_ASSERT(old_hooks);

View File

@@ -10,6 +10,7 @@
// licenses/APL.txt.
#include <atomic>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <optional>
@@ -21,6 +22,7 @@
#include "query_memory_control.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
#include "utils/memory_tracker.hpp"
#include "utils/rw_spin_lock.hpp"
@@ -32,20 +34,6 @@ namespace memgraph::memory {
#if USE_JEMALLOC
unsigned QueriesMemoryControl::GetArenaForThread() {
unsigned thread_arena{0};
size_t size_thread_arena = sizeof(thread_arena);
int err = mallctl("thread.arena", &thread_arena, &size_thread_arena, nullptr, 0);
if (err) {
LOG_FATAL("Can't get arena for thread.");
}
return thread_arena;
}
void QueriesMemoryControl::AddTrackingOnArena(unsigned arena_id) { arena_tracking[arena_id].fetch_add(1); }
void QueriesMemoryControl::RemoveTrackingOnArena(unsigned arena_id) { arena_tracking[arena_id].fetch_sub(1); }
void QueriesMemoryControl::UpdateThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) {
auto accessor = thread_id_to_transaction_id.access();
accessor.insert({thread_id, transaction_id});
@@ -58,34 +46,58 @@ void QueriesMemoryControl::EraseThreadToTransactionId(const std::thread::id &thr
accessor.remove(thread_id);
}
utils::MemoryTracker *QueriesMemoryControl::GetTrackerCurrentThread() {
void QueriesMemoryControl::TrackAllocOnCurrentThread(size_t size) {
auto thread_id_to_transaction_id_accessor = thread_id_to_transaction_id.access();
// we might be just constructing mapping between thread id and transaction id
// so we miss this allocation
auto thread_id_to_transaction_id_elem = thread_id_to_transaction_id_accessor.find(std::this_thread::get_id());
if (thread_id_to_transaction_id_elem == thread_id_to_transaction_id_accessor.end()) {
return nullptr;
return;
}
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto transaction_id_to_tracker =
transaction_id_to_tracker_accessor.find(thread_id_to_transaction_id_elem->transaction_id);
return &transaction_id_to_tracker->tracker;
// It can happen that some allocation happens between mapping thread to
// transaction id, so we miss this allocation
if (transaction_id_to_tracker == transaction_id_to_tracker_accessor.end()) [[unlikely]] {
return;
}
auto &query_tracker = transaction_id_to_tracker->tracker;
query_tracker.TrackAlloc(size);
}
void QueriesMemoryControl::TrackFreeOnCurrentThread(size_t size) {
auto thread_id_to_transaction_id_accessor = thread_id_to_transaction_id.access();
// we might be just constructing mapping between thread id and transaction id
// so we miss this allocation
auto thread_id_to_transaction_id_elem = thread_id_to_transaction_id_accessor.find(std::this_thread::get_id());
if (thread_id_to_transaction_id_elem == thread_id_to_transaction_id_accessor.end()) {
return;
}
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto transaction_id_to_tracker =
transaction_id_to_tracker_accessor.find(thread_id_to_transaction_id_elem->transaction_id);
// It can happen that some allocation happens between mapping thread to
// transaction id, so we miss this allocation
if (transaction_id_to_tracker == transaction_id_to_tracker_accessor.end()) [[unlikely]] {
return;
}
auto &query_tracker = transaction_id_to_tracker->tracker;
query_tracker.TrackFree(size);
}
void QueriesMemoryControl::CreateTransactionIdTracker(uint64_t transaction_id, size_t inital_limit) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto [elem, result] = transaction_id_to_tracker_accessor.insert({transaction_id, utils::MemoryTracker{}});
auto [elem, result] = transaction_id_to_tracker_accessor.insert({transaction_id, utils::QueryMemoryTracker{}});
elem->tracker.SetMaximumHardLimit(inital_limit);
elem->tracker.SetHardLimit(inital_limit);
}
bool QueriesMemoryControl::CheckTransactionIdTrackerExists(uint64_t transaction_id) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
return transaction_id_to_tracker_accessor.contains(transaction_id);
elem->tracker.SetQueryLimit(inital_limit);
}
bool QueriesMemoryControl::EraseTransactionIdTracker(uint64_t transaction_id) {
@@ -94,27 +106,68 @@ bool QueriesMemoryControl::EraseTransactionIdTracker(uint64_t transaction_id) {
return removed;
}
bool QueriesMemoryControl::IsArenaTracked(unsigned arena_ind) {
return arena_tracking[arena_ind].load(std::memory_order_acquire) != 0;
bool QueriesMemoryControl::CheckTransactionIdTrackerExists(uint64_t transaction_id) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
return transaction_id_to_tracker_accessor.contains(transaction_id);
}
void QueriesMemoryControl::InitializeArenaCounter(unsigned arena_ind) {
arena_tracking[arena_ind].store(0, std::memory_order_relaxed);
void QueriesMemoryControl::TryCreateTransactionProcTracker(uint64_t transaction_id, int64_t procedure_id,
size_t limit) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id);
if (query_tracker == transaction_id_to_tracker_accessor.end()) {
return;
}
query_tracker->tracker.TryCreateProcTracker(procedure_id, limit);
}
void QueriesMemoryControl::SetActiveProcIdTracker(uint64_t transaction_id, int64_t procedure_id) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id);
if (query_tracker == transaction_id_to_tracker_accessor.end()) {
return;
}
query_tracker->tracker.SetActiveProc(procedure_id);
}
void QueriesMemoryControl::PauseProcedureTracking(uint64_t transaction_id) {
auto transaction_id_to_tracker_accessor = transaction_id_to_tracker.access();
auto query_tracker = transaction_id_to_tracker_accessor.find(transaction_id);
if (query_tracker == transaction_id_to_tracker_accessor.end()) {
return;
}
query_tracker->tracker.StopProcTracking();
}
inline int &Get_Thread_Tracker() {
// store variable in bss segment for each thread
// https://cs-fundamentals.com/c-programming/memory-layout-of-c-program-code-data-segments#size-of-code-data-bss-segments
static thread_local int is_thread_tracked{0};
return is_thread_tracked;
}
bool QueriesMemoryControl::IsThreadTracked() { return Get_Thread_Tracker() == 1; }
#endif
void StartTrackingCurrentThreadTransaction(uint64_t transaction_id) {
#if USE_JEMALLOC
Get_Thread_Tracker() = 0;
GetQueriesMemoryControl().UpdateThreadToTransactionId(std::this_thread::get_id(), transaction_id);
GetQueriesMemoryControl().AddTrackingOnArena(QueriesMemoryControl::GetArenaForThread());
Get_Thread_Tracker() = 1;
#endif
}
void StopTrackingCurrentThreadTransaction(uint64_t transaction_id) {
#if USE_JEMALLOC
Get_Thread_Tracker() = 0;
GetQueriesMemoryControl().EraseThreadToTransactionId(std::this_thread::get_id(), transaction_id);
GetQueriesMemoryControl().RemoveTrackingOnArena(QueriesMemoryControl::GetArenaForThread());
#endif
}
@@ -137,4 +190,29 @@ void TryStopTrackingOnTransaction(uint64_t transaction_id) {
#endif
}
#if USE_JEMALLOC
bool IsTransactionTracked(uint64_t transaction_id) {
return GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id);
}
#else
bool IsTransactionTracked(uint64_t /*transaction_id*/) { return false; }
#endif
void CreateOrContinueProcedureTracking(uint64_t transaction_id, int64_t procedure_id, size_t limit) {
#if USE_JEMALLOC
if (!GetQueriesMemoryControl().CheckTransactionIdTrackerExists(transaction_id)) {
LOG_FATAL("Memory tracker for transaction was not set");
}
GetQueriesMemoryControl().TryCreateTransactionProcTracker(transaction_id, procedure_id, limit);
GetQueriesMemoryControl().SetActiveProcIdTracker(transaction_id, procedure_id);
#endif
}
void PauseProcedureTracking(uint64_t transaction_id) {
#if USE_JEMALLOC
GetQueriesMemoryControl().PauseProcedureTracking(transaction_id);
#endif
}
} // namespace memgraph::memory

View File

@@ -16,10 +16,13 @@
#include <unordered_map>
#include "utils/memory_tracker.hpp"
#include "utils/query_memory_tracker.hpp"
#include "utils/skip_list.hpp"
namespace memgraph::memory {
static constexpr int64_t UNLIMITED_MEMORY{0};
#if USE_JEMALLOC
// Track memory allocations per query.
@@ -30,25 +33,6 @@ namespace memgraph::memory {
// it is necessary to restart tracking at the beginning of new query for that transaction.
class QueriesMemoryControl {
public:
/*
Arena stats
*/
static unsigned GetArenaForThread();
// Add counter on threads allocating inside arena
void AddTrackingOnArena(unsigned);
// Remove counter on threads allocating in arena
void RemoveTrackingOnArena(unsigned);
// Are any threads using current arena for allocations
// Multiple threads can allocate inside one arena
bool IsArenaTracked(unsigned);
// Initialize arena counter
void InitializeArenaCounter(unsigned);
/*
Transaction id <-> tracker
*/
@@ -75,20 +59,25 @@ class QueriesMemoryControl {
// Important to reset if one thread gets reused for different transaction
void EraseThreadToTransactionId(const std::thread::id &, uint64_t);
// C-API functionality for thread to transaction mapping
void UpdateThreadToTransactionId(const char *, uint64_t);
// Find tracker for current thread if exists, track
// query allocation and procedure allocation if
// necessary
void TrackAllocOnCurrentThread(size_t size);
// C-API functionality for thread to transaction unmapping
void EraseThreadToTransactionId(const char *, uint64_t);
// Find tracker for current thread if exists, track
// query allocation and procedure allocation if
// necessary
void TrackFreeOnCurrentThread(size_t size);
// Get tracker to current thread if exists, otherwise return
// nullptr. This can happen only if tracker is still
// being constructed.
utils::MemoryTracker *GetTrackerCurrentThread();
void TryCreateTransactionProcTracker(uint64_t, int64_t, size_t);
void SetActiveProcIdTracker(uint64_t, int64_t);
void PauseProcedureTracking(uint64_t);
bool IsThreadTracked();
private:
std::unordered_map<unsigned, std::atomic<int>> arena_tracking;
struct ThreadIdToTransactionId {
std::thread::id thread_id;
uint64_t transaction_id;
@@ -102,7 +91,7 @@ class QueriesMemoryControl {
struct TransactionIdToTracker {
uint64_t transaction_id;
utils::MemoryTracker tracker;
utils::QueryMemoryTracker tracker;
bool operator<(const TransactionIdToTracker &other) const { return transaction_id < other.transaction_id; }
bool operator==(const TransactionIdToTracker &other) const { return transaction_id == other.transaction_id; }
@@ -138,4 +127,15 @@ void TryStartTrackingOnTransaction(uint64_t transaction_id, size_t limit);
// Does nothing if jemalloc is not enabled. Does nothing if tracker doesn't exist
void TryStopTrackingOnTransaction(uint64_t transaction_id);
// Is transaction with given id tracked in memory tracker
bool IsTransactionTracked(uint64_t transaction_id);
// Creates tracker on procedure if doesn't exist. Sets query tracker
// to track procedure with id.
void CreateOrContinueProcedureTracking(uint64_t transaction_id, int64_t procedure_id, size_t limit);
// Pauses procedure tracking. This enables to continue
// tracking on procedure once procedure execution resumes.
void PauseProcedureTracking(uint64_t transaction_id);
} // namespace memgraph::memory

View File

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

View File

@@ -12,15 +12,16 @@
#include "query/cypher_query_interpreter.hpp"
#include "query/frontend/ast/cypher_main_visitor.hpp"
#include "query/frontend/opencypher/parser.hpp"
#include "utils/synchronized.hpp"
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_int32(query_plan_cache_ttl, 60, "Time to live for cached query plans, in seconds.",
DEFINE_VALIDATED_int32(query_plan_cache_max_size, 1000, "Maximum number of query plans to cache.",
FLAG_IN_RANGE(0, std::numeric_limits<int32_t>::max()));
namespace memgraph::query {
CachedPlan::CachedPlan(std::unique_ptr<LogicalPlan> plan) : plan_(std::move(plan)) {}
PlanWrapper::PlanWrapper(std::unique_ptr<LogicalPlan> plan) : plan_(std::move(plan)) {}
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> &params,
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config) {
@@ -127,28 +128,24 @@ std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery
std::move(symbol_table));
}
std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
const Parameters &parameters, utils::SkipList<PlanCacheEntry> *plan_cache,
DbAccessor *db_accessor,
const std::vector<Identifier *> &predefined_identifiers) {
std::optional<utils::SkipList<PlanCacheEntry>::Accessor> plan_cache_access;
std::shared_ptr<PlanWrapper> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
const Parameters &parameters, PlanCacheLRU *plan_cache,
DbAccessor *db_accessor,
const std::vector<Identifier *> &predefined_identifiers) {
if (plan_cache) {
plan_cache_access.emplace(plan_cache->access());
auto it = plan_cache_access->find(hash);
if (it != plan_cache_access->end()) {
if (it->second->IsExpired()) {
plan_cache_access->remove(hash);
} else {
return it->second;
}
auto existing_plan = plan_cache->WithLock([&](auto &cache) { return cache.get(hash); });
if (existing_plan.has_value()) {
return existing_plan.value();
}
}
auto plan = std::make_shared<CachedPlan>(
auto plan = std::make_shared<PlanWrapper>(
MakeLogicalPlan(std::move(ast_storage), query, parameters, db_accessor, predefined_identifiers));
if (plan_cache_access) {
plan_cache_access->insert({hash, plan});
if (plan_cache) {
plan_cache->WithLock([&](auto &cache) { cache.put(hash, plan); });
}
return plan;
}
} // namespace memgraph::query

View File

@@ -17,12 +17,14 @@
#include "query/frontend/stripped.hpp"
#include "query/plan/planner.hpp"
#include "utils/flag_validation.hpp"
#include "utils/lru_cache.hpp"
#include "utils/synchronized.hpp"
#include "utils/timer.hpp"
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(query_cost_planner);
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_int32(query_plan_cache_ttl);
DECLARE_int32(query_plan_cache_max_size);
namespace memgraph::query {
@@ -45,23 +47,17 @@ class LogicalPlan {
virtual const AstStorage &GetAstStorage() const = 0;
};
class CachedPlan {
class PlanWrapper {
public:
explicit CachedPlan(std::unique_ptr<LogicalPlan> plan);
explicit PlanWrapper(std::unique_ptr<LogicalPlan> plan);
const auto &plan() const { return plan_->GetRoot(); }
double cost() const { return plan_->GetCost(); }
const auto &symbol_table() const { return plan_->GetSymbolTable(); }
const auto &ast_storage() const { return plan_->GetAstStorage(); }
bool IsExpired() const {
// NOLINTNEXTLINE (modernize-use-nullptr)
return cache_timer_.Elapsed() > std::chrono::seconds(FLAGS_query_plan_cache_ttl);
};
private:
std::unique_ptr<LogicalPlan> plan_;
utils::Timer cache_timer_;
};
struct CachedQuery {
@@ -82,18 +78,6 @@ struct QueryCacheEntry {
CachedQuery second;
};
struct PlanCacheEntry {
bool operator==(const PlanCacheEntry &other) const { return first == other.first; }
bool operator<(const PlanCacheEntry &other) const { return first < other.first; }
bool operator==(const uint64_t &other) const { return first == other; }
bool operator<(const uint64_t &other) const { return first < other; }
uint64_t first;
// TODO: Maybe store the query string here and use it as a key with the hash
// so that we eliminate the risk of hash collisions.
std::shared_ptr<CachedPlan> second;
};
/**
* A container for data related to the parsing of a query.
*/
@@ -129,6 +113,9 @@ class SingleNodeLogicalPlan final : public LogicalPlan {
SymbolTable symbol_table_;
};
using PlanCacheLRU =
utils::Synchronized<utils::LRUCache<uint64_t, std::shared_ptr<query::PlanWrapper>>, utils::RWSpinLock>;
std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery *query, const Parameters &parameters,
DbAccessor *db_accessor,
const std::vector<Identifier *> &predefined_identifiers);
@@ -141,9 +128,9 @@ std::unique_ptr<LogicalPlan> MakeLogicalPlan(AstStorage ast_storage, CypherQuery
* If an identifier is contained there, we inject it at that place and remove it,
* because a predefined identifier can be used only in one scope.
*/
std::shared_ptr<CachedPlan> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
const Parameters &parameters, utils::SkipList<PlanCacheEntry> *plan_cache,
DbAccessor *db_accessor,
const std::vector<Identifier *> &predefined_identifiers = {});
std::shared_ptr<PlanWrapper> CypherQueryToPlan(uint64_t hash, AstStorage ast_storage, CypherQuery *query,
const Parameters &parameters, PlanCacheLRU *plan_cache,
DbAccessor *db_accessor,
const std::vector<Identifier *> &predefined_identifiers = {});
} // namespace memgraph::query

View File

@@ -43,23 +43,23 @@ struct CachedValue {
return cache_.get_allocator().GetMemoryResource();
}
// Func to check if cache_ contains value
bool CacheValue(const TypedValue &value) {
if (!value.IsList()) {
bool CacheValue(const TypedValue &maybe_list) {
if (!maybe_list.IsList()) {
return false;
}
const auto &list = value.ValueList();
auto &list = maybe_list.ValueList();
TypedValue::Hash hash{};
for (const TypedValue &element : list) {
for (auto &element : list) {
const auto key = hash(element);
auto &vector_values = cache_[key];
if (!IsValueInVec(vector_values, element)) {
vector_values.push_back(element);
vector_values.emplace_back(element);
}
}
return true;
}
// Func to cache_value inside cache_
// Func to check if cache_ contains value
bool ContainsValue(const TypedValue &value) const {
TypedValue::Hash hash{};
const auto key = hash(value);
@@ -70,7 +70,7 @@ struct CachedValue {
}
private:
bool IsValueInVec(const std::vector<TypedValue> &vec_values, const TypedValue &value) const {
static bool IsValueInVec(const std::vector<TypedValue> &vec_values, const TypedValue &value) {
return std::any_of(vec_values.begin(), vec_values.end(), [&value](auto &vec_value) {
const auto is_value_equal = vec_value == value;
if (is_value_equal.IsNull()) return false;
@@ -80,43 +80,37 @@ struct CachedValue {
};
// Class tracks keys for which user can cache values which help with faster search or faster retrieval
// in the future.
// in the future. Used for IN LIST operator.
class FrameChangeCollector {
public:
explicit FrameChangeCollector(utils::MemoryResource *mem) : tracked_values_(mem){};
explicit FrameChangeCollector() : tracked_values_(&memory_resource_){};
// Add tracking key to cache later value
CachedValue &AddTrackingKey(const std::string &key) {
const auto &[it, _] = tracked_values_.emplace(key, tracked_values_.get_allocator().GetMemoryResource());
return it->second;
}
// Is key tracked
bool IsKeyTracked(const std::string &key) const { return tracked_values_.contains(key); }
// Is value for given key cached
bool IsKeyValueCached(const std::string &key) const {
return tracked_values_.contains(key) && !tracked_values_.at(key).cache_.empty();
return IsKeyTracked(key) && !tracked_values_.at(key).cache_.empty();
}
// Reset value for tracking key
bool ResetTrackingValue(const std::string &key) {
if (tracked_values_.contains(key)) {
tracked_values_.erase(key);
AddTrackingKey(key);
if (!tracked_values_.contains(key)) {
return false;
}
tracked_values_.erase(key);
AddTrackingKey(key);
return true;
}
// Get value cached for tracking key, throws if key is not in tracked
CachedValue &GetCachedValue(const std::string &key) { return tracked_values_.at(key); }
// Checks for keys tracked
bool IsTrackingValues() const { return !tracked_values_.empty(); }
private:
// Key is output of utils::GetFrameChangeId, value is utils::pmr::unordered_map
utils::MonotonicBufferResource memory_resource_{0};
memgraph::utils::pmr::unordered_map<std::string, CachedValue> tracked_values_;
};
} // namespace memgraph::query

View File

@@ -480,12 +480,20 @@ bool SymbolGenerator::PreVisit(None &none) {
}
bool SymbolGenerator::PreVisit(Reduce &reduce) {
auto &scope = scopes_.back();
scope.in_reduce = true;
reduce.initializer_->Accept(*this);
reduce.list_->Accept(*this);
VisitWithIdentifiers(reduce.expression_, {reduce.accumulator_, reduce.identifier_});
return false;
}
bool SymbolGenerator::PostVisit(Reduce & /*reduce*/) {
auto &scope = scopes_.back();
scope.in_reduce = false;
return true;
}
bool SymbolGenerator::PreVisit(Extract &extract) {
extract.list_->Accept(*this);
VisitWithIdentifiers(extract.expression_, {extract.identifier_});
@@ -496,15 +504,23 @@ bool SymbolGenerator::PreVisit(Exists &exists) {
auto &scope = scopes_.back();
if (scope.in_set_property) {
throw utils::NotYetImplemented("Set property can not be used with exists, but only during matching!");
throw utils::NotYetImplemented("Exists cannot be used within SET clause.!");
}
if (scope.in_with) {
throw utils::NotYetImplemented("WITH can not be used with exists, but only during matching!");
throw utils::NotYetImplemented("Exists cannot be used within WITH!");
}
if (scope.in_return) {
throw utils::NotYetImplemented("RETURN can not be used with exists, but only during matching!");
throw utils::NotYetImplemented("Exists cannot be used within RETURN!");
}
if (scope.in_reduce) {
throw utils::NotYetImplemented("Exists cannot be used within REDUCE!");
}
if (scope.num_if_operators) {
throw utils::NotYetImplemented("IF operator cannot be used with exists, but only during matching!");
}
scope.in_exists = true;

View File

@@ -84,6 +84,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool PreVisit(Any &) override;
bool PreVisit(None &) override;
bool PreVisit(Reduce &) override;
bool PostVisit(Reduce &) override;
bool PreVisit(Extract &) override;
bool PreVisit(Exists & /*exists*/) override;
bool PostVisit(Exists & /*exists*/) override;
@@ -123,6 +124,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
bool in_match{false};
bool in_foreach{false};
bool in_exists{false};
bool in_reduce{false};
bool in_set_property{false};
bool in_call_subquery{false};
bool has_return{false};

View File

@@ -1156,11 +1156,16 @@ void MapNumericParameters(auto &parameter_mappings, const auto &input_parameters
}
TypedValue Date(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Optional<Or<String, Map>>>("date", args, nargs);
FType<Optional<Or<String, Map, LocalDateTime>>>("date", args, nargs);
if (nargs == 0) {
return TypedValue(utils::LocalDateTime(ctx.timestamp).date, ctx.memory);
}
if (args[0].IsLocalDateTime()) {
utils::Date date{args[0].ValueLocalDateTime().date};
return TypedValue(date, ctx.memory);
}
if (args[0].IsString()) {
const auto &[date_parameters, is_extended] = utils::ParseDateParameters(args[0].ValueString());
return TypedValue(utils::Date(date_parameters), ctx.memory);
@@ -1178,12 +1183,17 @@ TypedValue Date(const TypedValue *args, int64_t nargs, const FunctionContext &ct
}
TypedValue LocalTime(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Optional<Or<String, Map>>>("localtime", args, nargs);
FType<Optional<Or<String, Map, LocalDateTime>>>("localtime", args, nargs);
if (nargs == 0) {
return TypedValue(utils::LocalDateTime(ctx.timestamp).local_time, ctx.memory);
}
if (args[0].IsLocalDateTime()) {
utils::LocalTime local_time{args[0].ValueLocalDateTime().local_time};
return TypedValue(local_time, ctx.memory);
}
if (args[0].IsString()) {
const auto &[local_time_parameters, is_extended] = utils::ParseLocalTimeParameters(args[0].ValueString());
return TypedValue(utils::LocalTime(local_time_parameters), ctx.memory);

View File

@@ -270,36 +270,33 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
TypedValue Visit(InListOperator &in_list) override {
TypedValue *_list_ptr = nullptr;
TypedValue _list;
auto literal = in_list.expression1_->Accept(*this);
auto get_list_literal = [this, &in_list, &_list, &_list_ptr]() -> void {
auto get_list_literal = [this, &in_list]() -> TypedValue {
ReferenceExpressionEvaluator reference_expression_evaluator{frame_, symbol_table_, ctx_};
_list_ptr = in_list.expression2_->Accept(reference_expression_evaluator);
if (nullptr == _list_ptr) {
_list = in_list.expression2_->Accept(*this);
_list_ptr = &_list;
auto *list_ptr = in_list.expression2_->Accept(reference_expression_evaluator);
if (nullptr == list_ptr) {
return in_list.expression2_->Accept(*this);
}
return *list_ptr;
};
auto do_list_literal_checks = [this, &literal, &_list_ptr]() -> std::optional<TypedValue> {
MG_ASSERT(_list_ptr, "List literal should have been defined");
if (_list_ptr->IsNull()) {
auto do_list_literal_checks = [this, &literal](const TypedValue &list) -> std::optional<TypedValue> {
if (list.IsNull()) {
return TypedValue(ctx_->memory);
}
// Exceptions have higher priority than returning nulls when list expression
// is not null.
if (_list_ptr->type() != TypedValue::Type::List) {
throw QueryRuntimeException("IN expected a list, got {}.", _list_ptr->type());
if (list.type() != TypedValue::Type::List) {
throw QueryRuntimeException("IN expected a list, got {}.", list.type());
}
const auto &list = _list_ptr->ValueList();
const auto &list_value = list.ValueList();
// If literal is NULL there is no need to try to compare it with every
// element in the list since result of every comparison will be NULL. There
// is one special case that we must test explicitly: if list is empty then
// result is false since no comparison will be performed.
if (list.empty()) return TypedValue(false, ctx_->memory);
if (list_value.empty()) return TypedValue(false, ctx_->memory);
if (literal.IsNull()) return TypedValue(ctx_->memory);
return {};
};
@@ -312,14 +309,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
if (!frame_change_collector_->IsKeyValueCached(*cached_id)) {
// Check only first time if everything is okay, later when we use
// cache there is no need to check again as we did check first time
get_list_literal();
auto preoperational_checks = do_list_literal_checks();
auto list = get_list_literal();
auto preoperational_checks = do_list_literal_checks(list);
if (preoperational_checks) {
return std::move(*preoperational_checks);
}
auto &cached_value = frame_change_collector_->GetCachedValue(*cached_id);
cached_value.CacheValue(*_list_ptr);
spdlog::trace("Value cached {}", *cached_id);
// Don't move here because we don't want to remove the element from the frame
cached_value.CacheValue(list);
}
const auto &cached_value = frame_change_collector_->GetCachedValue(*cached_id);
@@ -334,16 +331,15 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
// When caching is not an option, we need to evaluate list literal every time
// and do the checks
get_list_literal();
auto preoperational_checks = do_list_literal_checks();
const auto list = get_list_literal();
auto preoperational_checks = do_list_literal_checks(list);
if (preoperational_checks) {
return std::move(*preoperational_checks);
}
const auto &list = _list.ValueList();
spdlog::trace("Not using cache on IN LIST operator");
const auto &list_value = list.ValueList();
auto has_null = false;
for (const auto &element : list) {
for (const auto &element : list_value) {
auto result = literal == element;
if (result.IsNull()) {
has_null = true;

View File

@@ -37,6 +37,7 @@
#include "dbms/database.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/global.hpp"
#include "dbms/inmemory/storage_helper.hpp"
#include "flags/run_time_configurable.hpp"
#include "glue/communication.hpp"
#include "license/license.hpp"
@@ -94,16 +95,17 @@
#include "utils/on_scope_exit.hpp"
#include "utils/readable_size.hpp"
#include "utils/settings.hpp"
#include "utils/stat.hpp"
#include "utils/string.hpp"
#include "utils/tsc.hpp"
#include "utils/typeinfo.hpp"
#include "utils/variant_helpers.hpp"
#include "dbms/dbms_handler.hpp"
#include "dbms/replication_handler.hpp"
#include "query/auth_query_handler.hpp"
#include "query/interpreter_context.hpp"
#include "replication/state.hpp"
#include "storage/v2/replication/replication_handler.hpp"
namespace memgraph::metrics {
extern Event ReadQuery;
@@ -270,7 +272,8 @@ inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode
class ReplQueryHandler final : public query::ReplicationQueryHandler {
public:
explicit ReplQueryHandler(storage::Storage *db) : db_(db), handler_{db_->repl_state_, *db_} {}
explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler, memgraph::replication::ReplicationState *repl_state)
: dbms_handler_(dbms_handler), handler_{*repl_state, *dbms_handler} {}
/// @throw QueryRuntimeException if an error ocurred.
void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override {
@@ -314,10 +317,6 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
throw QueryRuntimeException("Replica can't register another replica!");
}
if (name == memgraph::replication::kReservedReplicationRoleName) {
throw QueryRuntimeException("This replica name is reserved and can not be used as replica name!");
}
auto repl_mode = convertToReplicationMode(sync_mode);
auto maybe_ip_and_port =
@@ -330,8 +329,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
.port = port,
.replica_check_frequency = replica_check_frequency,
.ssl = std::nullopt};
using storage::RegistrationMode;
auto ret = handler_.RegisterReplica(RegistrationMode::MUST_BE_INSTANTLY_VALID, config);
auto ret = handler_.RegisterReplica(config);
if (ret.HasError()) {
throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name));
}
@@ -344,7 +342,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
void DropReplica(std::string_view replica_name) override {
auto const result = handler_.UnregisterReplica(replica_name);
switch (result) {
using enum memgraph::storage::UnregisterReplicaResult;
using enum memgraph::dbms::UnregisterReplicaResult;
case NOT_MAIN:
throw QueryRuntimeException("Replica can't unregister a replica!");
case COULD_NOT_BE_PERSISTED:
@@ -358,13 +356,22 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
using Replica = ReplicationQueryHandler::Replica;
std::vector<Replica> ShowReplicas() const override {
auto const &replState = db_->repl_state_;
if (replState.IsReplica()) {
if (handler_.IsReplica()) {
// replica can't show registered replicas (it shouldn't have any)
throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!");
}
auto repl_infos = db_->ReplicasInfo();
// TODO: Combine results? Have a single place with clients???
// Also authentication checks (replica + database visibility)
std::vector<storage::ReplicaInfo> repl_infos{};
dbms_handler_->ForOne([&repl_infos](dbms::Database *db) -> bool {
auto infos = db->storage()->ReplicasInfo();
if (!infos.empty()) {
repl_infos = std::move(infos);
return true;
}
return false;
});
std::vector<Replica> replicas;
replicas.reserve(repl_infos.size());
@@ -408,8 +415,8 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
}
private:
storage::Storage *db_;
storage::ReplicationHandler handler_;
dbms::DbmsHandler *dbms_handler_;
dbms::ReplicationHandler handler_;
};
/// returns false if the replication role can't be set
@@ -418,7 +425,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_context, const Parameters &parameters) {
AuthQueryHandler *auth = interpreter_context->auth;
#ifdef MG_ENTERPRISE
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
#endif
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
@@ -702,8 +709,10 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_
}
} // namespace
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters, storage::Storage *storage,
const query::InterpreterConfig &config, std::vector<Notification> *notifications) {
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters,
dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config,
std::vector<Notification> *notifications,
memgraph::replication::ReplicationState *repl_state) {
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
EvaluationContext evaluation_context;
@@ -723,7 +732,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
notifications->emplace_back(SeverityLevel::WARNING, NotificationCode::REPLICA_PORT_WARNING,
"Be careful the replication port must be different from the memgraph port!");
}
callback.fn = [handler = ReplQueryHandler{storage}, role = repl_query->role_, maybe_port]() mutable {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, role = repl_query->role_,
maybe_port]() mutable {
handler.SetReplicationRole(role, maybe_port);
return std::vector<std::vector<TypedValue>>();
};
@@ -735,7 +745,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
}
case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: {
callback.header = {"replication role"};
callback.fn = [handler = ReplQueryHandler{storage}] {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}] {
auto mode = handler.ShowReplicationRole();
switch (mode) {
case ReplicationQuery::ReplicationRole::MAIN: {
@@ -754,7 +764,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
auto socket_address = repl_query->socket_address_->Accept(evaluator);
const auto replica_check_frequency = config.replication_replica_check_frequency;
callback.fn = [handler = ReplQueryHandler{storage}, name, socket_address, sync_mode,
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, name, socket_address, sync_mode,
replica_check_frequency]() mutable {
handler.RegisterReplica(name, std::string(socket_address.ValueString()), sync_mode, replica_check_frequency);
return std::vector<std::vector<TypedValue>>();
@@ -765,7 +775,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
}
case ReplicationQuery::Action::DROP_REPLICA: {
const auto &name = repl_query->replica_name_;
callback.fn = [handler = ReplQueryHandler{storage}, name]() mutable {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, name]() mutable {
handler.DropReplica(name);
return std::vector<std::vector<TypedValue>>();
};
@@ -777,7 +787,7 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
callback.header = {
"name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master",
"state"};
callback.fn = [handler = ReplQueryHandler{storage}, replica_nfields = callback.header.size()] {
callback.fn = [handler = ReplQueryHandler{dbms_handler, repl_state}, replica_nfields = callback.header.size()] {
const auto &replicas = handler.ShowReplicas();
auto typed_replicas = std::vector<std::vector<TypedValue>>{};
typed_replicas.reserve(replicas.size());
@@ -1203,7 +1213,7 @@ struct TxTimeout {
};
struct PullPlan {
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters &parameters, bool is_profile_query,
explicit PullPlan(std::shared_ptr<PlanWrapper> plan, const Parameters &parameters, bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
std::optional<std::string> username, std::atomic<TransactionStatus> *transaction_status,
std::shared_ptr<utils::AsyncTimer> tx_timer,
@@ -1216,7 +1226,7 @@ struct PullPlan {
std::map<std::string, TypedValue> *summary);
private:
std::shared_ptr<CachedPlan> plan_ = nullptr;
std::shared_ptr<PlanWrapper> plan_ = nullptr;
plan::UniqueCursorPtr cursor_ = nullptr;
Frame frame_;
ExecutionContext ctx_;
@@ -1243,7 +1253,7 @@ struct PullPlan {
bool use_monotonic_memory_;
};
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &parameters, const bool is_profile_query,
PullPlan::PullPlan(const std::shared_ptr<PlanWrapper> plan, const Parameters &parameters, const bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
std::optional<std::string> username, std::atomic<TransactionStatus> *transaction_status,
std::shared_ptr<utils::AsyncTimer> tx_timer, TriggerContextCollector *trigger_context_collector,
@@ -1399,42 +1409,16 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
using RWType = plan::ReadWriteTypeChecker::RWType;
bool IsWriteQueryOnMainMemoryReplica(storage::Storage *storage,
const query::plan::ReadWriteTypeChecker::RWType query_type) {
if (auto storage_mode = storage->GetStorageMode(); storage_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL ||
storage_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
auto const &replState = storage->repl_state_;
return replState.IsReplica() && (query_type == RWType::W || query_type == RWType::RW);
}
return false;
}
bool IsReplica(storage::Storage *storage) {
if (auto storage_mode = storage->GetStorageMode(); storage_mode == storage::StorageMode::IN_MEMORY_ANALYTICAL ||
storage_mode == storage::StorageMode::IN_MEMORY_TRANSACTIONAL) {
auto const &replState = storage->repl_state_;
return replState.IsReplica();
}
return false;
bool IsQueryWrite(const query::plan::ReadWriteTypeChecker::RWType query_type) {
return query_type == RWType::W || query_type == RWType::RW;
}
} // namespace
#ifdef MG_ENTERPRISE
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config, memgraph::dbms::DbmsHandler *handler,
query::AuthQueryHandler *ah, query::AuthChecker *ac)
: db_handler(handler), config(interpreter_config), auth(ah), auth_checker(ac) {}
#else
InterpreterContext::InterpreterContext(InterpreterConfig interpreter_config,
memgraph::utils::Gatekeeper<memgraph::dbms::Database> *db_gatekeeper,
query::AuthQueryHandler *ah, query::AuthChecker *ac)
: db_gatekeeper(db_gatekeeper), config(interpreter_config), auth(ah), auth_checker(ac) {}
#endif
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
#ifndef MG_ENTERPRISE
auto db_acc = interpreter_context_->db_gatekeeper->access();
auto db_acc = interpreter_context_->dbms_handler->Get();
MG_ASSERT(db_acc, "Database accessor needs to be valid");
current_db_.db_acc_ = std::move(db_acc);
#endif
@@ -1551,7 +1535,6 @@ inline static void TryCaching(const AstStorage &ast_storage, FrameChangeCollecto
continue;
}
frame_change_collector->AddTrackingKey(*cached_id);
spdlog::trace("Tracking {} operator, by id: {}", InListOperator::kType.name, *cached_id);
}
}
@@ -1612,8 +1595,6 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
// If this is LOAD CSV query, use PoolResource without MonotonicMemoryResource as we want to reuse allocated memory
auto use_monotonic_memory =
!contains_csv && !IsCallBatchedProcedureQuery(clauses) && !IsAllShortestPathsQuery(clauses);
spdlog::trace("PrepareCypher has {} encountered all shortest paths and will {} use of monotonic memory",
IsAllShortestPathsQuery(clauses) ? "" : "not", use_monotonic_memory ? "" : "not");
MG_ASSERT(current_db.execution_db_accessor_, "Cypher query expects a current DB transaction");
auto *dba =
@@ -2124,10 +2105,7 @@ PreparedQuery PrepareAnalyzeGraphQuery(ParsedQuery parsed_query, bool in_explici
// Creating an index influences computed plan costs.
auto invalidate_plan_cache = [plan_cache = current_db.db_acc_->get()->plan_cache()] {
auto access = plan_cache->access();
for (auto &kv : access) {
access.remove(kv.first);
}
plan_cache->WithLock([&](auto &cache) { cache.reset(); });
};
utils::OnScopeExit cache_invalidator(invalidate_plan_cache);
@@ -2172,10 +2150,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
// Creating an index influences computed plan costs.
auto invalidate_plan_cache = [plan_cache = db_acc->plan_cache()] {
auto access = plan_cache->access();
for (auto &kv : access) {
access.remove(kv.first);
}
plan_cache->WithLock([&](auto &cache) { cache.reset(); });
};
auto *storage = db_acc->storage();
@@ -2286,21 +2261,16 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
}
PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::vector<Notification> *notifications, CurrentDB &current_db,
const InterpreterConfig &config) {
std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler,
const InterpreterConfig &config,
memgraph::replication::ReplicationState *repl_state) {
if (in_explicit_transaction) {
throw ReplicationModificationInMulticommandTxException();
}
MG_ASSERT(current_db.db_acc_, "Replication query expects a current DB");
storage::Storage *storage = current_db.db_acc_->get()->storage();
if (storage->GetStorageMode() == storage::StorageMode::ON_DISK_TRANSACTIONAL) {
throw ReplicationDisabledOnDiskStorage();
}
auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, storage, config, notifications);
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, config,
notifications, repl_state);
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
@@ -2773,7 +2743,8 @@ PreparedQuery PrepareStorageModeQuery(ParsedQuery parsed_query, const bool in_ex
"transactions using 'SHOW TRANSACTIONS' query and ensure no other transactions are active.");
}
callback = [requested_mode, storage = db_acc->storage()]() -> std::function<void()> {
callback = [requested_mode,
storage = static_cast<storage::InMemoryStorage *>(db_acc->storage())]() -> std::function<void()> {
// SetStorageMode will probably be handled at the Database level
return [storage, requested_mode] { storage->SetStorageMode(requested_mode); };
}();
@@ -2836,15 +2807,11 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli
std::move(parsed_query.required_privileges),
[storage](AnyStream * /*stream*/, std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> {
auto *mem_storage = static_cast<storage::InMemoryStorage *>(storage);
if (auto maybe_error = mem_storage->CreateSnapshot(storage->repl_state_, {}); maybe_error.HasError()) {
if (auto maybe_error = mem_storage->CreateSnapshot(); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica:
throw utils::BasicException(
"Failed to create a snapshot. Replica instances are not allowed to create them.");
case storage::InMemoryStorage::CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/replication"));
break;
case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
@@ -3116,14 +3083,22 @@ PreparedQuery PrepareSystemInfoQuery(ParsedQuery parsed_query, bool in_explicit_
handler = [storage = current_db.db_acc_->get()->storage(), interpreter_isolation_level,
next_transaction_isolation_level] {
auto info = storage->GetBaseInfo();
const auto vm_max_map_count = utils::GetVmMaxMapCount();
const int64_t vm_max_map_count_storage_info =
vm_max_map_count.has_value() ? vm_max_map_count.value() : memgraph::utils::VM_MAX_MAP_COUNT_DEFAULT;
auto memory_res = utils::GetReadableSize(static_cast<double>(info.memory_res));
auto memory_tracked = utils::GetReadableSize(static_cast<double>(utils::total_memory_tracker.Amount()));
spdlog::trace("Memory res: {}, memory tracked: {}", memory_res, memory_tracked);
std::vector<std::vector<TypedValue>> results{
{TypedValue("name"), TypedValue(storage->id())},
{TypedValue("vertex_count"), TypedValue(static_cast<int64_t>(info.vertex_count))},
{TypedValue("edge_count"), TypedValue(static_cast<int64_t>(info.edge_count))},
{TypedValue("average_degree"), TypedValue(info.average_degree)},
{TypedValue("memory_usage"), TypedValue(utils::GetReadableSize(static_cast<double>(info.memory_usage)))},
{TypedValue("vm_max_map_count"), TypedValue(vm_max_map_count_storage_info)},
{TypedValue("memory_res"), TypedValue(utils::GetReadableSize(static_cast<double>(info.memory_res)))},
{TypedValue("disk_usage"), TypedValue(utils::GetReadableSize(static_cast<double>(info.disk_usage)))},
{TypedValue("memory_allocated"),
{TypedValue("memory_tracked"),
TypedValue(utils::GetReadableSize(static_cast<double>(utils::total_memory_tracker.Amount())))},
{TypedValue("allocation_limit"),
TypedValue(utils::GetReadableSize(static_cast<double>(utils::total_memory_tracker.HardLimit())))},
@@ -3377,22 +3352,23 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &current_db,
InterpreterContext *interpreter_context,
std::optional<std::function<void(std::string_view)>> on_change_cb) {
std::optional<std::function<void(std::string_view)>> on_change_cb,
memgraph::replication::ReplicationState *repl_state) {
#ifdef MG_ENTERPRISE
if (!license::global_license_checker.IsEnterpriseValidFast()) {
throw QueryException("Trying to use enterprise feature without a valid license.");
}
// TODO: Remove once replicas support multi-tenant replication
if (!current_db.db_acc_) throw DatabaseContextRequiredException("Multi database queries require a defined database.");
if (IsReplica(current_db.db_acc_->get()->storage())) {
throw QueryException("Query forbidden on the replica!");
}
auto *query = utils::Downcast<MultiDatabaseQuery>(parsed_query.query);
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
switch (query->action_) {
case MultiDatabaseQuery::Action::CREATE:
if (repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{
{"STATUS"},
std::move(parsed_query.required_privileges),
@@ -3435,6 +3411,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
if (current_db.in_explicit_db_) {
throw QueryException("Database switching is prohibited if session explicitly defines the used database");
}
if (!dbms::allow_mt_repl && repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{{"STATUS"},
std::move(parsed_query.required_privileges),
[db_name = query->db_name_, db_handler, &current_db, on_change_cb](
@@ -3466,6 +3445,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
query->db_name_};
case MultiDatabaseQuery::Action::DROP:
if (repl_state->IsReplica()) {
throw QueryException("Query forbidden on the replica!");
}
return PreparedQuery{
{"STATUS"},
std::move(parsed_query.required_privileges),
@@ -3473,11 +3455,9 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
std::vector<std::vector<TypedValue>> status;
memgraph::dbms::DeleteResult success{};
try {
// Remove database
success = db_handler->Delete(db_name);
auto success = db_handler->Delete(db_name);
if (!success.HasError()) {
// Remove from auth
auth->DeleteDatabase(db_name);
@@ -3526,14 +3506,9 @@ PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB &cur
if (!license::global_license_checker.IsEnterpriseValidFast()) {
throw QueryException("Trying to use enterprise feature without a valid license.");
}
// TODO: Remove once replicas support multi-tenant replication
auto &replState = storage->repl_state_;
if (replState.IsReplica()) {
throw QueryException("SHOW DATABASES forbidden on the replica!");
}
// TODO pick directly from ic
auto *db_handler = interpreter_context->db_handler;
auto *db_handler = interpreter_context->dbms_handler;
AuthQueryHandler *auth = interpreter_context->auth;
Callback callback;
@@ -3637,7 +3612,7 @@ void Interpreter::RollbackTransaction() {
void Interpreter::SetCurrentDB(std::string_view db_name, bool in_explicit_db) {
// Can throw
// do we lock here?
current_db_.SetCurrentDB(interpreter_context_->db_handler->Get(db_name), in_explicit_db);
current_db_.SetCurrentDB(interpreter_context_->dbms_handler->Get(db_name), in_explicit_db);
}
#endif
@@ -3714,8 +3689,6 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
// Setup QueryExecution
// its MemoryResource is mostly used for allocations done on Frame and storing `row`s
if (usePool) {
spdlog::trace("PrepareCypher has {} encountered all shortest paths, QueryExecution will use PoolResource",
hasAllShortestPaths ? "" : "not");
query_executions_.emplace_back(QueryExecution::Create(utils::PoolResource(128, kExecutionPoolMaxBlockSize)));
} else {
query_executions_.emplace_back(QueryExecution::Create(utils::MonotonicBufferResource(kExecutionMemoryBlockSize)));
@@ -3761,7 +3734,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
std::visit([](auto &execution_memory) -> utils::MemoryResource * { return &execution_memory; },
query_execution->execution_memory);
frame_change_collector_.reset();
frame_change_collector_.emplace(memory_resource);
frame_change_collector_.emplace();
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
current_db_, memory_resource, &query_execution->notifications, username_,
@@ -3794,9 +3767,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
&query_execution->notifications, current_db_);
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
/// TODO: make replication DB agnostic
prepared_query =
PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
current_db_, interpreter_context_->config);
prepared_query = PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_,
&query_execution->notifications, *interpreter_context_->dbms_handler,
interpreter_context_->config, interpreter_context_->repl_state);
} else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, current_db_);
} else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) {
@@ -3836,8 +3809,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
throw MultiDatabaseQueryInMulticommandTxException();
}
/// SYSTEM (Replication) + INTERPRETER
prepared_query =
PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_);
prepared_query = PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_,
interpreter_context_->repl_state);
} else if (utils::Downcast<ShowDatabasesQuery>(parsed_query.query)) {
/// SYSTEM PURE ("SHOW DATABASES")
/// INTERPRETER (TODO: "SHOW DATABASE")
@@ -3859,7 +3832,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
UpdateTypeCount(rw_type);
if (IsWriteQueryOnMainMemoryReplica(current_db_.db_acc_->get()->storage(), rw_type)) {
if (interpreter_context_->repl_state->IsReplica() && IsQueryWrite(rw_type)) {
query_execution = nullptr;
throw QueryException("Write query forbidden on the replica!");
}
@@ -4088,7 +4061,8 @@ void Interpreter::Commit() {
auto commit_confirmed_by_all_sync_repplicas = true;
auto maybe_commit_error = current_db_.db_transactional_accessor_->Commit();
auto maybe_commit_error =
current_db_.db_transactional_accessor_->Commit(std::nullopt, interpreter_context_->repl_state->IsMain());
if (maybe_commit_error.HasError()) {
const auto &error = maybe_commit_error.GetError();

View File

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

View File

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

View File

@@ -27,6 +27,7 @@
#include <cppitertools/chain.hpp>
#include <cppitertools/imap.hpp>
#include "memory/query_memory_control.hpp"
#include "query/common.hpp"
#include "spdlog/spdlog.h"
@@ -57,6 +58,7 @@
#include "utils/logging.hpp"
#include "utils/memory.hpp"
#include "utils/message.hpp"
#include "utils/on_scope_exit.hpp"
#include "utils/pmr/deque.hpp"
#include "utils/pmr/list.hpp"
#include "utils/pmr/unordered_map.hpp"
@@ -2588,39 +2590,68 @@ void Delete::DeleteCursor::UpdateDeleteBuffer(Frame &frame, ExecutionContext &co
expression_results.emplace_back(expression->Accept(evaluator));
}
auto vertex_auth_checker = [&context](const VertexAccessor &va) -> bool {
#ifdef MG_ENTERPRISE
return !(license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE));
#else
return true;
#endif
};
auto edge_auth_checker = [&context](const EdgeAccessor &ea) -> bool {
#ifdef MG_ENTERPRISE
return !(
license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE)));
#else
return true;
#endif
};
for (TypedValue &expression_result : expression_results) {
AbortCheck(context);
switch (expression_result.type()) {
case TypedValue::Type::Vertex: {
auto va = expression_result.ValueVertex();
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
if (vertex_auth_checker(va)) {
buffer_.nodes.push_back(va);
} else {
throw QueryRuntimeException("Vertex not deleted due to not having enough permission!");
}
#endif
buffer_.nodes.push_back(va);
break;
}
case TypedValue::Type::Edge: {
auto ea = expression_result.ValueEdge();
#ifdef MG_ENTERPRISE
if (license::global_license_checker.IsEnterpriseValidFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW,
query::AuthQuery::FineGrainedPrivilege::UPDATE))) {
if (edge_auth_checker(ea)) {
buffer_.edges.push_back(ea);
} else {
throw QueryRuntimeException("Edge not deleted due to not having enough permission!");
}
#endif
buffer_.edges.push_back(ea);
break;
}
case TypedValue::Type::Path: {
auto path = expression_result.ValuePath();
#ifdef MG_ENTERPRISE
auto edges_res = std::any_of(path.edges().cbegin(), path.edges().cend(),
[&edge_auth_checker](const auto &ea) { return !edge_auth_checker(ea); });
auto vertices_res = std::any_of(path.vertices().cbegin(), path.vertices().cend(),
[&vertex_auth_checker](const auto &va) { return !vertex_auth_checker(va); });
if (edges_res || vertices_res) {
throw QueryRuntimeException(
"Path not deleted due to not having enough permission on all edges and vertices on the path!");
}
#endif
buffer_.nodes.insert(buffer_.nodes.begin(), path.vertices().begin(), path.vertices().end());
buffer_.edges.insert(buffer_.edges.begin(), path.edges().begin(), path.edges().end());
}
case TypedValue::Type::Null:
break;
// check we're not trying to delete anything except vertices and edges
default:
throw QueryRuntimeException("Only edges and vertices can be deleted.");
throw QueryRuntimeException("Edges, vertices and paths can be deleted.");
}
}
}
@@ -2789,15 +2820,13 @@ SetProperties::SetPropertiesCursor::SetPropertiesCursor(const SetProperties &sel
namespace {
template <typename T>
concept AccessorWithProperties =
requires(T value, storage::PropertyId property_id, storage::PropertyValue property_value,
std::map<storage::PropertyId, storage::PropertyValue> properties) {
{
value.ClearProperties()
} -> std::same_as<storage::Result<std::map<storage::PropertyId, storage::PropertyValue>>>;
{ value.SetProperty(property_id, property_value) };
{ value.UpdateProperties(properties) };
};
concept AccessorWithProperties = requires(T value, storage::PropertyId property_id,
storage::PropertyValue property_value,
std::map<storage::PropertyId, storage::PropertyValue> properties) {
{ value.ClearProperties() } -> std::same_as<storage::Result<std::map<storage::PropertyId, storage::PropertyValue>>>;
{value.SetProperty(property_id, property_value)};
{value.UpdateProperties(properties)};
};
/// Helper function that sets the given values on either a Vertex or an Edge.
///
@@ -2869,6 +2898,8 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
auto update_props = [&, record](PropertiesMap &new_properties) {
auto updated_properties = UpdatePropertiesChecked(record, new_properties);
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
context->execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += new_properties.size();
if (should_register_change) {
for (const auto &[id, old_value, new_value] : updated_properties) {
@@ -3432,7 +3463,7 @@ class AggregateCursor : public Cursor {
SCOPED_PROFILE_OP_BY_REF(self_);
if (!pulled_all_input_) {
ProcessAll(&frame, &context);
if (!ProcessAll(&frame, &context) && self_.AreAllAggregationsForCollecting()) return false;
pulled_all_input_ = true;
aggregation_it_ = aggregation_.begin();
@@ -3456,7 +3487,6 @@ class AggregateCursor : public Cursor {
return true;
}
}
if (aggregation_it_ == aggregation_.end()) return false;
// place aggregation values on the frame
@@ -3536,12 +3566,16 @@ class AggregateCursor : public Cursor {
* cache cardinality depends on number of
* aggregation results, and not on the number of inputs.
*/
void ProcessAll(Frame *frame, ExecutionContext *context) {
bool ProcessAll(Frame *frame, ExecutionContext *context) {
ExpressionEvaluator evaluator(frame, context->symbol_table, context->evaluation_context, context->db_accessor,
storage::View::NEW);
bool pulled = false;
while (input_cursor_->Pull(*frame, *context)) {
ProcessOne(*frame, &evaluator);
pulled = true;
}
if (!pulled) return false;
// post processing
for (size_t pos = 0; pos < self_.aggregations_.size(); ++pos) {
@@ -3575,6 +3609,7 @@ class AggregateCursor : public Cursor {
break;
}
}
return true;
}
/**
@@ -3788,6 +3823,12 @@ UniqueCursorPtr Aggregate::MakeCursor(utils::MemoryResource *mem) const {
return MakeUniqueCursorPtr<AggregateCursor>(mem, *this, mem);
}
auto Aggregate::AreAllAggregationsForCollecting() const -> bool {
return std::all_of(aggregations_.begin(), aggregations_.end(), [](const auto &agg) {
return agg.op == Aggregation::Op::COLLECT_LIST || agg.op == Aggregation::Op::COLLECT_MAP;
});
}
Skip::Skip(const std::shared_ptr<LogicalOperator> &input, Expression *expression)
: input_(input), expression_(expression) {}
@@ -4588,7 +4629,7 @@ UniqueCursorPtr OutputTableStream::MakeCursor(utils::MemoryResource *mem) const
CallProcedure::CallProcedure(std::shared_ptr<LogicalOperator> input, std::string name, std::vector<Expression *> args,
std::vector<std::string> fields, std::vector<Symbol> symbols, Expression *memory_limit,
size_t memory_scale, bool is_write, bool void_procedure)
size_t memory_scale, bool is_write, int64_t procedure_id, bool void_procedure)
: input_(input ? input : std::make_shared<Once>()),
procedure_name_(name),
arguments_(args),
@@ -4597,6 +4638,7 @@ CallProcedure::CallProcedure(std::shared_ptr<LogicalOperator> input, std::string
memory_limit_(memory_limit),
memory_scale_(memory_scale),
is_write_(is_write),
procedure_id_(procedure_id),
void_procedure_(void_procedure) {}
ACCEPT_WITH_INPUT(CallProcedure);
@@ -4625,7 +4667,7 @@ namespace {
void CallCustomProcedure(const std::string_view fully_qualified_procedure_name, const mgp_proc &proc,
const std::vector<Expression *> &args, mgp_graph &graph, ExpressionEvaluator *evaluator,
utils::MemoryResource *memory, std::optional<size_t> memory_limit, mgp_result *result,
const bool call_initializer = false) {
int64_t procedure_id, uint64_t transaction_id, const bool call_initializer = false) {
static_assert(std::uses_allocator_v<mgp_value, utils::Allocator<mgp_value>>,
"Expected mgp_value to use custom allocator and makes STL "
"containers aware of that");
@@ -4657,12 +4699,46 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name,
if (memory_limit) {
SPDLOG_INFO("Running '{}' with memory limit of {}", fully_qualified_procedure_name,
utils::GetReadableSize(*memory_limit));
utils::LimitedMemoryResource limited_mem(memory, *memory_limit);
mgp_memory proc_memory{&limited_mem};
// Only allocations which can leak memory are
// our own mgp object allocations. Jemalloc can track
// memory correctly, but some memory may not be released
// immediately, so we want to give user info on leak still
// considering our allocations
utils::MemoryTrackingResource memory_tracking_resource{memory, *memory_limit};
// if we are already tracking, no harm no faul
// if we are not tracking, we need to start now, with unlimited memory
// for query, but limited for procedure
// check if transaction is tracked currently, so we
// can disable tracking on that arena if it is not
// once we are done with procedure tracking
bool is_transaction_tracked = memgraph::memory::IsTransactionTracked(transaction_id);
if (!is_transaction_tracked) {
// start tracking with unlimited limit on query
// which is same as not being tracked at all
memgraph::memory::TryStartTrackingOnTransaction(transaction_id, memgraph::memory::UNLIMITED_MEMORY);
}
memgraph::memory::StartTrackingCurrentThreadTransaction(transaction_id);
// due to mgp_batch_read_proc and mgp_batch_write_proc
// we can return to execution without exhausting whole
// memory. Here we need to update tracking
memgraph::memory::CreateOrContinueProcedureTracking(transaction_id, procedure_id, *memory_limit);
mgp_memory proc_memory{&memory_tracking_resource};
MG_ASSERT(result->signature == &proc.results);
utils::OnScopeExit on_scope_exit{[transaction_id = transaction_id]() {
memgraph::memory::StopTrackingCurrentThreadTransaction(transaction_id);
memgraph::memory::PauseProcedureTracking(transaction_id);
}};
// TODO: What about cross library boundary exceptions? OMG C++?!
proc.cb(&proc_args, &graph, result, &proc_memory);
size_t leaked_bytes = limited_mem.GetAllocatedBytes();
auto leaked_bytes = memory_tracking_resource.GetAllocatedBytes();
if (leaked_bytes > 0U) {
spdlog::warn("Query procedure '{}' leaked {} *tracked* bytes", fully_qualified_procedure_name, leaked_bytes);
}
@@ -4770,8 +4846,10 @@ class CallProcedureCursor : public Cursor {
auto *memory = self_->memory_resource;
auto memory_limit = EvaluateMemoryLimit(evaluator, self_->memory_limit_, self_->memory_scale_);
auto graph = mgp_graph::WritableGraph(*context.db_accessor, graph_view, context);
const auto transaction_id = context.db_accessor->GetTransactionId();
MG_ASSERT(transaction_id.has_value());
CallCustomProcedure(self_->procedure_name_, *proc, self_->arguments_, graph, &evaluator, memory, memory_limit,
result_, call_initializer);
result_, self_->procedure_id_, transaction_id.value(), call_initializer);
if (call_initializer) call_initializer = false;
@@ -5336,7 +5414,7 @@ class HashJoinCursor : public Cursor {
// Check if the join value from the pulled frame is shared with any left frames
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
storage::View::OLD);
auto right_value = self_.hash_join_condition_->expression1_->Accept(evaluator);
auto right_value = self_.hash_join_condition_->expression2_->Accept(evaluator);
if (hashtable_.contains(right_value)) {
// If so, finish pulling for now and proceed to joining the pulled frame
right_op_frame_.assign(frame.elems().begin(), frame.elems().end());
@@ -5354,7 +5432,8 @@ class HashJoinCursor : public Cursor {
restore_frame(self_.left_symbols_, *left_op_frame_it_);
left_op_frame_it_++;
// When all left frames with the common value have been joined, move on to pulling and joining the next right frame
// When all left frames with the common value have been joined, move on to pulling and joining the next right
// frame
if (common_value_found_ && left_op_frame_it_ == hashtable_[common_value].end()) {
common_value_found_ = false;
}
@@ -5383,7 +5462,7 @@ class HashJoinCursor : public Cursor {
while (left_op_cursor_->Pull(frame, context)) {
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
storage::View::OLD);
auto left_value = self_.hash_join_condition_->expression2_->Accept(evaluator);
auto left_value = self_.hash_join_condition_->expression1_->Accept(evaluator);
if (left_value.type() != TypedValue::Type::Null) {
hashtable_[left_value].emplace_back(frame.elems().begin(), frame.elems().end());
}

View File

@@ -1758,6 +1758,9 @@ class Aggregate : public memgraph::query::plan::LogicalOperator {
Aggregate() = default;
Aggregate(const std::shared_ptr<LogicalOperator> &input, const std::vector<Element> &aggregations,
const std::vector<Expression *> &group_by, const std::vector<Symbol> &remember);
auto AreAllAggregationsForCollecting() const -> bool;
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
std::vector<Symbol> ModifiedSymbols(const SymbolTable &) const override;
@@ -2361,7 +2364,7 @@ class CallProcedure : public memgraph::query::plan::LogicalOperator {
CallProcedure() = default;
CallProcedure(std::shared_ptr<LogicalOperator> input, std::string name, std::vector<Expression *> arguments,
std::vector<std::string> fields, std::vector<Symbol> symbols, Expression *memory_limit,
size_t memory_scale, bool is_write, bool void_procedure = false);
size_t memory_scale, bool is_write, int64_t procedure_id, bool void_procedure = false);
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
@@ -2383,6 +2386,7 @@ class CallProcedure : public memgraph::query::plan::LogicalOperator {
Expression *memory_limit_{nullptr};
size_t memory_scale_{1024U};
bool is_write_;
int64_t procedure_id_;
bool void_procedure_;
mutable utils::MonotonicBufferResource monotonic_memory{1024UL * 1024UL};
utils::MemoryResource *memory_resource = &monotonic_memory;
@@ -2405,6 +2409,7 @@ class CallProcedure : public memgraph::query::plan::LogicalOperator {
object->memory_limit_ = memory_limit_ ? memory_limit_->Clone(storage) : nullptr;
object->memory_scale_ = memory_scale_;
object->is_write_ = is_write_;
object->procedure_id_ = procedure_id_;
object->void_procedure_ = void_procedure_;
return object;
}

View File

@@ -27,6 +27,7 @@
#include "query/plan/operator.hpp"
#include "query/plan/preprocess.hpp"
#include "utils/algorithm.hpp"
namespace memgraph::query::plan {
@@ -145,8 +146,11 @@ class JoinRewriter final : public HierarchicalLogicalOperatorVisitor {
bool PreVisit(IndexedJoin &op) override {
prev_ops_.push_back(&op);
return true;
RewriteBranch(&op.main_branch_);
RewriteBranch(&op.sub_branch_);
return false;
}
bool PostVisit(IndexedJoin &) override {
prev_ops_.pop_back();
return true;
@@ -523,6 +527,13 @@ class JoinRewriter final : public HierarchicalLogicalOperatorVisitor {
auto rhs_property = rhs_lookup->property_;
filter_exprs_for_removal_.insert(filter.expression);
filters_.EraseFilter(filter);
if (utils::Contains(right_symbols, lhs_symbol) && utils::Contains(left_symbols, rhs_symbol)) {
// We need to duplicate this because expressions are shared between plans
join_condition = join_condition->Clone(ast_storage_);
std::swap(join_condition->expression1_, join_condition->expression2_);
}
return std::make_unique<HashJoin>(left_op, left_symbols, right_op, right_symbols, join_condition);
}

View File

@@ -12,6 +12,7 @@
/// @file
#pragma once
#include <cstdint>
#include <optional>
#include <variant>
@@ -185,6 +186,10 @@ class RuleBasedPlanner {
uint64_t merge_id = 0;
uint64_t subquery_id = 0;
// procedures need to start from 1
// due to swapping mechanism of procedure
// tracking
uint64_t procedure_id = 1;
for (const auto &clause : single_query_part.remaining_clauses) {
MG_ASSERT(!utils::IsSubtype(*clause, Match::kType), "Unexpected Match in remaining clauses");
@@ -224,7 +229,7 @@ class RuleBasedPlanner {
input_op = std::make_unique<plan::CallProcedure>(
std::move(input_op), call_proc->procedure_name_, call_proc->arguments_, call_proc->result_fields_,
result_symbols, call_proc->memory_limit_, call_proc->memory_scale_, call_proc->is_write_,
call_proc->void_procedure_);
procedure_id++, call_proc->void_procedure_);
} else if (auto *load_csv = utils::Downcast<query::LoadCsv>(clause)) {
const auto &row_sym = context.symbol_table->at(*load_csv->row_var_);
context.bound_symbols.insert(row_sym);

View File

@@ -169,7 +169,7 @@ Trigger::TriggerPlan::TriggerPlan(std::unique_ptr<LogicalPlan> logical_plan, std
std::shared_ptr<Trigger::TriggerPlan> Trigger::GetPlan(DbAccessor *db_accessor,
const query::AuthChecker *auth_checker) const {
std::lock_guard plan_guard{plan_lock_};
if (!parsed_statements_.is_cacheable || !trigger_plan_ || trigger_plan_->cached_plan.IsExpired()) {
if (!parsed_statements_.is_cacheable || !trigger_plan_) {
auto identifiers = GetPredefinedIdentifiers(event_type_);
AstStorage ast_storage;

View File

@@ -62,7 +62,7 @@ struct Trigger {
explicit TriggerPlan(std::unique_ptr<LogicalPlan> logical_plan, std::vector<IdentifierInfo> identifiers);
CachedPlan cached_plan;
PlanWrapper cached_plan;
std::vector<IdentifierInfo> identifiers;
};
std::shared_ptr<TriggerPlan> GetPlan(DbAccessor *db_accessor, const query::AuthChecker *auth_checker) const;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,96 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication/replication_server.hpp"
#include "rpc/messages.hpp"
#include "slk/serialization.hpp"
#include "slk/streams.hpp"
namespace memgraph::slk {
// Serialize code for FrequentHeartbeatRes
void Save(const memgraph::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self.success, builder);
}
void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(&self->success, reader);
}
// Serialize code for FrequentHeartbeatReq
void Save(const memgraph::replication::FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {}
void Load(memgraph::replication::FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {}
} // namespace memgraph::slk
namespace memgraph::replication {
namespace {
auto CreateServerContext(const memgraph::replication::ReplicationServerConfig &config) -> communication::ServerContext {
return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
config.ssl->verify_peer}
: communication::ServerContext{};
}
void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
FrequentHeartbeatReq req;
memgraph::slk::Load(&req, req_reader);
FrequentHeartbeatRes res{true};
memgraph::slk::Save(res, res_builder);
}
// NOTE: The replication server must have a single thread for processing
// because there is no need for more processing threads - each replica can
// have only a single main server. Also, the single-threaded guarantee
// simplifies the rest of the implementation.
constexpr auto kReplicationServerThreads = 1;
} // namespace
constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq",
nullptr};
constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes",
nullptr};
void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {
memgraph::slk::Save(self, builder);
}
void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {
memgraph::slk::Load(self, reader);
}
ReplicationServer::ReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
: rpc_server_context_{CreateServerContext(config)},
rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
kReplicationServerThreads} {
rpc_server_.Register<FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
spdlog::debug("Received FrequentHeartbeatRpc");
FrequentHeartbeatHandler(req_reader, res_builder);
});
}
ReplicationServer::~ReplicationServer() {
if (rpc_server_.IsRunning()) {
auto const &endpoint = rpc_server_.endpoint();
spdlog::trace("Closing replication server on {}:{}", endpoint.address, endpoint.port);
rpc_server_.Shutdown();
}
rpc_server_.AwaitShutdown();
}
bool ReplicationServer::Start() { return rpc_server_.Start(); }
} // namespace memgraph::replication

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,27 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <cstdint>
namespace memgraph::rpc {
using Version = uint64_t;
// versioning of RPC was/will be introduced in 2.13
// We start the versioning with a strange number, to radically reduce the
// probability of accidental match/conformance with pre 2.13 versions
constexpr auto v1 = Version{2023'10'30'0'2'13};
constexpr auto current_version = v1;
} // namespace memgraph::rpc

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -801,7 +801,7 @@ StorageInfo DiskStorage::GetBaseInfo(bool /* unused */) {
// NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions)
info.average_degree = 2.0 * static_cast<double>(info.edge_count) / info.vertex_count;
}
info.memory_usage = utils::GetMemoryUsage();
info.memory_res = utils::GetMemoryRES();
info.disk_usage = GetDiskSpaceUsage();
return info;
}
@@ -1551,7 +1551,7 @@ DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label,
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
const std::optional<uint64_t> desired_commit_timestamp, bool /*is_main*/) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@@ -1957,7 +1957,7 @@ UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstrain
return UniqueConstraints::DeletionStatus::SUCCESS;
}
Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool /*is_main*/) {
/// We acquire the transaction engine lock here because we access (and
/// modify) the transaction engine variables (`transaction_id` and
/// `timestamp`) below.
@@ -1982,7 +1982,8 @@ uint64_t DiskStorage::CommitTimestamp(const std::optional<uint64_t> desired_comm
return *desired_commit_timestamp;
}
std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level) {
std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level,
bool /*is_main*/) {
auto isolation_level = override_isolation_level.value_or(isolation_level_);
if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");
@@ -1990,7 +1991,8 @@ std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLe
return std::unique_ptr<DiskAccessor>(
new DiskAccessor{Storage::Accessor::shared_access, this, isolation_level, storage_mode_});
}
std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level) {
std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool /*is_main*/) {
auto isolation_level = override_isolation_level.value_or(isolation_level_);
if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) {
throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level.");

View File

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

View File

@@ -211,12 +211,10 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
memgraph::replication::ReplicationEpoch &epoch,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,
Indices *indices, Constraints *constraints, const Config &config,
uint64_t *wal_seq_num) {
ReplicationStorageState &repl_storage_state, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges, std::atomic<uint64_t> *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
const Config &config, uint64_t *wal_seq_num) {
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory,
wal_directory);
@@ -226,6 +224,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
return std::nullopt;
}
auto *const epoch_history = &repl_storage_state.history;
utils::Timer timer;
auto snapshot_files = GetSnapshotFiles(snapshot_directory);
@@ -264,7 +263,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
recovery_info = recovered_snapshot->recovery_info;
indices_constraints = std::move(recovered_snapshot->indices_constraints);
snapshot_timestamp = recovered_snapshot->snapshot_info.start_timestamp;
epoch.SetEpoch(std::move(recovered_snapshot->snapshot_info.epoch_id));
repl_storage_state.epoch_.SetEpoch(std::move(recovered_snapshot->snapshot_info.epoch_id));
if (!utils::DirExists(wal_directory)) {
const auto par_exec_info = config.durability.allow_parallel_index_creation
@@ -309,7 +308,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
// UUID used for durability is the UUID of the last WAL file.
// Same for the epoch id.
*uuid = std::move(wal_files.back().uuid);
epoch.SetEpoch(std::move(wal_files.back().epoch_id));
repl_storage_state.epoch_.SetEpoch(std::move(wal_files.back().epoch_id));
}
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
@@ -365,7 +364,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
}
previous_seq_num = wal_file.seq_num;
if (wal_file.epoch_id != epoch.id()) {
if (wal_file.epoch_id != repl_storage_state.epoch_.id()) {
// This way we skip WALs finalized only because of role change.
// We can also set the last timestamp to 0 if last loaded timestamp
// is nullopt as this can only happen if the WAL file with seq = 0
@@ -373,7 +372,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
if (last_loaded_timestamp) {
epoch_history->emplace_back(wal_file.epoch_id, *last_loaded_timestamp);
}
epoch.SetEpoch(std::move(wal_file.epoch_id));
repl_storage_state.epoch_.SetEpoch(std::move(wal_file.epoch_id));
}
try {
auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper,
@@ -398,7 +397,12 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
spdlog::info("All necessary WAL files are loaded successfully.");
}
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices);
const auto par_exec_info =
config.durability.allow_parallel_index_creation && !recovery_info.vertex_batches.empty()
? std::make_optional(std::make_pair(recovery_info.vertex_batches, config.durability.recovery_thread_count))
: std::nullopt;
RecoverIndicesAndConstraints(indices_constraints, indices, constraints, vertices, par_exec_info);
memgraph::metrics::Measure(memgraph::metrics::SnapshotRecoveryLatency_us,
std::chrono::duration_cast<std::chrono::microseconds>(timer.Elapsed()).count());

View File

@@ -19,6 +19,7 @@
#include <variant>
#include "replication/epoch.hpp"
#include "replication/state.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/constraints/constraints.hpp"
#include "storage/v2/durability/metadata.hpp"
@@ -26,6 +27,7 @@
#include "storage/v2/edge.hpp"
#include "storage/v2/indices/indices.hpp"
#include "storage/v2/name_id_mapper.hpp"
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/vertex.hpp"
#include "utils/skip_list.hpp"
@@ -110,11 +112,9 @@ void RecoverIndicesAndConstraints(
/// @throw std::bad_alloc
std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory,
const std::filesystem::path &wal_directory, std::string *uuid,
memgraph::replication::ReplicationEpoch &epoch,
std::deque<std::pair<std::string, uint64_t>> *epoch_history,
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges,
std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper,
Indices *indices, Constraints *constraints, const Config &config,
uint64_t *wal_seq_num);
ReplicationStorageState &repl_storage_state, utils::SkipList<Vertex> *vertices,
utils::SkipList<Edge> *edges, std::atomic<uint64_t> *edge_count,
NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints,
const Config &config, uint64_t *wal_seq_num);
} // namespace memgraph::storage::durability

File diff suppressed because it is too large Load Diff

View File

@@ -110,6 +110,9 @@ class EdgeAccessor final {
} // namespace memgraph::storage
static_assert(std::is_trivially_copyable<memgraph::storage::EdgeAccessor>::value,
"storage::EdgeAccessor must be trivially copyable!");
namespace std {
template <>
struct hash<memgraph::storage::EdgeAccessor> {

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,11 +16,11 @@
#include "storage/v2/metadata_delta.hpp"
/// REPLICATION ///
#include "dbms/inmemory/replication_handlers.hpp"
#include "storage/v2/inmemory/replication/replication_client.hpp"
#include "storage/v2/inmemory/replication/replication_server.hpp"
#include "storage/v2/inmemory/unique_constraints.hpp"
#include "storage/v2/replication/replication_handler.hpp"
#include "utils/resource_lock.hpp"
#include "utils/stat.hpp"
namespace memgraph::storage {
@@ -60,12 +60,10 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
"process!",
config_.durability.storage_directory);
}
auto &repl_state = repl_state_;
if (config_.durability.recover_on_startup) {
auto &epoch = repl_state.GetEpoch();
auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, epoch,
&repl_storage_state_.history, &vertices_, &edges_, &edge_count_,
name_id_mapper_.get(), &indices_, &constraints_, config_, &wal_seq_num_);
auto info =
durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, repl_storage_state_, &vertices_, &edges_,
&edge_count_, name_id_mapper_.get(), &indices_, &constraints_, config_, &wal_seq_num_);
if (info) {
vertex_id_ = info->next_vertex_id;
edge_id_ = info->next_edge_id;
@@ -103,51 +101,14 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode)
"those files into a .backup directory inside the storage directory.");
}
}
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] {
auto const &repl_state = repl_state_;
if (auto maybe_error = this->CreateSnapshot(repl_state, {true}); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(
utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/durability"));
break;
case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
});
}
if (config_.gc.type == Config::Gc::Type::PERIODIC) {
gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->CollectGarbage<false>(); });
}
if (timestamp_ == kTimestampInitialId) {
commit_log_.emplace();
} else {
commit_log_.emplace(timestamp_);
}
if (config_.durability.restore_replication_state_on_startup) {
spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash.");
ReplicationHandler{repl_state, *this}.RestoreReplication();
} else {
spdlog::warn(
"Replication configuration will NOT be stored. When the server restarts, replication state will be "
"forgotten.");
}
if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && repl_state.IsMain()) {
spdlog::warn(
"The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider "
"enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because "
"without write-ahead logs this instance is not replicating any data.");
}
}
InMemoryStorage::InMemoryStorage(Config config) : InMemoryStorage(config, StorageMode::IN_MEMORY_TRANSACTIONAL) {}
@@ -167,29 +128,15 @@ InMemoryStorage::~InMemoryStorage() {
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Stop();
}
if (config_.durability.snapshot_on_exit) {
auto const &repl_state = repl_state_;
if (auto maybe_error = this->CreateSnapshot(repl_state, {false}); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
case CreateSnapshotError::DisabledForAnalyticsPeriodicCommit:
spdlog::warn(utils::MessageWithLink("Periodic snapshots are disabled for analytical mode.",
"https://memgr.ph/replication"));
break;
case storage::InMemoryStorage::CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
if (config_.durability.snapshot_on_exit && this->create_snapshot_handler) {
create_snapshot_handler();
}
committed_transactions_.WithLock([](auto &transactions) { transactions.clear(); });
}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
: Accessor(tag, storage, isolation_level, storage_mode), config_(storage->config_.items) {}
StorageMode storage_mode, bool is_main)
: Accessor(tag, storage, isolation_level, storage_mode, is_main), config_(storage->config_.items) {}
InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept
: Accessor(std::move(other)), config_(other.config_) {}
@@ -711,7 +658,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces
// NOLINTNEXTLINE(google-default-arguments)
utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
const std::optional<uint64_t> desired_commit_timestamp, bool is_main) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
@@ -719,7 +666,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
auto *mem_storage = static_cast<InMemoryStorage *>(storage_);
auto const &replState = mem_storage->repl_state_;
// TODO: duplicated transaction finalisation in md_deltas and deltas processing cases
if (!transaction_.md_deltas.empty()) {
// This is usually done by the MVCC, but it does not handle the metadata deltas
transaction_.EnsureCommitTimestampExists();
@@ -739,14 +686,14 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
// modifications before they are written to disk.
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataDefinition(transaction_, *commit_timestamp_); // protected by engine_guard
// TODO: release lock, and update all deltas to have a local copy of the commit timestamp
transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); // protected by engine_guard
// Replica can only update the last commit timestamp with
// the commits received from main.
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_); // protected by engine_guard
}
@@ -820,7 +767,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
// modifications before they are written to disk.
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
could_replicate_all_sync_replicas =
mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_); // protected by engine_guard
}
@@ -831,7 +778,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
std::memory_order_release); // protected by engine_guard
// Replica can only update the last commit timestamp with
// the commits received from main.
if (replState.IsMain() || desired_commit_timestamp.has_value()) {
if (is_main || desired_commit_timestamp.has_value()) {
// Update the last commit timestamp
mem_storage->repl_storage_state_.last_commit_timestamp_.store(
*commit_timestamp_); // protected by engine_guard
@@ -1195,7 +1142,7 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices(
mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_));
}
Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) {
// We acquire the transaction engine lock here because we access (and
// modify) the transaction engine variables (`transaction_id` and
// `timestamp`) below.
@@ -1210,16 +1157,34 @@ Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, S
// of any query on replica to the last commited transaction
// which is timestamp_ as only commit of transaction with writes
// can change the value of it.
auto const &replState = repl_state_;
if (replState.IsReplica()) {
start_timestamp = timestamp_;
} else {
if (is_main) {
start_timestamp = timestamp_++;
} else {
start_timestamp = timestamp_;
}
}
return {transaction_id, start_timestamp, isolation_level, storage_mode, false};
}
void InMemoryStorage::SetStorageMode(StorageMode new_storage_mode) {
std::unique_lock main_guard{main_lock_};
MG_ASSERT(
(storage_mode_ == StorageMode::IN_MEMORY_ANALYTICAL || storage_mode_ == StorageMode::IN_MEMORY_TRANSACTIONAL) &&
(new_storage_mode == StorageMode::IN_MEMORY_ANALYTICAL ||
new_storage_mode == StorageMode::IN_MEMORY_TRANSACTIONAL));
if (storage_mode_ != new_storage_mode) {
if (new_storage_mode == StorageMode::IN_MEMORY_ANALYTICAL) {
snapshot_runner_.Stop();
} else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval,
[this]() { this->create_snapshot_handler(); });
}
storage_mode_ = new_storage_mode;
FreeMemory(std::move(main_guard));
}
}
template <bool force>
void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_guard) {
// NOTE: You do not need to consider cleanup of deleted object that occurred in
@@ -1557,7 +1522,7 @@ StorageInfo InMemoryStorage::GetBaseInfo(bool force_directory) {
// NOLINTNEXTLINE(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions)
info.average_degree = 2.0 * static_cast<double>(info.edge_count) / info.vertex_count;
}
info.memory_usage = utils::GetMemoryUsage();
info.memory_res = utils::GetMemoryRES();
// Special case for the default database
auto update_path = [&](const std::filesystem::path &dir) {
if (!force_directory && std::filesystem::is_directory(dir) && dir.has_filename()) {
@@ -1629,8 +1594,7 @@ void InMemoryStorage::FinalizeWalFile() {
}
bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
if (!InitializeWalFile(repl_storage_state_.epoch_)) {
return true;
}
// Traverse deltas and append them to the WAL file.
@@ -1800,8 +1764,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
}
bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp) {
auto &replState = repl_state_;
if (!InitializeWalFile(replState.GetEpoch())) {
if (!InitializeWalFile(repl_storage_state_.epoch_)) {
return true;
}
@@ -1910,13 +1873,8 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera
return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp);
}
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot(
memgraph::replication::ReplicationState const &replicationState, std::optional<bool> is_periodic) {
if (replicationState.IsReplica()) {
return CreateSnapshotError::DisabledForReplica;
}
auto const &epoch = replicationState.GetEpoch();
utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot() {
auto const &epoch = repl_storage_state_.epoch_;
auto snapshot_creator = [this, &epoch]() {
utils::Timer timer;
auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_);
@@ -1943,9 +1901,6 @@ utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::Create
} else {
std::unique_lock main_guard{main_lock_};
if (storage_mode_ == memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL) {
if (is_periodic && *is_periodic) {
return CreateSnapshotError::DisabledForAnalyticsPeriodicCommit;
}
snapshot_creator();
return {};
}
@@ -1976,13 +1931,13 @@ uint64_t InMemoryStorage::CommitTimestamp(const std::optional<uint64_t> desired_
return *desired_commit_timestamp;
}
void InMemoryStorage::PrepareForNewEpoch(std::string prev_epoch) {
void InMemoryStorage::PrepareForNewEpoch() {
std::unique_lock engine_guard{engine_lock_};
if (wal_file_) {
wal_file_->FinalizeWal();
wal_file_.reset();
}
repl_storage_state_.AddEpochToHistory(std::move(prev_epoch));
repl_storage_state_.TrackLatestHistory();
}
utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::IsPathLocked() {
@@ -2010,26 +1965,45 @@ utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath()
return true;
}
auto InMemoryStorage::CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
auto InMemoryStorage::CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *current_epoch)
-> std::unique_ptr<ReplicationClient> {
auto &replState = this->repl_state_;
return std::make_unique<InMemoryReplicationClient>(this, config, &replState.GetEpoch());
return std::make_unique<InMemoryReplicationClient>(this, config, current_epoch);
}
std::unique_ptr<ReplicationServer> InMemoryStorage::CreateReplicationServer(
const memgraph::replication::ReplicationServerConfig &config) {
auto &replState = this->repl_state_;
return std::make_unique<InMemoryReplicationServer>(this, config, &replState.GetEpoch());
std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(std::optional<IsolationLevel> override_isolation_level,
bool is_main) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::shared_access, this,
override_isolation_level.value_or(isolation_level_),
storage_mode_, is_main});
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::unique_access, this,
override_isolation_level.value_or(isolation_level_),
storage_mode_, is_main});
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(std::optional<IsolationLevel> override_isolation_level) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{
Storage::Accessor::shared_access, this, override_isolation_level.value_or(isolation_level_), storage_mode_});
}
std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(
std::optional<IsolationLevel> override_isolation_level) {
return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{
Storage::Accessor::unique_access, this, override_isolation_level.value_or(isolation_level_), storage_mode_});
void InMemoryStorage::CreateSnapshotHandler(
std::function<utils::BasicResult<InMemoryStorage::CreateSnapshotError>()> cb) {
create_snapshot_handler = [cb]() {
if (auto maybe_error = cb(); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
case CreateSnapshotError::ReachedMaxNumTries:
spdlog::warn("Failed to create snapshot. Reached max number of tries. Please contact support");
break;
}
}
};
// Run the snapshot thread (if enabled)
if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) {
snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval,
[this]() { this->create_snapshot_handler(); });
}
}
IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const {
auto *in_memory = static_cast<InMemoryStorage *>(storage_);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,61 +0,0 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "replication_server.hpp"
#include "io/network/endpoint.hpp"
#include "replication/config.hpp"
#include "rpc.hpp"
namespace memgraph::storage {
namespace {
auto CreateServerContext(const memgraph::replication::ReplicationServerConfig &config) -> communication::ServerContext {
return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file,
config.ssl->verify_peer}
: communication::ServerContext{};
}
// NOTE: The replication server must have a single thread for processing
// because there is no need for more processing threads - each replica can
// have only a single main server. Also, the single-threaded guarantee
// simplifies the rest of the implementation.
constexpr auto kReplictionServerThreads = 1;
} // namespace
ReplicationServer::ReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
: rpc_server_context_{CreateServerContext(config)},
rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_,
kReplictionServerThreads} {
rpc_server_.Register<replication::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) {
spdlog::debug("Received FrequentHeartbeatRpc");
FrequentHeartbeatHandler(req_reader, res_builder);
});
}
ReplicationServer::~ReplicationServer() {
if (rpc_server_.IsRunning()) {
auto const &endpoint = rpc_server_.endpoint();
spdlog::trace("Closing replication server on {}:{}", endpoint.address, endpoint.port);
rpc_server_.Shutdown();
}
rpc_server_.AwaitShutdown();
}
bool ReplicationServer::Start() { return rpc_server_.Start(); }
void ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) {
replication::FrequentHeartbeatReq req;
slk::Load(&req, req_reader);
replication::FrequentHeartbeatRes res{true};
slk::Save(res, res_builder);
}
} // namespace memgraph::storage

View File

@@ -11,8 +11,8 @@
#include "storage/v2/replication/replication_storage_state.hpp"
#include "replication/replication_server.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
namespace memgraph::storage {
@@ -91,17 +91,16 @@ std::vector<ReplicaInfo> ReplicationStorageState::ReplicasInfo() const {
}
void ReplicationStorageState::Reset() {
replication_server_.reset();
replication_clients_.WithLock([](auto &clients) { clients.clear(); });
}
void ReplicationStorageState::AddEpochToHistory(std::string prev_epoch) {
void ReplicationStorageState::TrackLatestHistory() {
constexpr uint16_t kEpochHistoryRetention = 1000;
// Generate new epoch id and save the last one to the history.
if (history.size() == kEpochHistoryRetention) {
history.pop_front();
}
history.emplace_back(std::move(prev_epoch), last_commit_timestamp_);
history.emplace_back(epoch_.id(), last_commit_timestamp_);
}
void ReplicationStorageState::AddEpochToHistoryForce(std::string prev_epoch) {

View File

@@ -27,11 +27,12 @@
#include "storage/v2/replication/global.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::storage {
class Storage;
class ReplicationServer;
class ReplicationClient;
struct ReplicationStorageState {
@@ -49,7 +50,7 @@ struct ReplicationStorageState {
auto ReplicasInfo() const -> std::vector<ReplicaInfo>;
// History
void AddEpochToHistory(std::string prev_epoch);
void TrackLatestHistory();
void AddEpochToHistoryForce(std::string prev_epoch);
void Reset();
@@ -76,10 +77,9 @@ struct ReplicationStorageState {
using ReplicationClientPtr = std::unique_ptr<ReplicationClient>;
using ReplicationClientList = utils::Synchronized<std::vector<ReplicationClientPtr>, utils::RWSpinLock>;
// NOTE: Server is not in MAIN it is in REPLICA
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
ReplicationClientList replication_clients_;
memgraph::replication::ReplicationEpoch epoch_;
};
} // namespace memgraph::storage

View File

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

View File

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

View File

@@ -31,14 +31,8 @@ namespace memgraph::storage {
class InMemoryStorage;
auto ReplicationStateHelper(Config const &config) -> std::optional<std::filesystem::path> {
if (!config.durability.restore_replication_state_on_startup) return std::nullopt;
return {config.durability.storage_directory};
}
Storage::Storage(Config config, StorageMode storage_mode)
: repl_state_(ReplicationStateHelper(config)),
name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> {
: name_id_mapper_(std::invoke([config, storage_mode]() -> std::unique_ptr<NameIdMapper> {
if (storage_mode == StorageMode::ON_DISK_TRANSACTIONAL) {
return std::make_unique<DiskNameIdMapper>(config.disk.name_id_mapper_directory,
config.disk.id_name_mapper_directory);
@@ -55,26 +49,26 @@ Storage::Storage(Config config, StorageMode storage_mode)
}
Storage::Accessor::Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
StorageMode storage_mode, bool is_main)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_),
unique_guard_(storage_->main_lock_, std::defer_lock),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}
Storage::Accessor::Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level,
StorageMode storage_mode)
StorageMode storage_mode, bool is_main)
: storage_(storage),
// The lock must be acquired before creating the transaction object to
// prevent freshly created transactions from dangling in an active state
// during exclusive operations.
storage_guard_(storage_->main_lock_, std::defer_lock),
unique_guard_(storage_->main_lock_),
transaction_(storage->CreateTransaction(isolation_level, storage_mode)),
transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)),
is_transaction_active_(true),
creation_storage_mode_(storage_mode) {}
@@ -91,18 +85,6 @@ Storage::Accessor::Accessor(Accessor &&other) noexcept
other.commit_timestamp_.reset();
}
/// Main lock is taken by the caller.
void Storage::SetStorageMode(StorageMode storage_mode) {
std::unique_lock main_guard{main_lock_};
MG_ASSERT(
(storage_mode_ == StorageMode::IN_MEMORY_ANALYTICAL || storage_mode_ == StorageMode::IN_MEMORY_TRANSACTIONAL) &&
(storage_mode == StorageMode::IN_MEMORY_ANALYTICAL || storage_mode == StorageMode::IN_MEMORY_TRANSACTIONAL));
if (storage_mode_ != storage_mode) {
storage_mode_ = storage_mode;
FreeMemory(std::move(main_guard));
}
}
StorageMode Storage::GetStorageMode() const { return storage_mode_; }
IsolationLevel Storage::GetIsolationLevel() const noexcept { return isolation_level_; }

View File

@@ -20,6 +20,7 @@
#include "kvstore/kvstore.hpp"
#include "query/exceptions.hpp"
#include "replication/config.hpp"
#include "replication/replication_server.hpp"
#include "storage/v2/all_vertices_iterable.hpp"
#include "storage/v2/commit_log.hpp"
#include "storage/v2/config.hpp"
@@ -30,7 +31,6 @@
#include "storage/v2/mvcc.hpp"
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/replication/replication_storage_state.hpp"
#include "storage/v2/storage_error.hpp"
#include "storage/v2/storage_mode.hpp"
@@ -69,7 +69,7 @@ struct StorageInfo {
uint64_t vertex_count;
uint64_t edge_count;
double average_degree;
uint64_t memory_usage;
uint64_t memory_res;
uint64_t disk_usage;
uint64_t label_indices;
uint64_t label_property_indices;
@@ -86,7 +86,7 @@ static inline nlohmann::json ToJson(const StorageInfo &info) {
res["edges"] = info.edge_count;
res["vertices"] = info.vertex_count;
res["memory"] = info.memory_usage;
res["memory"] = info.memory_res;
res["disk"] = info.disk_usage;
res["label_indices"] = info.label_indices;
res["label_prop_indices"] = info.label_property_indices;
@@ -130,8 +130,10 @@ class Storage {
static constexpr struct UniqueAccess {
} unique_access;
Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode);
Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
bool is_main = true);
Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode,
bool is_main = true);
Accessor(const Accessor &) = delete;
Accessor &operator=(const Accessor &) = delete;
Accessor &operator=(Accessor &&other) = delete;
@@ -211,7 +213,7 @@ class Storage {
// NOLINTNEXTLINE(google-default-arguments)
virtual utils::BasicResult<StorageManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {}) = 0;
std::optional<uint64_t> desired_commit_timestamp = {}, bool is_main = true) = 0;
virtual void Abort() = 0;
@@ -299,19 +301,26 @@ class Storage {
return EdgeTypeId::FromUint(name_id_mapper_->NameToId(name));
}
void SetStorageMode(StorageMode storage_mode);
StorageMode GetStorageMode() const;
virtual void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) = 0;
void FreeMemory() { FreeMemory({}); }
virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) = 0;
std::unique_ptr<Accessor> Access() { return Access(std::optional<IsolationLevel>{}); }
virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level, bool is_main) = 0;
std::unique_ptr<Accessor> Access(bool is_main = true) { return Access(std::optional<IsolationLevel>{}, is_main); }
std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) {
return Access(std::move(override_isolation_level), true);
}
virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) = 0;
std::unique_ptr<Accessor> UniqueAccess() { return UniqueAccess(std::optional<IsolationLevel>{}); }
virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level,
bool is_main) = 0;
std::unique_ptr<Accessor> UniqueAccess(bool is_main = true) {
return UniqueAccess(std::optional<IsolationLevel>{}, is_main);
}
std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) {
return UniqueAccess(std::move(override_isolation_level), true);
}
enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode };
@@ -338,23 +347,24 @@ class Storage {
return GetInfo(force_dir);
}
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) = 0;
Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) {
return CreateTransaction(isolation_level, storage_mode, true);
}
virtual void PrepareForNewEpoch(std::string prev_epoch) = 0;
virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) = 0;
virtual auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config)
virtual void PrepareForNewEpoch() = 0;
virtual auto CreateReplicationClient(const memgraph::replication::ReplicationClientConfig &config,
const memgraph::replication::ReplicationEpoch *current_epoch)
-> std::unique_ptr<ReplicationClient> = 0;
virtual auto CreateReplicationServer(const memgraph::replication::ReplicationServerConfig &config)
-> std::unique_ptr<ReplicationServer> = 0;
auto ReplicasInfo() const { return repl_storage_state_.ReplicasInfo(); }
auto GetReplicaState(std::string_view name) const -> std::optional<replication::ReplicaState> {
return repl_storage_state_.GetReplicaState(name);
}
// TODO: make non-public
memgraph::replication::ReplicationState repl_state_;
ReplicationStorageState repl_storage_state_;
// Main storage lock.

View File

@@ -127,6 +127,9 @@ class VertexAccessor final {
bool for_deleted_{false};
};
static_assert(std::is_trivially_copyable<memgraph::storage::VertexAccessor>::value,
"storage::VertexAccessor must be trivially copyable!");
struct EdgesVertexAccessorResult {
std::vector<EdgeAccessor> edges;
int64_t expanded_count;

View File

@@ -78,8 +78,12 @@ const nlohmann::json GetResourceUsage(std::filesystem::path root_directory) {
}
auto cpu_total = GetCpuUsage(pid);
cpu["usage"] = cpu_total.second;
return {{"cpu", cpu}, {"memory", utils::GetMemoryUsage()}, {"disk", utils::GetDirDiskUsage(root_directory)}};
const auto vm_max_map_count = utils::GetVmMaxMapCount();
return {{"cpu", cpu},
{"memory", utils::GetMemoryRES()},
{"disk", utils::GetDirDiskUsage(root_directory)},
{"vm_max_map_count",
vm_max_map_count.has_value() ? *vm_max_map_count : memgraph::utils::VM_MAX_MAP_COUNT_DEFAULT}};
}
} // namespace memgraph::telemetry

View File

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

View File

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

View File

@@ -14,7 +14,8 @@ set(utils_src_files
tsc.cpp
system_info.cpp
uuid.cpp
build_info.cpp)
build_info.cpp
query_memory_tracker.cpp)
find_package(Boost REQUIRED)
find_package(fmt REQUIRED)

View File

@@ -519,7 +519,7 @@ void OutputFile::FlushBufferInternal() {
auto *buffer = buffer_;
auto buffer_position = buffer_position_.load();
while (buffer_position > 0) {
auto written = write(fd_, buffer, buffer_position_);
auto written = write(fd_, buffer, buffer_position);
if (written == -1 && errno == EINTR) {
continue;
}

View File

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

View File

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

68
src/utils/lru_cache.hpp Normal file
View File

@@ -0,0 +1,68 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <list>
#include <optional>
#include <unordered_map>
#include <utility>
namespace memgraph::utils {
/// A simple LRU cache implementation.
/// It is not thread-safe.
template <class TKey, class TVal>
class LRUCache {
public:
LRUCache(int cache_size_) : cache_size(cache_size_){};
void put(const TKey &key, const TVal &val) {
auto it = item_map.find(key);
if (it != item_map.end()) {
item_list.erase(it->second);
item_map.erase(it);
}
item_list.push_front(std::make_pair(key, val));
item_map.insert(std::make_pair(key, item_list.begin()));
try_clean();
};
std::optional<TVal> get(const TKey &key) {
if (!exists(key)) {
return std::nullopt;
}
auto it = item_map.find(key);
item_list.splice(item_list.begin(), item_list, it->second);
return it->second->second;
}
void reset() {
item_list.clear();
item_map.clear();
};
std::size_t size() { return item_map.size(); };
private:
void try_clean() {
while (item_map.size() > cache_size) {
auto last_it_elem_it = item_list.end();
last_it_elem_it--;
item_map.erase(last_it_elem_it->first);
item_list.pop_back();
}
};
bool exists(const TKey &key) { return (item_map.count(key) > 0); };
std::list<std::pair<TKey, TVal>> item_list;
std::unordered_map<TKey, decltype(item_list.begin())> item_map;
std::size_t cache_size;
};
} // namespace memgraph::utils

View File

@@ -539,9 +539,9 @@ class SynchronizedPoolResource final : public MemoryResource {
bool DoIsEqual(const MemoryResource &other) const noexcept override { return this == &other; }
};
class LimitedMemoryResource final : public utils::MemoryResource {
class MemoryTrackingResource final : public utils::MemoryResource {
public:
explicit LimitedMemoryResource(utils::MemoryResource *memory, size_t max_allocated_bytes)
explicit MemoryTrackingResource(utils::MemoryResource *memory, size_t max_allocated_bytes)
: memory_(memory), max_allocated_bytes_(max_allocated_bytes) {}
size_t GetAllocatedBytes() const noexcept { return max_allocated_bytes_ - available_bytes_; }
@@ -552,13 +552,11 @@ class LimitedMemoryResource final : public utils::MemoryResource {
size_t available_bytes_{max_allocated_bytes_};
void *DoAllocate(size_t bytes, size_t alignment) override {
if (bytes > available_bytes_) throw utils::BadAlloc("Memory allocation limit exceeded!");
available_bytes_ -= bytes;
return memory_->Allocate(bytes, alignment);
}
void DoDeallocate(void *p, size_t bytes, size_t alignment) override {
MG_ASSERT(available_bytes_ + bytes > available_bytes_, "Failed deallocation");
available_bytes_ += bytes;
return memory_->Deallocate(p, bytes, alignment);
}

View File

@@ -111,7 +111,7 @@ void MemoryTracker::Alloc(const int64_t size) {
const auto current_hard_limit = hard_limit_.load(std::memory_order_relaxed);
if (UNLIKELY(current_hard_limit && will_be > current_hard_limit && MemoryTrackerCanThrow())) {
if (current_hard_limit && will_be > current_hard_limit && MemoryTrackerCanThrow()) [[unlikely]] {
MemoryTracker::OutOfMemoryExceptionBlocker exception_blocker;
amount_.fetch_sub(size, std::memory_order_relaxed);
@@ -121,7 +121,6 @@ void MemoryTracker::Alloc(const int64_t size) {
"use to {}, while the maximum allowed size for allocation is set to {}.",
GetReadableSize(size), GetReadableSize(will_be), GetReadableSize(current_hard_limit)));
}
UpdatePeak(will_be);
}

View File

@@ -25,17 +25,6 @@ class OutOfMemoryException : public utils::BasicException {
};
class MemoryTracker final {
private:
std::atomic<int64_t> amount_{0};
std::atomic<int64_t> peak_{0};
std::atomic<int64_t> hard_limit_{0};
// Maximum possible value of a hard limit. If it's set to 0, no upper bound on the hard limit is set.
int64_t maximum_hard_limit_{0};
void UpdatePeak(int64_t will_be);
static void LogMemoryUsage(int64_t current);
public:
void LogPeakMemoryUsage() const;
@@ -73,6 +62,13 @@ class MemoryTracker final {
void ResetTrackings();
bool IsProcedureTracked();
void SetProcTrackingLimit(size_t limit);
void StartProcTracking();
void StopProcTracking();
// By creating an object of this class, every allocation in its scope that goes over
// the set hard limit produces an OutOfMemoryException.
class OutOfMemoryExceptionEnabler final {
@@ -109,6 +105,17 @@ class MemoryTracker final {
private:
static thread_local uint64_t counter_;
};
private:
std::atomic<int64_t> amount_{0};
std::atomic<int64_t> peak_{0};
std::atomic<int64_t> hard_limit_{0};
// Maximum possible value of a hard limit. If it's set to 0, no upper bound on the hard limit is set.
int64_t maximum_hard_limit_{0};
void UpdatePeak(int64_t will_be);
static void LogMemoryUsage(int64_t current);
};
// Global memory tracker which tracks every allocation in the application.

View File

@@ -0,0 +1,78 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "utils/query_memory_tracker.hpp"
#include <atomic>
#include <optional>
#include "memory/query_memory_control.hpp"
#include "utils/memory_tracker.hpp"
namespace memgraph::utils {
void QueryMemoryTracker::TrackAlloc(size_t size) {
if (query_tracker_.has_value()) [[likely]] {
query_tracker_->Alloc(static_cast<int64_t>(size));
}
auto *proc_tracker = GetActiveProc();
if (proc_tracker == nullptr) {
return;
}
proc_tracker->Alloc(static_cast<int64_t>(size));
}
void QueryMemoryTracker::TrackFree(size_t size) {
if (query_tracker_.has_value()) [[likely]] {
query_tracker_->Free(static_cast<int64_t>(size));
}
auto *proc_tracker = GetActiveProc();
if (proc_tracker == nullptr) {
return;
}
proc_tracker->Free(static_cast<int64_t>(size));
}
void QueryMemoryTracker::SetQueryLimit(size_t size) {
if (size == memgraph::memory::UNLIMITED_MEMORY) {
return;
}
InitializeQueryTracker();
query_tracker_->SetMaximumHardLimit(static_cast<int64_t>(size));
query_tracker_->SetHardLimit(static_cast<int64_t>(size));
}
memgraph::utils::MemoryTracker *QueryMemoryTracker::GetActiveProc() {
if (active_proc_id == NO_PROCEDURE) [[likely]] {
return nullptr;
}
return &proc_memory_trackers_[active_proc_id];
}
void QueryMemoryTracker::SetActiveProc(int64_t new_active_proc) { active_proc_id = new_active_proc; }
void QueryMemoryTracker::StopProcTracking() { active_proc_id = QueryMemoryTracker::NO_PROCEDURE; }
void QueryMemoryTracker::TryCreateProcTracker(int64_t procedure_id, size_t limit) {
if (proc_memory_trackers_.contains(procedure_id)) {
return;
}
auto [it, inserted] = proc_memory_trackers_.emplace(procedure_id, utils::MemoryTracker{});
it->second.SetMaximumHardLimit(static_cast<int64_t>(limit));
it->second.SetHardLimit(static_cast<int64_t>(limit));
}
void QueryMemoryTracker::InitializeQueryTracker() { query_tracker_.emplace(MemoryTracker{}); }
} // namespace memgraph::utils

Some files were not shown because too many files have changed in this diff Show More