Cleanup durability config, docs, CHANGELOG

Reviewers: teon.banek, buda, mislav.bradac, dgleich

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D992
This commit is contained in:
florijan 2017-11-20 11:58:05 +01:00
parent df4cbdc5b2
commit 8bbf1af525
27 changed files with 336 additions and 306 deletions

View File

@ -5,9 +5,11 @@
### Breaking Changes ### Breaking Changes
* Snapshot format changed (not backward compatible). * Snapshot format changed (not backward compatible).
* Snapshot configuration flags changed, general durability flags added.
### Major Features and Improvements ### Major Features and Improvements
* Write-ahead log added.
* `nodes` and `relationships` functions added. * `nodes` and `relationships` functions added.
### Bug Fixes and Other Changes ### Bug Fixes and Other Changes

View File

@ -192,6 +192,7 @@ set(memgraph_src_files
${src_dir}/communication/reactor/reactor_distributed.cpp ${src_dir}/communication/reactor/reactor_distributed.cpp
${src_dir}/data_structures/concurrent/skiplist_gc.cpp ${src_dir}/data_structures/concurrent/skiplist_gc.cpp
${src_dir}/database/graph_db.cpp ${src_dir}/database/graph_db.cpp
${src_dir}/database/graph_db_config.cpp
${src_dir}/database/graph_db_accessor.cpp ${src_dir}/database/graph_db_accessor.cpp
${src_dir}/durability/recovery.cpp ${src_dir}/durability/recovery.cpp
${src_dir}/durability/snapshooter.cpp ${src_dir}/durability/snapshooter.cpp

View File

@ -3,34 +3,17 @@
# NOTE: all paths are relative to the run folder # NOTE: all paths are relative to the run folder
# (where the executable is run) # (where the executable is run)
# directory to the folder with snapshots # no durability
--snapshot-directory=snapshots --durability-enabled=false
--snapshot-on-exit=false
--db-recover-on-startup=false
# cleaning cycle interval # no GC
# if set to -1 the GC will not run
--gc-cycle-sec=-1 --gc-cycle-sec=-1
# skiplist gc cycle interval
# if set to 0 the GC will not run
--skiplist_gc_interval=0 --skiplist_gc_interval=0
# snapshot cycle interval # no query execution time limit
# if set to -1 the snapshooter will not run
--snapshot-cycle-sec=-1
# snapshot cycle interval
# if set to -1 the snapshooter will not run
--query_execution_time_sec=-1 --query_execution_time_sec=-1
# create snapshot disabled on db exit
--snapshot-on-exit=false
# max number of snapshots which will be kept on the disk at some point
# if set to -1 the max number of snapshots is unlimited
--snapshot-max-retained=-1
# database recovering is disabled by default
--snapshot-recover-on-startup=false
# number of workers # number of workers
--num-workers=1 --num-workers=1

View File

@ -48,29 +48,33 @@
# downside of caching by evicting old plans after the given time. # downside of caching by evicting old plans after the given time.
#--query-plan-cache-ttl=60 #--query-plan-cache-ttl=60
## Snapshot ## Durability
# #
# Snapshots store the database state to persistent storage. Snapshots are # Memgraph can store database state to persistent storage. Two mechanisms
# taken in intervals and can be used to restore the database to a previous # are used: snapshots store the total current database state while write-ahead
# state. # logs store small changes incrementally. They are used in tandem to provide
# fast and storage-efficient persistence. Some aspects of snapshot taking
# are configurable, while write-ahead logging is pre-configured for optimal
# performance.
--durability-enabled=true
# Interval of taking snapshots, in seconds. If set to -1, snapshot feature # Path to the directory where snapshots and write-ahead log files will be stored.
--durability-directory=/var/lib/memgraph/durability
# Recover the database on startup.
--db-recover-on-startup=true
# Interval of taking snapshots, in seconds. If set to -1, the snapshot feature
# will be turned off. # will be turned off.
--snapshot-cycle-sec=300 --snapshot-cycle-sec=300
# Create a snapshot when closing Memgraph. # Create a snapshot when closing Memgraph.
--snapshot-on-exit=true --snapshot-on-exit=true
# Path to the directory where snapshots will be stored.
--snapshot-directory=/var/lib/memgraph/snapshots
# Maximum number of kept snapshots. Old snapshots will be deleted to make room # Maximum number of kept snapshots. Old snapshots will be deleted to make room
# for new ones. If set to -1, the number of kept snapshots is unlimited. # for new ones. If set to -1, the number of kept snapshots is unlimited.
--snapshot-max-retained=3 --snapshot-max-retained=3
# Recover the database from the latest snapshot on startup.
--snapshot-recover-on-startup=true
## Logging ## Logging
# Path to where the log should be stored. # Path to where the log should be stored.

View File

@ -3,26 +3,13 @@
# NOTE: all paths are relative to the run folder # NOTE: all paths are relative to the run folder
# (where the executable is run) # (where the executable is run)
# directory to the folder with snapshots # enable durability
--snapshot-directory=snapshots --durability-enabled=true
# cleaning cycle interval
# if set to -1 the GC will not run
--gc-cycle-sec=30
# snapshot cycle interval
# if set to -1 the snapshooter will not run
--snapshot-cycle-sec=600 --snapshot-cycle-sec=600
# create snapshot enabled on db exit
--snapshot-on-exit=true --snapshot-on-exit=true
# max number of snapshots which will be kept on the disk at some point
# if set to -1 the max number of snapshots is unlimited
--snapshot-max-retained=1 --snapshot-max-retained=1
# database recovering is disabled by default --db-recover-on-startup=false
--snapshot-recover-on-startup=false
# increase query timeout (10 min) # increase query timeout (10 min)
--query-execution-time-sec=600 --query-execution-time-sec=600

View File

@ -3,27 +3,7 @@
# NOTE: all paths are relative to the run folder # NOTE: all paths are relative to the run folder
# (where the executable is run) # (where the executable is run)
# directory to the folder with snapshots # no durability
--snapshot-directory=snapshots --durability-enabled=false
# cleaning cycle interval
# if set to -1 the GC will not run
--gc-cycle-sec=30
# snapshot cycle interval
# if set to -1 the snapshooter will not run
--snapshot-cycle-sec=-1
# create snapshot disabled on db exit
--snapshot-on-exit=false --snapshot-on-exit=false
--db-recover-on-startup=false
# disable WAL
--wal-flush-interval-millis=-1
# max number of snapshots which will be kept on the disk at some point
# if set to -1 the max number of snapshots is unlimited
--snapshot-max-retained=-1
# database recovering is disabled by default
--snapshot-recover-on-startup=false

View File

@ -110,11 +110,12 @@ parameters:
--query-execution-time-sec | integer | 30 | Maximum allowed query execution time, in seconds. <br/>Queries exceeding this limit will be aborted. Value of -1 means no limit. --query-execution-time-sec | integer | 30 | Maximum allowed query execution time, in seconds. <br/>Queries exceeding this limit will be aborted. Value of -1 means no limit.
--query-plan-cache | bool | true | Cache generated query plans. --query-plan-cache | bool | true | Cache generated query plans.
--query-plan-cache-ttl | int | 60 | Time to live for cached query plans, in seconds. --query-plan-cache-ttl | int | 60 | Time to live for cached query plans, in seconds.
--snapshot-cycle-sec | integer | 300 | Interval (seconds) between database snapshots.<br/>Value of -1 turns taking snapshots off. --durability-enabled | bool | true | If database state persistence is enabled (snapshot and write-ahead log).
--snapshot-on-exit | bool | true | Make a snapshot when closing Memgraph. --durability-directory | string | "/var/lib/memgraph/durability" | Path to the directory where durability files will be stored.
--snapshot-directory | string | "/var/lib/memgraph/snapshots" | Path to the directory where snapshots will be stored. --db-recover-on-startup | bool | true | Recover the database on startup (from snapshots and write-ahead logs).
--snapshot-cycle-sec | integer | 300 | Interval between database snapshots, in seconds.
--snapshot-max-retained | integer | 3 | Number of retained snapshots.<br/>Value -1 means without limit. --snapshot-max-retained | integer | 3 | Number of retained snapshots.<br/>Value -1 means without limit.
--snapshot-recover-on-startup | bool | true | Recover the database on startup using the last<br/>stored snapshot. --snapshot-on-exit | bool | true | Make a snapshot when closing Memgraph.
--log-file | string | "/var/log/memgraph/memgraph.log" | Path to where the log should be stored. --log-file | string | "/var/log/memgraph/memgraph.log" | Path to where the log should be stored.
--also-log-to-stderr | bool | false | If `true`, log messages will go to stderr in addition to logfiles. --also-log-to-stderr | bool | false | If `true`, log messages will go to stderr in addition to logfiles.
--flag-file | string | "" | Path to a file containing additional configuration settings. --flag-file | string | "" | Path to a file containing additional configuration settings.

View File

@ -1,66 +1,49 @@
#include <experimental/filesystem>
#include <functional> #include <functional>
#include <gflags/gflags.h>
#include <glog/logging.h> #include <glog/logging.h>
#include "database/creation_exception.hpp" #include "database/creation_exception.hpp"
#include "database/graph_db.hpp" #include "database/graph_db.hpp"
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "durability/paths.hpp"
#include "durability/recovery.hpp" #include "durability/recovery.hpp"
#include "durability/snapshooter.hpp" #include "durability/snapshooter.hpp"
#include "storage/edge.hpp" #include "storage/edge.hpp"
#include "storage/garbage_collector.hpp" #include "storage/garbage_collector.hpp"
#include "utils/timer.hpp" #include "utils/timer.hpp"
bool ValidateSnapshotDirectory(const char *flagname, const std::string &value) { namespace fs = std::experimental::filesystem;
if (fs::exists(value) && !fs::is_directory(value)) {
std::cout << "The snapshot directory path '" << value
<< "' is not a directory!" << std::endl;
return false;
}
return true;
}
DEFINE_int32(gc_cycle_sec, 30, GraphDb::GraphDb(GraphDb::Config config)
"Amount of time between starts of two cleaning cycles in seconds. " : config_(config),
"-1 to turn off."); gc_vertices_(vertices_, vertex_record_deleter_,
DEFINE_int32(snapshot_max_retained, -1,
"Number of retained snapshots, -1 means without limit.");
DEFINE_int32(snapshot_cycle_sec, -1,
"Amount of time between starts of two snapshooters in seconds. -1 "
"to turn off.");
DEFINE_int32(query_execution_time_sec, 180,
"Maximum allowed query execution time. Queries exceeding this "
"limit will be aborted. Value of -1 means no limit.");
DEFINE_bool(snapshot_on_exit, false, "Snapshot on exiting the database.");
DEFINE_string(snapshot_directory, "snapshots",
"Path to directory in which to save snapshots.");
DEFINE_validator(snapshot_directory, &ValidateSnapshotDirectory);
DEFINE_bool(snapshot_recover_on_startup, false, "Recover database on startup.");
GraphDb::GraphDb()
: gc_vertices_(vertices_, vertex_record_deleter_,
vertex_version_list_deleter_), vertex_version_list_deleter_),
gc_edges_(edges_, edge_record_deleter_, edge_version_list_deleter_) { gc_edges_(edges_, edge_record_deleter_, edge_version_list_deleter_),
wal_{config.durability_directory, config.durability_enabled} {
// Pause of -1 means we shouldn't run the GC. // Pause of -1 means we shouldn't run the GC.
if (FLAGS_gc_cycle_sec != -1) { if (config.gc_cycle_sec != -1) {
gc_scheduler_.Run(std::chrono::seconds(FLAGS_gc_cycle_sec), gc_scheduler_.Run(std::chrono::seconds(config.gc_cycle_sec),
[this]() { CollectGarbage(); }); [this]() { CollectGarbage(); });
} }
if (FLAGS_snapshot_recover_on_startup) // If snapshots are enabled we need the durability dir.
durability::Recover(FLAGS_snapshot_directory, *this); if (config.durability_enabled)
wal_.Enable(); durability::CheckDurabilityDir(config.durability_directory);
if (config.db_recover_on_startup)
durability::Recover(config.durability_directory, *this);
if (config.durability_enabled) wal_.Enable();
StartSnapshooting(); StartSnapshooting();
if (FLAGS_query_execution_time_sec != -1) { if (config.query_execution_time_sec != -1) {
transaction_killer_.Run( transaction_killer_.Run(
std::chrono::seconds( std::chrono::seconds(
std::max(1, std::min(5, FLAGS_query_execution_time_sec / 4))), std::max(1, std::min(5, config.query_execution_time_sec / 4))),
[this]() { [this]() {
tx_engine_.ForEachActiveTransaction([](tx::Transaction &t) { tx_engine_.ForEachActiveTransaction([this](tx::Transaction &t) {
if (t.creation_time() + if (t.creation_time() +
std::chrono::seconds(FLAGS_query_execution_time_sec) < std::chrono::seconds(config_.query_execution_time_sec) <
std::chrono::steady_clock::now()) { std::chrono::steady_clock::now()) {
t.set_should_abort(); t.set_should_abort();
}; };
@ -75,16 +58,17 @@ void GraphDb::Shutdown() {
} }
void GraphDb::StartSnapshooting() { void GraphDb::StartSnapshooting() {
if (FLAGS_snapshot_cycle_sec != -1) { if (config_.durability_enabled) {
auto create_snapshot = [this]() -> void { auto create_snapshot = [this]() -> void {
GraphDbAccessor db_accessor(*this); GraphDbAccessor db_accessor(*this);
if (!durability::MakeSnapshot(db_accessor, fs::path(FLAGS_snapshot_directory), if (!durability::MakeSnapshot(db_accessor,
FLAGS_snapshot_max_retained)) { fs::path(config_.durability_directory),
config_.snapshot_max_retained)) {
LOG(WARNING) << "Durability: snapshot creation failed"; LOG(WARNING) << "Durability: snapshot creation failed";
} }
db_accessor.Commit(); db_accessor.Commit();
}; };
snapshot_creator_.Run(std::chrono::seconds(FLAGS_snapshot_cycle_sec), snapshot_creator_.Run(std::chrono::seconds(config_.snapshot_cycle_sec),
create_snapshot); create_snapshot);
} }
} }
@ -152,12 +136,12 @@ GraphDb::~GraphDb() {
transaction_killer_.Stop(); transaction_killer_.Stop();
// Create last database snapshot // Create last database snapshot
if (FLAGS_snapshot_on_exit == true) { if (config_.snapshot_on_exit == true) {
GraphDbAccessor db_accessor(*this); GraphDbAccessor db_accessor(*this);
LOG(INFO) << "Creating snapshot on shutdown..." << std::endl; LOG(INFO) << "Creating snapshot on shutdown..." << std::endl;
const bool status = durability::MakeSnapshot( const bool status = durability::MakeSnapshot(
db_accessor, fs::path(FLAGS_snapshot_directory), db_accessor, fs::path(config_.durability_directory),
FLAGS_snapshot_max_retained); config_.snapshot_max_retained);
if (status) { if (status) {
std::cout << "Snapshot created successfully." << std::endl; std::cout << "Snapshot created successfully." << std::endl;
} else { } else {

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include <thread>
#include "cppitertools/filter.hpp" #include "cppitertools/filter.hpp"
#include "cppitertools/imap.hpp" #include "cppitertools/imap.hpp"
@ -21,8 +19,6 @@
#include "transactions/engine.hpp" #include "transactions/engine.hpp"
#include "utils/scheduler.hpp" #include "utils/scheduler.hpp"
namespace fs = std::experimental::filesystem;
/** /**
* Main class which represents Database concept in code. * Main class which represents Database concept in code.
* This class is essentially a data structure. It exposes * This class is essentially a data structure. It exposes
@ -51,7 +47,23 @@ namespace fs = std::experimental::filesystem;
*/ */
class GraphDb { class GraphDb {
public: public:
GraphDb(); /// GraphDb configuration. Initialized from flags, but modifiable.
struct Config {
Config();
// Durability flags.
bool durability_enabled;
std::string durability_directory;
bool db_recover_on_startup;
int snapshot_cycle_sec;
int snapshot_max_retained;
int snapshot_on_exit;
// Misc flags.
int gc_cycle_sec;
int query_execution_time_sec;
};
explicit GraphDb(Config config = Config{});
/** Delete all vertices and edges and free all deferred deleters. */ /** Delete all vertices and edges and free all deferred deleters. */
~GraphDb(); ~GraphDb();
@ -74,6 +86,8 @@ class GraphDb {
void StartSnapshooting(); void StartSnapshooting();
Config config_;
/** transaction engine related to this database */ /** transaction engine related to this database */
tx::Engine tx_engine_; tx::Engine tx_engine_;

View File

@ -0,0 +1,44 @@
#include <experimental/filesystem>
#include <iostream>
#include <limits>
#include <gflags/gflags.h>
#include "database/graph_db.hpp"
#include "utils/flag_validation.hpp"
namespace fs = std::experimental::filesystem;
// TODO review: tech docs say the default here is 'true', which it is in the
// community config. Should we set the default here to true? On some other
// points the tech docs are consistent with community config, and not with these
// defaults.
DEFINE_bool(durability_enabled, false,
"If durability (database persistence) should be enabled");
DEFINE_string(
durability_directory, "durability",
"Path to directory in which to save snapshots and write-ahead log files.");
DEFINE_bool(db_recover_on_startup, false, "Recover database on startup.");
DEFINE_VALIDATED_int32(
snapshot_cycle_sec, 3600,
"Amount of time between two snapshots, in seconds (min 60).",
FLAG_IN_RANGE(1, std::numeric_limits<int32_t>::max()));
DEFINE_int32(snapshot_max_retained, -1,
"Number of retained snapshots, -1 means without limit.");
DEFINE_bool(snapshot_on_exit, false, "Snapshot on exiting the database.");
DEFINE_int32(gc_cycle_sec, 30,
"Amount of time between starts of two cleaning cycles in seconds. "
"-1 to turn off.");
DEFINE_int32(query_execution_time_sec, 180,
"Maximum allowed query execution time. Queries exceeding this "
"limit will be aborted. Value of -1 means no limit.");
GraphDb::Config::Config()
: durability_enabled{FLAGS_durability_enabled},
durability_directory{FLAGS_durability_directory},
db_recover_on_startup{FLAGS_db_recover_on_startup},
snapshot_cycle_sec{FLAGS_snapshot_cycle_sec},
snapshot_max_retained{FLAGS_snapshot_max_retained},
snapshot_on_exit{FLAGS_snapshot_on_exit},
gc_cycle_sec{FLAGS_gc_cycle_sec},
query_execution_time_sec{FLAGS_query_execution_time_sec} {}

26
src/durability/paths.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <experimental/filesystem>
#include <string>
#include "glog/logging.h"
namespace durability {
const std::string kSnapshotDir = "snapshots";
const std::string kWalDir = "wal";
/// Ensures the given durability directory exists and is ready for use. Creates
/// the directory if it doesn't exist.
inline void CheckDurabilityDir(const std::string &durability_dir) {
namespace fs = std::experimental::filesystem;
if (fs::exists(durability_dir)) {
CHECK(fs::is_directory(durability_dir)) << "The durability directory path '"
<< durability_dir
<< "' is not a directory!";
} else {
bool success = fs::create_directory(durability_dir);
CHECK(success) << "Failed to create durability directory '"
<< durability_dir << "'.";
}
}
}

View File

@ -6,6 +6,7 @@
#include "communication/bolt/v1/decoder/decoder.hpp" #include "communication/bolt/v1/decoder/decoder.hpp"
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "durability/hashed_file_reader.hpp" #include "durability/hashed_file_reader.hpp"
#include "durability/paths.hpp"
#include "durability/version.hpp" #include "durability/version.hpp"
#include "durability/wal.hpp" #include "durability/wal.hpp"
#include "query/typed_value.hpp" #include "query/typed_value.hpp"
@ -222,11 +223,12 @@ std::experimental::optional<tx::transaction_id_t> TransactionIdFromWalFilename(
} }
// TODO - finer-grained recovery feedback could be useful here. // TODO - finer-grained recovery feedback could be useful here.
bool RecoverWal(GraphDbAccessor &db_accessor, RecoveryData &recovery_data) { bool RecoverWal(const fs::path &wal_dir, GraphDbAccessor &db_accessor,
RecoveryData &recovery_data) {
// Get paths to all the WAL files and sort them (on date). // Get paths to all the WAL files and sort them (on date).
std::vector<fs::path> wal_files; std::vector<fs::path> wal_files;
if (!fs::exists(FLAGS_wal_directory)) return true; if (!fs::exists(wal_dir)) return true;
for (auto &wal_file : fs::directory_iterator(FLAGS_wal_directory)) for (auto &wal_file : fs::directory_iterator(wal_dir))
wal_files.emplace_back(wal_file); wal_files.emplace_back(wal_file);
std::sort(wal_files.begin(), wal_files.end()); std::sort(wal_files.begin(), wal_files.end());
@ -335,11 +337,12 @@ bool RecoverWal(GraphDbAccessor &db_accessor, RecoveryData &recovery_data) {
} }
} // anonymous namespace } // anonymous namespace
bool Recover(const fs::path &snapshot_dir, GraphDb &db) { bool Recover(const fs::path &durability_dir, GraphDb &db) {
RecoveryData recovery_data; RecoveryData recovery_data;
// Attempt to recover from snapshot files in reverse order (from newest // Attempt to recover from snapshot files in reverse order (from newest
// backwards). // backwards).
const auto snapshot_dir = durability_dir / kSnapshotDir;
std::vector<fs::path> snapshot_files; std::vector<fs::path> snapshot_files;
if (fs::exists(snapshot_dir) && fs::is_directory(snapshot_dir)) if (fs::exists(snapshot_dir) && fs::is_directory(snapshot_dir))
for (auto &file : fs::directory_iterator(snapshot_dir)) for (auto &file : fs::directory_iterator(snapshot_dir))
@ -365,7 +368,7 @@ bool Recover(const fs::path &snapshot_dir, GraphDb &db) {
// WAL recovery does not have to be complete for the recovery to be // WAL recovery does not have to be complete for the recovery to be
// considered successful. For the time being ignore the return value, // considered successful. For the time being ignore the return value,
// consider a better system. // consider a better system.
RecoverWal(db_accessor, recovery_data); RecoverWal(durability_dir / kWalDir, db_accessor, recovery_data);
db_accessor.Commit(); db_accessor.Commit();
// Index recovery. // Index recovery.

View File

@ -17,14 +17,14 @@ bool ReadSnapshotSummary(HashedFileReader &buffer, int64_t &vertex_count,
int64_t &edge_count, uint64_t &hash); int64_t &edge_count, uint64_t &hash);
/** /**
* Recovers database from snapshot_file. If recovering fails, false is returned * Recovers database from durability. If recovering fails, false is returned
* and db_accessor aborts transaction, else true is returned and transaction is * and db_accessor aborts transaction, else true is returned and transaction is
* commited. * commited.
* *
* @param snapshot_dir - Path to snapshot directory. * @param durability_dir - Path to durability directory.
* @param db - The database to recover into. * @param db - The database to recover into.
* @return - If recovery was succesful. * @return - If recovery was succesful.
*/ */
bool Recover(const std::experimental::filesystem::path &snapshot_dir, bool Recover(const std::experimental::filesystem::path &durability_dir,
GraphDb &db); GraphDb &db);
} }

View File

@ -7,9 +7,12 @@
#include "communication/bolt/v1/encoder/base_encoder.hpp" #include "communication/bolt/v1/encoder/base_encoder.hpp"
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "durability/hashed_file_writer.hpp" #include "durability/hashed_file_writer.hpp"
#include "durability/paths.hpp"
#include "durability/version.hpp" #include "durability/version.hpp"
#include "utils/datetime/timestamp.hpp" #include "utils/datetime/timestamp.hpp"
namespace fs = std::experimental::filesystem;
namespace durability { namespace durability {
namespace { namespace {
@ -67,11 +70,11 @@ bool Encode(const fs::path &snapshot_file, GraphDbAccessor &db_accessor_) {
return true; return true;
} }
void MaintainMaxRetainedFiles(const fs::path &snapshot_folder, void MaintainMaxRetainedFiles(const fs::path &snapshot_dir,
int snapshot_max_retained) { int snapshot_max_retained) {
if (snapshot_max_retained == -1) return; if (snapshot_max_retained == -1) return;
std::vector<fs::path> files; std::vector<fs::path> files;
for (auto &file : fs::directory_iterator(snapshot_folder)) for (auto &file : fs::directory_iterator(snapshot_dir))
files.push_back(file.path()); files.push_back(file.path());
if (static_cast<int>(files.size()) <= snapshot_max_retained) return; if (static_cast<int>(files.size()) <= snapshot_max_retained) return;
sort(files.begin(), files.end()); sort(files.begin(), files.end());
@ -83,25 +86,29 @@ void MaintainMaxRetainedFiles(const fs::path &snapshot_folder,
} }
} // annonnymous namespace } // annonnymous namespace
fs::path MakeSnapshotPath(const fs::path &snapshot_folder) { fs::path MakeSnapshotPath(const fs::path &durability_dir) {
std::string date_str = std::string date_str =
Timestamp(Timestamp::now()) Timestamp(Timestamp::now())
.to_string("{:04d}_{:02d}_{:02d}__{:02d}_{:02d}_{:02d}_{:05d}"); .to_string("{:04d}_{:02d}_{:02d}__{:02d}_{:02d}_{:02d}_{:05d}");
return snapshot_folder / date_str; return durability_dir / kSnapshotDir / date_str;
} }
bool MakeSnapshot(GraphDbAccessor &db_accessor_, bool MakeSnapshot(GraphDbAccessor &db_accessor_, const fs::path &durability_dir,
const fs::path &snapshot_folder,
const int snapshot_max_retained) { const int snapshot_max_retained) {
if (!fs::exists(snapshot_folder) && auto ensure_dir = [](const auto &dir) {
!fs::create_directories(snapshot_folder)) { if (!fs::exists(dir) && !fs::create_directories(dir)) {
LOG(ERROR) << "Error while creating directory " << snapshot_folder; LOG(ERROR) << "Error while creating directory " << dir;
return false; return false;
} }
const auto snapshot_file = MakeSnapshotPath(snapshot_folder); return true;
};
if (!ensure_dir(durability_dir)) return false;
if (!ensure_dir(durability_dir / kSnapshotDir)) return false;
const auto snapshot_file = MakeSnapshotPath(durability_dir);
if (fs::exists(snapshot_file)) return false; if (fs::exists(snapshot_file)) return false;
if (Encode(snapshot_file, db_accessor_)) { if (Encode(snapshot_file, db_accessor_)) {
MaintainMaxRetainedFiles(snapshot_folder, snapshot_max_retained); MaintainMaxRetainedFiles(durability_dir / kSnapshotDir,
snapshot_max_retained);
return true; return true;
} else { } else {
std::error_code error_code; // Just for exception suppression. std::error_code error_code; // Just for exception suppression.

View File

@ -9,17 +9,15 @@ using path = std::experimental::filesystem::path;
/** Generates a path for a DB snapshot in the given folder in a well-defined /** Generates a path for a DB snapshot in the given folder in a well-defined
* sortable format. */ * sortable format. */
path MakeSnapshotPath(const path &snapshot_folder); // TODO review - move to paths.hpp?
path MakeSnapshotPath(const path &durability_dir);
/** /**
* Make snapshot and save it in snapshots folder. Returns true if successful. * Make snapshot and save it in snapshots folder. Returns true if successful.
* @param db_accessor: * @param db_accessor- GraphDbAccessor used to access elements of GraphDb.
* GraphDbAccessor used to access elements of GraphDb. * @param durability_dir - directory where durability data is stored.
* @param snapshot_folder: * @param snapshot_max_retained - maximum number of snapshots to retain.
* folder where snapshots are stored.
* @param snapshot_max_retained:
* maximum number of snapshots stored in snapshot folder.
*/ */
bool MakeSnapshot(GraphDbAccessor &db_accessor, const path &snapshot_folder, bool MakeSnapshot(GraphDbAccessor &db_accessor, const path &durability_dir,
int snapshot_max_retained); int snapshot_max_retained);
} }

View File

@ -1,22 +1,22 @@
#include "wal.hpp" #include "wal.hpp"
#include "communication/bolt/v1/decoder/decoded_value.hpp" #include "communication/bolt/v1/decoder/decoded_value.hpp"
#include "durability/paths.hpp"
#include "utils/datetime/timestamp.hpp" #include "utils/datetime/timestamp.hpp"
#include "utils/flag_validation.hpp" #include "utils/flag_validation.hpp"
DEFINE_int32(wal_flush_interval_millis, -1, DEFINE_HIDDEN_int32(
"Interval between two write-ahead log flushes, in milliseconds. " wal_flush_interval_millis, 2,
"Set to -1 to disable the WAL."); "Interval between two write-ahead log flushes, in milliseconds.");
DEFINE_string(wal_directory, "wal", DEFINE_HIDDEN_int32(
"Directory in which the write-ahead log files are stored."); wal_rotate_ops_count, 10000,
"How many write-ahead ops should be stored in a single WAL file "
"before rotating it.");
DEFINE_int32(wal_rotate_ops_count, 10000, DEFINE_VALIDATED_HIDDEN_int32(wal_buffer_size, 4096,
"How many write-ahead ops should be stored in a single WAL file " "Write-ahead log buffer size.",
"before rotating it."); FLAG_IN_RANGE(1, 1 << 30));
DEFINE_VALIDATED_int32(wal_buffer_size, 4096, "Write-ahead log buffer size.",
FLAG_IN_RANGE(1, 1 << 30));
namespace durability { namespace durability {
@ -146,8 +146,12 @@ std::experimental::optional<WriteAheadLog::Op> WriteAheadLog::Op::Decode(
#undef DECODE_MEMBER #undef DECODE_MEMBER
WriteAheadLog::WriteAheadLog() { WriteAheadLog::WriteAheadLog(
if (FLAGS_wal_flush_interval_millis >= 0) { const std::experimental::filesystem::path &durability_dir,
bool durability_enabled)
: ops_{FLAGS_wal_buffer_size}, wal_file_{durability_dir} {
if (durability_enabled) {
CheckDurabilityDir(durability_dir);
wal_file_.Init(); wal_file_.Init();
scheduler_.Run(std::chrono::milliseconds(FLAGS_wal_flush_interval_millis), scheduler_.Run(std::chrono::milliseconds(FLAGS_wal_flush_interval_millis),
[this]() { wal_file_.Flush(ops_); }); [this]() { wal_file_.Flush(ops_); });
@ -155,10 +159,9 @@ WriteAheadLog::WriteAheadLog() {
} }
WriteAheadLog::~WriteAheadLog() { WriteAheadLog::~WriteAheadLog() {
if (FLAGS_wal_flush_interval_millis >= 0) { // TODO review : scheduler.Stop() legal if it wasn't started?
scheduler_.Stop(); scheduler_.Stop();
wal_file_.Flush(ops_); if (enabled_) wal_file_.Flush(ops_);
}
} }
void WriteAheadLog::TxBegin(tx::transaction_id_t tx_id) { void WriteAheadLog::TxBegin(tx::transaction_id_t tx_id) {
@ -250,24 +253,28 @@ void WriteAheadLog::BuildIndex(tx::transaction_id_t tx_id,
Emplace(std::move(op)); Emplace(std::move(op));
} }
WriteAheadLog::WalFile::WalFile(
const std::experimental::filesystem::path &durability_dir)
: wal_dir_{durability_dir / kWalDir} {}
WriteAheadLog::WalFile::~WalFile() { WriteAheadLog::WalFile::~WalFile() {
if (!current_wal_file_.empty()) writer_.Close(); if (!current_wal_file_.empty()) writer_.Close();
} }
namespace { namespace {
auto MakeFilePath(const std::string &suffix) { auto MakeFilePath(const std::experimental::filesystem::path &wal_dir,
return std::experimental::filesystem::path(FLAGS_wal_directory) / const std::string &suffix) {
(Timestamp::now().to_iso8601() + suffix); return wal_dir / (Timestamp::now().to_iso8601() + suffix);
} }
} }
void WriteAheadLog::WalFile::Init() { void WriteAheadLog::WalFile::Init() {
if (!std::experimental::filesystem::exists(FLAGS_wal_directory) && if (!std::experimental::filesystem::exists(wal_dir_) &&
!std::experimental::filesystem::create_directories(FLAGS_wal_directory)) { !std::experimental::filesystem::create_directories(wal_dir_)) {
LOG(ERROR) << "Can't write to WAL directory: " << FLAGS_wal_directory; LOG(ERROR) << "Can't write to WAL directory: " << wal_dir_;
current_wal_file_ = std::experimental::filesystem::path(); current_wal_file_ = std::experimental::filesystem::path();
} else { } else {
current_wal_file_ = MakeFilePath("__current"); current_wal_file_ = MakeFilePath(wal_dir_, "__current");
try { try {
writer_.Open(current_wal_file_); writer_.Open(current_wal_file_);
} catch (std::ios_base::failure &) { } catch (std::ios_base::failure &) {
@ -311,7 +318,8 @@ void WriteAheadLog::WalFile::RotateFile() {
writer_.Close(); writer_.Close();
std::experimental::filesystem::rename( std::experimental::filesystem::rename(
current_wal_file_, current_wal_file_,
MakeFilePath("__max_transaction_" + std::to_string(latest_tx_))); MakeFilePath(wal_dir_,
"__max_transaction_" + std::to_string(latest_tx_)));
Init(); Init();
} }

View File

@ -18,19 +18,6 @@
#include "transactions/type.hpp" #include "transactions/type.hpp"
#include "utils/scheduler.hpp" #include "utils/scheduler.hpp"
// The amount of time between two flushes of the write-ahead log,
// in milliseconds.
DECLARE_int32(wal_flush_interval_millis);
// Directory in which the WAL is dumped.
DECLARE_string(wal_directory);
// How many Ops are stored in a single WAL file.
DECLARE_int32(wal_rotate_ops_count);
// The WAL buffer size (number of ops in a buffer).
DECLARE_int32(wal_buffer_size);
namespace durability { namespace durability {
/** A database operation log for durability. Buffers and periodically serializes /** A database operation log for durability. Buffers and periodically serializes
@ -99,7 +86,8 @@ class WriteAheadLog {
communication::bolt::Decoder<HashedFileReader> &decoder); communication::bolt::Decoder<HashedFileReader> &decoder);
}; };
WriteAheadLog(); WriteAheadLog(const std::experimental::filesystem::path &durability_dir,
bool durability_enabled);
~WriteAheadLog(); ~WriteAheadLog();
/** Enables the WAL. Called at the end of GraphDb construction, after /** Enables the WAL. Called at the end of GraphDb construction, after
@ -130,6 +118,7 @@ class WriteAheadLog {
/** Groups the logic of WAL file handling (flushing, naming, rotating) */ /** Groups the logic of WAL file handling (flushing, naming, rotating) */
class WalFile { class WalFile {
public: public:
WalFile(const std::experimental::filesystem::path &wal__dir);
~WalFile(); ~WalFile();
/** Initializes the WAL file. Must be called before first flush. Can be /** Initializes the WAL file. Must be called before first flush. Can be
@ -141,6 +130,7 @@ class WriteAheadLog {
void Flush(RingBuffer<Op> &buffer); void Flush(RingBuffer<Op> &buffer);
private: private:
const std::experimental::filesystem::path wal_dir_;
HashedFileWriter writer_; HashedFileWriter writer_;
communication::bolt::PrimitiveEncoder<HashedFileWriter> encoder_{writer_}; communication::bolt::PrimitiveEncoder<HashedFileWriter> encoder_{writer_};
@ -159,7 +149,7 @@ class WriteAheadLog {
void RotateFile(); void RotateFile();
}; };
RingBuffer<Op> ops_{FLAGS_wal_buffer_size}; RingBuffer<Op> ops_;
Scheduler scheduler_; Scheduler scheduler_;
WalFile wal_file_; WalFile wal_file_;
// Used for disabling the WAL during DB recovery. // Used for disabling the WAL during DB recovery.

View File

@ -32,9 +32,9 @@ class Memgraph:
default=get_absolute_path("memgraph", "build")) default=get_absolute_path("memgraph", "build"))
argp.add_argument("--port", default="7687", argp.add_argument("--port", default="7687",
help="Database and client port") help="Database and client port")
argp.add_argument("--snapshot-directory", default=None) argp.add_argument("--durability-directory", default=None)
argp.add_argument("--snapshot-on-exit", action="store_true") argp.add_argument("--snapshot-on-exit", action="store_true")
argp.add_argument("--snapshot-recover-on-startup", action="store_true") argp.add_argument("--db-recover-on-startup", action="store_true")
self.log.info("Initializing Runner with arguments %r", args) self.log.info("Initializing Runner with arguments %r", args)
self.args, _ = argp.parse_known_args(args) self.args, _ = argp.parse_known_args(args)
self.config = config self.config = config
@ -48,11 +48,11 @@ class Memgraph:
database_args = ["--port", self.args.port] database_args = ["--port", self.args.port]
if self.num_workers: if self.num_workers:
database_args += ["--num_workers", str(self.num_workers)] database_args += ["--num_workers", str(self.num_workers)]
if self.args.snapshot_directory: if self.args.durability_directory:
database_args += ["--snapshot-directory", database_args += ["--durability-directory",
self.args.snapshot_directory] self.args.durability_directory]
if self.args.snapshot_recover_on_startup: if self.args.db_recover_on_startup:
database_args += ["--snapshot-recover-on-startup"] database_args += ["--db-recover-on-startup"]
if self.args.snapshot_on_exit: if self.args.snapshot_on_exit:
database_args += ["--snapshot-on-exit"] database_args += ["--snapshot-on-exit"]

View File

@ -3,25 +3,17 @@
# NOTE: all paths are relative to the run folder # NOTE: all paths are relative to the run folder
# (where the executable is run) # (where the executable is run)
# directory to the folder with snapshots # recover from the 'durability' directory
--snapshot-directory=snapshots --durability-directory=durability
--db-recover-on-startup=true
# but don't perform durability
--durability-enabled=false
--snapshot-on-exit=false
# cleaning cycle interval # cleaning cycle interval
# if set to -1 the GC will not run # if set to -1 the GC will not run
--gc-cycle-sec=-1 --gc-cycle-sec=-1
# snapshot cycle interval
# if set to -1 the snapshooter will not run
--snapshot-cycle-sec=-1
# create snapshot disabled on db exit
--snapshot-on-exit=false
# max number of snapshots which will be kept on the disk at some point
# if set to -1 the max number of snapshots is unlimited
--snapshot-max-retained=-1
--snapshot-recover-on-startup=true
# number of workers # number of workers
--num-workers=8 --num-workers=8

View File

@ -12,7 +12,6 @@ import argparse
import os import os
import shutil import shutil
import subprocess import subprocess
import sys
import tempfile import tempfile
import time import time
@ -42,9 +41,9 @@ class Memgraph:
# database args # database args
database_args = [binary, "--num-workers", self.num_workers, database_args = [binary, "--num-workers", self.num_workers,
"--snapshot-directory", os.path.join(self.dataset, "--durability-directory", os.path.join(self.dataset,
"memgraph"), "memgraph"),
"--snapshot-recover-on-startup", "true", "--db-recover-on-startup", "true",
"--port", self.port] "--port", self.port]
# database env # database env

View File

@ -118,7 +118,7 @@ parser.add_argument("--memgraph", default = os.path.join(BUILD_DIR,
parser.add_argument("--config", default = os.path.join(CONFIG_DIR, parser.add_argument("--config", default = os.path.join(CONFIG_DIR,
"stress.conf")) "stress.conf"))
parser.add_argument("--log-file", default = "") parser.add_argument("--log-file", default = "")
parser.add_argument("--snapshot-directory", default = "") parser.add_argument("--durability-directory", default = "")
parser.add_argument("--python", default = os.path.join(SCRIPT_DIR, parser.add_argument("--python", default = os.path.join(SCRIPT_DIR,
"ve3", "bin", "python3"), type = str) "ve3", "bin", "python3"), type = str)
parser.add_argument("--large-dataset", action = "store_const", parser.add_argument("--large-dataset", action = "store_const",
@ -138,8 +138,8 @@ if not args.verbose:
cmd += ["--min-log-level", "1"] cmd += ["--min-log-level", "1"]
if args.log_file: if args.log_file:
cmd += ["--log-file", args.log_file] cmd += ["--log-file", args.log_file]
if args.snapshot_directory: if args.durability_directory:
cmd += ["--snapshot-directory", args.snapshot_directory] cmd += ["--durability-directory", args.durability_directory]
proc_mg = subprocess.Popen(cmd, cwd = cwd, proc_mg = subprocess.Popen(cmd, cwd = cwd,
env = {"MEMGRAPH_CONFIG": args.config}) env = {"MEMGRAPH_CONFIG": args.config})
time.sleep(1.0) time.sleep(1.0)

View File

@ -14,15 +14,12 @@
#include "database/graph_db.hpp" #include "database/graph_db.hpp"
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "durability/hashed_file_reader.hpp" #include "durability/hashed_file_reader.hpp"
#include "durability/paths.hpp"
#include "durability/recovery.hpp" #include "durability/recovery.hpp"
#include "durability/snapshooter.hpp" #include "durability/snapshooter.hpp"
#include "durability/version.hpp" #include "durability/version.hpp"
#include "utils/string.hpp" #include "utils/string.hpp"
DECLARE_string(snapshot_directory);
DECLARE_bool(snapshot_on_exit);
DECLARE_int32(snapshot_cycle_sec);
DECLARE_bool(snapshot_recover_on_startup);
DECLARE_int32(wal_flush_interval_millis); DECLARE_int32(wal_flush_interval_millis);
DECLARE_int32(wal_rotate_ops_count); DECLARE_int32(wal_rotate_ops_count);
@ -214,27 +211,36 @@ void CompareDbs(GraphDb &a, GraphDb &b) {
} }
} }
const fs::path kSnapshotDir = const fs::path kDurabilityDir =
fs::temp_directory_path() / "MG_test_unit_durability" / "snapshot"; fs::temp_directory_path() / "MG_test_unit_durability";
const fs::path kWalDir = const fs::path kSnapshotDir = kDurabilityDir / durability::kSnapshotDir;
fs::temp_directory_path() / "MG_test_unit_durability" / "wal"; const fs::path kWalDir = kDurabilityDir / durability::kWalDir;
void CleanDurability() { void CleanDurability() {
if (fs::exists(kSnapshotDir)) if (fs::exists(kDurabilityDir)) fs::remove_all(kDurabilityDir);
for (auto file : fs::directory_iterator(kSnapshotDir)) fs::remove(file);
if (fs::exists(kWalDir))
for (auto file : fs::directory_iterator(kWalDir)) fs::remove(file);
} }
std::vector<fs::path> DirFiles(fs::path dir) { std::vector<fs::path> DirFiles(fs::path dir) {
std::vector<fs::path> files; std::vector<fs::path> files;
for (auto &file : fs::directory_iterator(dir)) files.push_back(file.path()); if (fs::exists(dir))
for (auto &file : fs::directory_iterator(dir)) files.push_back(file.path());
return files; return files;
} }
auto DbConfig() {
GraphDb::Config config;
config.durability_enabled = false;
config.durability_directory = kDurabilityDir;
config.snapshot_on_exit = false;
config.db_recover_on_startup = false;
return config;
}
void MakeSnapshot(GraphDb &db, int snapshot_max_retained = -1) { void MakeSnapshot(GraphDb &db, int snapshot_max_retained = -1) {
GraphDbAccessor dba(db); GraphDbAccessor dba(db);
durability::MakeSnapshot(dba, kSnapshotDir, snapshot_max_retained); ASSERT_TRUE(
durability::MakeSnapshot(dba, kDurabilityDir, snapshot_max_retained));
dba.Commit(); dba.Commit();
} }
@ -278,23 +284,18 @@ void MakeDb(GraphDb &db, int scale, std::vector<int> indices = {}) {
class Durability : public ::testing::Test { class Durability : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
FLAGS_wal_rotate_ops_count = 1000;
CleanDurability(); CleanDurability();
FLAGS_snapshot_cycle_sec = -1;
FLAGS_snapshot_directory = kSnapshotDir;
FLAGS_snapshot_on_exit = false;
FLAGS_wal_flush_interval_millis = -1;
FLAGS_wal_directory = kWalDir;
FLAGS_snapshot_recover_on_startup = false;
} }
void TearDown() override { CleanDurability(); } void TearDown() override { CleanDurability(); }
}; };
TEST_F(Durability, WalEncoding) { TEST_F(Durability, WalEncoding) {
FLAGS_wal_flush_interval_millis = 1;
FLAGS_wal_rotate_ops_count = 5000;
{ {
GraphDb db; auto config = DbConfig();
config.durability_enabled = true;
GraphDb db{config};
GraphDbAccessor dba(db); GraphDbAccessor dba(db);
auto v0 = dba.InsertVertex(); auto v0 = dba.InsertVertex();
ASSERT_EQ(v0.id(), 0); ASSERT_EQ(v0.id(), 0);
@ -371,7 +372,7 @@ TEST_F(Durability, WalEncoding) {
TEST_F(Durability, SnapshotEncoding) { TEST_F(Durability, SnapshotEncoding) {
{ {
GraphDb db; GraphDb db{DbConfig()};
GraphDbAccessor dba(db); GraphDbAccessor dba(db);
auto v0 = dba.InsertVertex(); auto v0 = dba.InsertVertex();
ASSERT_EQ(v0.id(), 0); ASSERT_EQ(v0.id(), 0);
@ -473,22 +474,23 @@ TEST_F(Durability, SnapshotEncoding) {
} }
TEST_F(Durability, SnapshotRecovery) { TEST_F(Durability, SnapshotRecovery) {
GraphDb db; GraphDb db{DbConfig()};
MakeDb(db, 300, {0, 1, 2}); MakeDb(db, 300, {0, 1, 2});
MakeDb(db, 300); MakeDb(db, 300);
MakeDb(db, 300, {3, 4}); MakeDb(db, 300, {3, 4});
MakeSnapshot(db); MakeSnapshot(db);
{ {
FLAGS_snapshot_recover_on_startup = true; auto recovered_config = DbConfig();
GraphDb recovered; recovered_config.db_recover_on_startup = true;
GraphDb recovered{recovered_config};
CompareDbs(db, recovered); CompareDbs(db, recovered);
} }
} }
TEST_F(Durability, WalRecovery) { TEST_F(Durability, WalRecovery) {
FLAGS_wal_flush_interval_millis = 2; auto config = DbConfig();
FLAGS_wal_rotate_ops_count = 5000; config.durability_enabled = true;
GraphDb db; GraphDb db{config};
MakeDb(db, 300, {0, 1, 2}); MakeDb(db, 300, {0, 1, 2});
MakeDb(db, 300); MakeDb(db, 300);
MakeDb(db, 300, {3, 4}); MakeDb(db, 300, {3, 4});
@ -499,16 +501,17 @@ TEST_F(Durability, WalRecovery) {
EXPECT_GT(DirFiles(kWalDir).size(), 1); EXPECT_GT(DirFiles(kWalDir).size(), 1);
{ {
FLAGS_snapshot_recover_on_startup = true; auto recovered_config = DbConfig();
GraphDb recovered; recovered_config.db_recover_on_startup = true;
GraphDb recovered{recovered_config};
CompareDbs(db, recovered); CompareDbs(db, recovered);
} }
} }
TEST_F(Durability, SnapshotAndWalRecovery) { TEST_F(Durability, SnapshotAndWalRecovery) {
FLAGS_wal_flush_interval_millis = 2; auto config = DbConfig();
FLAGS_wal_rotate_ops_count = 1000; config.durability_enabled = true;
GraphDb db; GraphDb db{config};
MakeDb(db, 300, {0, 1, 2}); MakeDb(db, 300, {0, 1, 2});
MakeDb(db, 300); MakeDb(db, 300);
MakeSnapshot(db); MakeSnapshot(db);
@ -522,16 +525,17 @@ TEST_F(Durability, SnapshotAndWalRecovery) {
EXPECT_GT(DirFiles(kWalDir).size(), 1); EXPECT_GT(DirFiles(kWalDir).size(), 1);
{ {
FLAGS_snapshot_recover_on_startup = true; auto recovered_config = DbConfig();
GraphDb recovered; recovered_config.db_recover_on_startup = true;
GraphDb recovered{recovered_config};
CompareDbs(db, recovered); CompareDbs(db, recovered);
} }
} }
TEST_F(Durability, SnapshotAndWalRecoveryAfterComplexTxSituation) { TEST_F(Durability, SnapshotAndWalRecoveryAfterComplexTxSituation) {
FLAGS_wal_flush_interval_millis = 2; auto config = DbConfig();
FLAGS_wal_rotate_ops_count = 1000; config.durability_enabled = true;
GraphDb db; GraphDb db{config};
// The first transaction modifies and commits. // The first transaction modifies and commits.
GraphDbAccessor dba_1{db}; GraphDbAccessor dba_1{db};
@ -571,17 +575,18 @@ TEST_F(Durability, SnapshotAndWalRecoveryAfterComplexTxSituation) {
ASSERT_EQ(DirFiles(kSnapshotDir).size(), 1); ASSERT_EQ(DirFiles(kSnapshotDir).size(), 1);
EXPECT_GT(DirFiles(kWalDir).size(), 1); EXPECT_GT(DirFiles(kWalDir).size(), 1);
{ {
FLAGS_snapshot_recover_on_startup = true; auto recovered_config = DbConfig();
GraphDb recovered; recovered_config.db_recover_on_startup = true;
GraphDb recovered{recovered_config};
ASSERT_EQ(VisibleVertexCount(recovered), 400); ASSERT_EQ(VisibleVertexCount(recovered), 400);
CompareDbs(db, recovered); CompareDbs(db, recovered);
} }
} }
TEST_F(Durability, NoWalDuringRecovery) { TEST_F(Durability, NoWalDuringRecovery) {
FLAGS_wal_flush_interval_millis = 2; auto config = DbConfig();
FLAGS_wal_rotate_ops_count = 1000; config.durability_enabled = true;
GraphDb db; GraphDb db{config};
MakeDb(db, 300, {0, 1, 2}); MakeDb(db, 300, {0, 1, 2});
// Sleep to ensure the WAL gets flushed. // Sleep to ensure the WAL gets flushed.
@ -590,17 +595,17 @@ TEST_F(Durability, NoWalDuringRecovery) {
auto wal_files_before = DirFiles(kWalDir); auto wal_files_before = DirFiles(kWalDir);
ASSERT_GT(wal_files_before.size(), 3); ASSERT_GT(wal_files_before.size(), 3);
{ {
FLAGS_snapshot_recover_on_startup = true; auto recovered_config = DbConfig();
GraphDb recovered; recovered_config.db_recover_on_startup = true;
GraphDb recovered{recovered_config};
CompareDbs(db, recovered); CompareDbs(db, recovered);
auto wal_files_after = DirFiles(kWalDir); auto wal_files_after = DirFiles(kWalDir);
// We get an extra file for the "current" wal of the recovered db. EXPECT_EQ(wal_files_after.size(), wal_files_before.size());
EXPECT_EQ(wal_files_after.size(), wal_files_before.size() + 1);
} }
} }
TEST_F(Durability, SnapshotRetention) { TEST_F(Durability, SnapshotRetention) {
GraphDb db; GraphDb db{DbConfig()};
for (auto &pair : {std::pair<int, int>{5, 10}, {5, 3}, {7, -1}}) { for (auto &pair : {std::pair<int, int>{5, 10}, {5, 3}, {7, -1}}) {
CleanDurability(); CleanDurability();
int count, retain; int count, retain;
@ -609,8 +614,7 @@ TEST_F(Durability, SnapshotRetention) {
// Track the added snapshots to ensure the correct ones are pruned. // Track the added snapshots to ensure the correct ones are pruned.
std::unordered_set<std::string> snapshots; std::unordered_set<std::string> snapshots;
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
GraphDbAccessor dba(db); MakeSnapshot(db, retain);
durability::MakeSnapshot(dba, kSnapshotDir, retain);
auto latest = GetLastFile(kSnapshotDir); auto latest = GetLastFile(kSnapshotDir);
snapshots.emplace(GetLastFile(kSnapshotDir)); snapshots.emplace(GetLastFile(kSnapshotDir));
// Ensures that the latest snapshot was not in the snapshots collection // Ensures that the latest snapshot was not in the snapshots collection
@ -624,8 +628,10 @@ TEST_F(Durability, SnapshotRetention) {
} }
TEST_F(Durability, SnapshotOnExit) { TEST_F(Durability, SnapshotOnExit) {
FLAGS_snapshot_directory = kSnapshotDir; {
FLAGS_snapshot_on_exit = true; auto config = DbConfig();
{ GraphDb graph_db; } config.snapshot_on_exit = true;
GraphDb graph_db{config};
}
EXPECT_EQ(DirFiles(kSnapshotDir).size(), 1); EXPECT_EQ(DirFiles(kSnapshotDir).size(), 1);
} }

View File

@ -7,11 +7,10 @@
#include "database/graph_db_datatypes.hpp" #include "database/graph_db_datatypes.hpp"
#include "database/indexes/label_property_index.hpp" #include "database/indexes/label_property_index.hpp"
DECLARE_int32(gc_cycle_sec);
TEST(GraphDbTest, GarbageCollectIndices) { TEST(GraphDbTest, GarbageCollectIndices) {
FLAGS_gc_cycle_sec = -1; GraphDb::Config config;
GraphDb graph_db; config.gc_cycle_sec = -1;
GraphDb graph_db{config};
std::unique_ptr<GraphDbAccessor> dba = std::unique_ptr<GraphDbAccessor> dba =
std::make_unique<GraphDbAccessor>(graph_db); std::make_unique<GraphDbAccessor>(graph_db);

View File

@ -1,15 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import time import sys
import sys
import os import os
import tempfile import tempfile
from tabulate import tabulate from tabulate import tabulate
from timeit import default_timer as timer from timeit import default_timer as timer
# hackish way to resuse existing start code # hackish way to resuse existing start code
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + sys.path.append(os.path.dirname(os.path.realpath(__file__)) +
"/../tests/macro_benchmark/") "/../tests/macro_benchmark/")
from databases import * from databases import *
from clients import * from clients import *
@ -17,11 +16,11 @@ from common import get_absolute_path
def main(): def main():
path = get_absolute_path("benchmarking.conf", "config") path = get_absolute_path("benchmarking.conf", "config")
tmp_dir = tempfile.TemporaryDirectory() durability_dir = tempfile.TemporaryDirectory()
SNAPSHOT_DIR_ARG = ["--snapshot-directory", tmp_dir.name] DURABILITY_DIR_ARG = ["--durability-directory", durability_dir.name]
MAKE_SNAPSHOT_ARGS = ["--snapshot-on-exit"] + SNAPSHOT_DIR_ARG MAKE_SNAPSHOT_ARGS = ["--snapshot-on-exit"] + DURABILITY_DIR_ARG
RECOVER_SNAPSHOT_ARGS = ["--snapshot-recover-on-startup"] + SNAPSHOT_DIR_ARG RECOVER_SNAPSHOT_ARGS = ["--db-recover-on-startup"] + DURABILITY_DIR_ARG
snapshot_memgraph = Memgraph(MAKE_SNAPSHOT_ARGS, path, 1) snapshot_memgraph = Memgraph(MAKE_SNAPSHOT_ARGS, path, 1)
recover_memgraph = Memgraph(RECOVER_SNAPSHOT_ARGS, path, 1) recover_memgraph = Memgraph(RECOVER_SNAPSHOT_ARGS, path, 1)
client = QueryClient(None, 1) client = QueryClient(None, 1)
@ -32,7 +31,7 @@ def main():
for prop_per_node in [0, 1]: for prop_per_node in [0, 1]:
snapshot_memgraph.start() snapshot_memgraph.start()
properties = "{}".format(",".join( properties = "{}".format(",".join(
["p{}: 0".format(x) for x in range(prop_per_node)])) ["p{}: 0".format(x) for x in range(prop_per_node)]))
client(["UNWIND RANGE(1, {}) AS _ CREATE ({{ {} }})".format(node_cnt, properties)], snapshot_memgraph) client(["UNWIND RANGE(1, {}) AS _ CREATE ({{ {} }})".format(node_cnt, properties)], snapshot_memgraph)
client(["UNWIND RANGE(1, {}) AS _ MATCH (n) CREATE (n)-[:l]->(n)" client(["UNWIND RANGE(1, {}) AS _ MATCH (n) CREATE (n)-[:l]->(n)"
.format(edge_per_node)], snapshot_memgraph) .format(edge_per_node)], snapshot_memgraph)
@ -45,17 +44,16 @@ def main():
stop = timer() stop = timer()
diff = stop - start diff = stop - start
snapshots = os.listdir(tmp_dir.name) snapshots_dir = os.path.join(durability_dir.name, "snapshots")
assert len(snapshots) == 1 assert (len(os.listdir(snapshots_dir)) == 1)
snapshot_file = os.path.join(snapshots_dir, os.listdir(snapshots_dir)[0])
snap_path = tmp_dir.name + "/" + snapshots[0] snap_size = round(os.path.getsize(snapshot_file) / 1024. / 1024., 2)
snap_size = round(os.path.getsize(snap_path) / 1024. / 1024., 2) os.remove(snapshot_file)
os.remove(snap_path)
edge_cnt = edge_per_node * node_cnt edge_cnt = edge_per_node * node_cnt
results.append((node_cnt, edge_cnt, prop_per_node, snap_size, diff)) results.append((node_cnt, edge_cnt, prop_per_node, snap_size, diff))
print(tabulate(tabular_data=results, headers=["Nodes", "Edges", print(tabulate(tabular_data=results, headers=["Nodes", "Edges",
"Properties", "Snapshot size (MB)", "Elapsed time (s)"])) "Properties", "Snapshot size (MB)", "Elapsed time (s)"]))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,6 +5,7 @@ add_executable(mg_recovery_check
${memgraph_src_dir}/communication/bolt/v1/decoder/decoded_value.cpp ${memgraph_src_dir}/communication/bolt/v1/decoder/decoded_value.cpp
${memgraph_src_dir}/data_structures/concurrent/skiplist_gc.cpp ${memgraph_src_dir}/data_structures/concurrent/skiplist_gc.cpp
${memgraph_src_dir}/database/graph_db.cpp ${memgraph_src_dir}/database/graph_db.cpp
${memgraph_src_dir}/database/graph_db_config.cpp
${memgraph_src_dir}/database/graph_db_accessor.cpp ${memgraph_src_dir}/database/graph_db_accessor.cpp
${memgraph_src_dir}/durability/recovery.cpp ${memgraph_src_dir}/durability/recovery.cpp
${memgraph_src_dir}/durability/snapshooter.cpp ${memgraph_src_dir}/durability/snapshooter.cpp

View File

@ -3,24 +3,24 @@
#include "gflags/gflags.h" #include "gflags/gflags.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "database/graph_db_accessor.hpp"
#include "database/graph_db.hpp" #include "database/graph_db.hpp"
#include "database/graph_db_accessor.hpp"
#include "durability/recovery.hpp" #include "durability/recovery.hpp"
#include "query/typed_value.hpp" #include "query/typed_value.hpp"
static const char *usage = static const char *usage =
"--snapshot-dir SNAPSHOT_DIR\n" "--durability-dir DURABILITY_DIR\n"
"Check that Memgraph can recover that snapshot. This tool should be " "Check that Memgraph can recover the snapshot. This tool should be "
"invoked through 'test_mg_import' wrapper, so as to check that 'mg_import' " "invoked through 'test_mg_import' wrapper, so as to check that 'mg_import' "
"tools work correctly.\n"; "tools work correctly.\n";
DEFINE_string(snapshot_dir, "", "Path to where the snapshot is stored"); DEFINE_string(durability_dir, "", "Path to where the durability directory");
class RecoveryTest : public ::testing::Test { class RecoveryTest : public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
std::string snapshot(FLAGS_snapshot_dir); std::string durability_dir(FLAGS_durability_dir);
durability::Recover(snapshot, db_); durability::Recover(durability_dir, db_);
} }
GraphDb db_; GraphDb db_;

View File

@ -25,14 +25,17 @@ def main():
forum_nodes = os.path.join(_SCRIPT_DIR, 'csv', 'forum_nodes.csv') forum_nodes = os.path.join(_SCRIPT_DIR, 'csv', 'forum_nodes.csv')
relationships_0 = os.path.join(_SCRIPT_DIR, 'csv', 'relationships_0.csv') relationships_0 = os.path.join(_SCRIPT_DIR, 'csv', 'relationships_0.csv')
relationships_1 = os.path.join(_SCRIPT_DIR, 'csv', 'relationships_1.csv') relationships_1 = os.path.join(_SCRIPT_DIR, 'csv', 'relationships_1.csv')
with tempfile.TemporaryDirectory(suffix='-snapshots', dir=_SCRIPT_DIR) as snapshot_dir: with tempfile.TemporaryDirectory(
suffix='-durability', dir=_SCRIPT_DIR) as durability_dir:
snapshot_dir = os.path.join(durability_dir, 'snapshots')
os.makedirs(snapshot_dir, exist_ok=True)
out_snapshot = os.path.join(snapshot_dir, 'snapshot') out_snapshot = os.path.join(snapshot_dir, 'snapshot')
mg_import_csv = [args.mg_import_csv, '--nodes', comment_nodes, mg_import_csv = [args.mg_import_csv, '--nodes', comment_nodes,
'--nodes', forum_nodes, '--relationships', relationships_0, '--nodes', forum_nodes, '--relationships', relationships_0,
'--relationships', relationships_1, '--relationships', relationships_1,
'--out', out_snapshot, '--csv-delimiter=|', '--array-delimiter=;'] '--out', out_snapshot, '--csv-delimiter=|', '--array-delimiter=;']
subprocess.check_call(mg_import_csv) subprocess.check_call(mg_import_csv)
mg_recovery_check = [args.mg_recovery_check, '--snapshot-dir', snapshot_dir] mg_recovery_check = [args.mg_recovery_check, '--durability-dir', durability_dir]
subprocess.check_call(mg_recovery_check) subprocess.check_call(mg_recovery_check)