Skip to content

Commit

Permalink
LMDB sync options and new config settings
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptocode committed Feb 23, 2020
1 parent 861979c commit 1585ede
Show file tree
Hide file tree
Showing 17 changed files with 262 additions and 38 deletions.
3 changes: 2 additions & 1 deletion nano/core_test/block_store.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <nano/core_test/testutil.hpp>
#include <nano/crypto_lib/random_pool.hpp>
#include <nano/lib/lmdbconfig.hpp>
#include <nano/lib/stats.hpp>
#include <nano/lib/utility.hpp>
#include <nano/lib/work.hpp>
Expand Down Expand Up @@ -2090,7 +2091,7 @@ TEST (mdb_block_store, upgrade_backup)

// Now do the upgrade and confirm that backup is saved
nano::logger_mt logger;
nano::mdb_store store (logger, path, nano::txn_tracking_config{}, std::chrono::seconds (5), 128, 512, true);
nano::mdb_store store (logger, path, nano::txn_tracking_config{}, std::chrono::seconds (5), nano::lmdb_config{}, 512, true);
ASSERT_FALSE (store.init_error ());
auto transaction (store.tx_begin_read ());
ASSERT_LT (14, store.version_get (transaction));
Expand Down
6 changes: 3 additions & 3 deletions nano/core_test/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ TEST (node_config, serialization)
config1.callback_address = "test";
config1.callback_port = 10;
config1.callback_target = "test";
config1.lmdb_max_dbs = 256;
config1.deprecated_lmdb_max_dbs = 256;
nano::jsonconfig tree;
config1.serialize_json (tree);
nano::logging logging2;
Expand All @@ -572,7 +572,7 @@ TEST (node_config, serialization)
ASSERT_NE (config2.callback_address, config1.callback_address);
ASSERT_NE (config2.callback_port, config1.callback_port);
ASSERT_NE (config2.callback_target, config1.callback_target);
ASSERT_NE (config2.lmdb_max_dbs, config1.lmdb_max_dbs);
ASSERT_NE (config2.deprecated_lmdb_max_dbs, config1.deprecated_lmdb_max_dbs);

ASSERT_FALSE (tree.get_optional<std::string> ("epoch_block_link"));
ASSERT_FALSE (tree.get_optional<std::string> ("epoch_block_signer"));
Expand All @@ -590,7 +590,7 @@ TEST (node_config, serialization)
ASSERT_EQ (config2.callback_address, config1.callback_address);
ASSERT_EQ (config2.callback_port, config1.callback_port);
ASSERT_EQ (config2.callback_target, config1.callback_target);
ASSERT_EQ (config2.lmdb_max_dbs, config1.lmdb_max_dbs);
ASSERT_EQ (config2.deprecated_lmdb_max_dbs, config1.deprecated_lmdb_max_dbs);
}

TEST (node_config, v1_v2_upgrade)
Expand Down
18 changes: 16 additions & 2 deletions nano/core_test/toml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ TEST (toml, daemon_config_deserialize_defaults)
[node.statistics.log]
[node.statistics.sampling]
[node.websocket]
[node.lmdb]
[node.rocksdb]
[opencl]
[rpc]
Expand Down Expand Up @@ -157,7 +158,7 @@ TEST (toml, daemon_config_deserialize_defaults)
ASSERT_EQ (conf.node.external_address, defaults.node.external_address);
ASSERT_EQ (conf.node.external_port, defaults.node.external_port);
ASSERT_EQ (conf.node.io_threads, defaults.node.io_threads);
ASSERT_EQ (conf.node.lmdb_max_dbs, defaults.node.lmdb_max_dbs);
ASSERT_EQ (conf.node.deprecated_lmdb_max_dbs, defaults.node.deprecated_lmdb_max_dbs);
ASSERT_EQ (conf.node.max_work_generate_multiplier, defaults.node.max_work_generate_multiplier);
ASSERT_EQ (conf.node.network_threads, defaults.node.network_threads);
ASSERT_EQ (conf.node.secondary_work_peers, defaults.node.secondary_work_peers);
Expand Down Expand Up @@ -244,6 +245,10 @@ TEST (toml, daemon_config_deserialize_defaults)
ASSERT_EQ (conf.node.stat_config.log_counters_filename, defaults.node.stat_config.log_counters_filename);
ASSERT_EQ (conf.node.stat_config.log_samples_filename, defaults.node.stat_config.log_samples_filename);

ASSERT_EQ (conf.node.lmdb_config.sync, defaults.node.lmdb_config.sync);
ASSERT_EQ (conf.node.lmdb_config.max_databases, defaults.node.lmdb_config.max_databases);
ASSERT_EQ (conf.node.lmdb_config.map_size, defaults.node.lmdb_config.map_size);

ASSERT_EQ (conf.node.rocksdb_config.enable, defaults.node.rocksdb_config.enable);
ASSERT_EQ (conf.node.rocksdb_config.bloom_filter_bits, defaults.node.rocksdb_config.bloom_filter_bits);
ASSERT_EQ (conf.node.rocksdb_config.block_cache, defaults.node.rocksdb_config.block_cache);
Expand Down Expand Up @@ -493,6 +498,11 @@ TEST (toml, daemon_config_deserialize_no_defaults)
enable = true
port = 999
[node.lmdb]
sync = "nosync_safe"
max_databases = 999
map_size = 999
[node.rocksdb]
enable = true
bloom_filter_bits = 10
Expand Down Expand Up @@ -554,7 +564,7 @@ TEST (toml, daemon_config_deserialize_no_defaults)
ASSERT_NE (conf.node.external_address, defaults.node.external_address);
ASSERT_NE (conf.node.external_port, defaults.node.external_port);
ASSERT_NE (conf.node.io_threads, defaults.node.io_threads);
ASSERT_NE (conf.node.lmdb_max_dbs, defaults.node.lmdb_max_dbs);
ASSERT_NE (conf.node.deprecated_lmdb_max_dbs, defaults.node.deprecated_lmdb_max_dbs);
ASSERT_NE (conf.node.max_work_generate_multiplier, defaults.node.max_work_generate_multiplier);
ASSERT_NE (conf.node.frontiers_confirmation, defaults.node.frontiers_confirmation);
ASSERT_NE (conf.node.network_threads, defaults.node.network_threads);
Expand Down Expand Up @@ -642,6 +652,10 @@ TEST (toml, daemon_config_deserialize_no_defaults)
ASSERT_NE (conf.node.stat_config.log_counters_filename, defaults.node.stat_config.log_counters_filename);
ASSERT_NE (conf.node.stat_config.log_samples_filename, defaults.node.stat_config.log_samples_filename);

ASSERT_NE (conf.node.lmdb_config.sync, defaults.node.lmdb_config.sync);
ASSERT_NE (conf.node.lmdb_config.max_databases, defaults.node.lmdb_config.max_databases);
ASSERT_NE (conf.node.lmdb_config.map_size, defaults.node.lmdb_config.map_size);

ASSERT_NE (conf.node.rocksdb_config.enable, defaults.node.rocksdb_config.enable);
ASSERT_NE (conf.node.rocksdb_config.bloom_filter_bits, defaults.node.rocksdb_config.bloom_filter_bits);
ASSERT_NE (conf.node.rocksdb_config.block_cache, defaults.node.rocksdb_config.block_cache);
Expand Down
2 changes: 1 addition & 1 deletion nano/core_test/wallets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ TEST (wallets, DISABLED_wallet_create_max)
bool error (false);
nano::wallets wallets (error, *system.nodes[0]);
const int nonWalletDbs = 19;
for (int i = 0; i < system.nodes[0]->config.lmdb_max_dbs - nonWalletDbs; i++)
for (int i = 0; i < system.nodes[0]->config.deprecated_lmdb_max_dbs - nonWalletDbs; i++)
{
auto wallet_id = nano::random_wallet_id ();
auto wallet = wallets.create (wallet_id);
Expand Down
2 changes: 2 additions & 0 deletions nano/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ add_library (nano_lib
json_error_response.hpp
jsonconfig.hpp
jsonconfig.cpp
lmdbconfig.hpp
lmdbconfig.cpp
locks.hpp
locks.cpp
logger_mt.hpp
Expand Down
72 changes: 72 additions & 0 deletions nano/lib/lmdbconfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <nano/lib/lmdbconfig.hpp>
#include <nano/lib/tomlconfig.hpp>
#include <nano/secure/common.hpp>

#include <iostream>

nano::error nano::lmdb_config::serialize_toml (nano::tomlconfig & toml) const
{
std::string sync_string;
switch (sync)
{
case nano::lmdb_config::sync_strategy::always:
sync_string = "always";
break;
case nano::lmdb_config::sync_strategy::nosync_safe:
sync_string = "nosync_safe";
break;
case nano::lmdb_config::sync_strategy::nosync_unsafe:
sync_string = "nosync_unsafe";
break;
case nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory:
sync_string = "nosync_unsafe_large_memory";
break;
}

toml.put ("sync", sync_string, "Sync strategy for flushing commits to the ledger database. This does not affect the wallet database.\ntype:string,{always, nosync_safe, nosync_unsafe, nosync_unsafe_large_memory}");
toml.put ("max_databases", max_databases, "Maximum open lmdb databases. Increase default if more than 100 wallets is required.\nNote: external management is recommended when a large amounts of wallets are required (see https://docs.nano.org/integration-guides/key-management/).\ntype:uin32");
toml.put ("map_size", map_size, "Maximum ledger database map size in bytes.\ntype:uint64");
return toml.get_error ();
}

nano::error nano::lmdb_config::deserialize_toml (nano::tomlconfig & toml, bool is_deprecated_lmdb_dbs_used)
{
static nano::network_params params;
auto default_max_databases = max_databases;
toml.get_optional<uint32_t> ("max_databases", max_databases);
toml.get_optional<size_t> ("map_size", map_size);

// For now we accept either setting, but not both
if (!params.network.is_test_network () && is_deprecated_lmdb_dbs_used && default_max_databases != max_databases)
{
toml.get_error ().set ("Both the deprecated node.lmdb_max_dbs and the new node.lmdb.max_databases setting are used. Please use max_databases only.");
}

if (!toml.get_error ())
{
std::string sync_string = "always";
toml.get_optional<std::string> ("sync", sync_string);
if (sync_string == "always")
{
sync = nano::lmdb_config::sync_strategy::always;
}
else if (sync_string == "nosync_safe")
{
sync = nano::lmdb_config::sync_strategy::nosync_safe;
}
else if (sync_string == "nosync_unsafe")
{
sync = nano::lmdb_config::sync_strategy::nosync_unsafe;
}
else if (sync_string == "nosync_unsafe_large_memory")
{
sync = nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory;
}
else
{
toml.get_error ().set (sync_string + " is not a valid sync option");
}
}

return toml.get_error ();
}
50 changes: 50 additions & 0 deletions nano/lib/lmdbconfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include <nano/lib/errors.hpp>

#include <thread>

namespace nano
{
class tomlconfig;

/** Configuration options for LMDB */
class lmdb_config final
{
public:
/**
* Dictates how lmdb flushes to disk on commit.
* These options only apply to the ledger database; the wallet database
* always flush.
*/
enum sync_strategy
{
/** Always flush to disk on commit. This is default. */
always,

/** Do not flush meta data eagerly. This may cause loss of transactions, but maintains integrity. */
nosync_safe,

/**
* Let the OS decide when to flush to disk. On filesystems with write ordering, this has the same
* guarantees as nosync_safe, otherwise corruption may occur on system crash.
*/
nosync_unsafe,
/**
* Use a writeable memory map. Let the OS decide when to flush to disk, and make the request asynchronous.
* This may give better performance on systems where the database fits entirely in memory, otherwise is
* may be slower.
* @warning Do not use this option if external processes uses the database concurrently.
*/
nosync_unsafe_large_memory
};

nano::error serialize_toml (nano::tomlconfig & toml_a) const;
nano::error deserialize_toml (nano::tomlconfig & toml_a, bool is_deprecated_lmdb_dbs_used);

/** Sync strategy for the ledger database */
sync_strategy sync{ always };
uint32_t max_databases{ 128 };
size_t map_size{ 128ULL * 1024 * 1024 * 1024 };
};
}
16 changes: 10 additions & 6 deletions nano/node/lmdb/lmdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ void mdb_val::convert_buffer_to_value ()
}
}

nano::mdb_store::mdb_store (nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, int lmdb_max_dbs, size_t const batch_size, bool backup_before_upgrade) :
nano::mdb_store::mdb_store (nano::logger_mt & logger_a, boost::filesystem::path const & path_a, nano::txn_tracking_config const & txn_tracking_config_a, std::chrono::milliseconds block_processor_batch_max_time_a, nano::lmdb_config const & lmdb_config_a, size_t const batch_size, bool backup_before_upgrade) :
logger (logger_a),
env (error, path_a, lmdb_max_dbs, true),
env (error, path_a, nano::mdb_env::options::make ().set_config (lmdb_config_a).set_use_no_mem_init (true)),
mdb_txn_tracker (logger_a, txn_tracking_config_a, block_processor_batch_max_time_a),
txn_tracking_enabled (txn_tracking_config_a.enable)
{
Expand Down Expand Up @@ -91,7 +91,7 @@ txn_tracking_enabled (txn_tracking_config_a.enable)
if (needs_vacuuming && !network_constants.is_test_network ())
{
logger.always_log ("Preparing vacuum...");
auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_max_dbs);
auto vacuum_success = vacuum_after_upgrade (path_a, lmdb_config_a);
logger.always_log (vacuum_success ? "Vacuum succeeded." : "Failed to vacuum. (Optional) Ensure enough disk space is available for a copy of the database and try to vacuum after shutting down the node");
}
}
Expand All @@ -103,7 +103,7 @@ txn_tracking_enabled (txn_tracking_config_a.enable)
}
}

bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path_a, int lmdb_max_dbs)
bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a)
{
// Vacuum the database. This is not a required step and may actually fail if there isn't enough storage space.
auto vacuum_path = path_a.parent_path () / "vacuumed.ldb";
Expand All @@ -112,14 +112,18 @@ bool nano::mdb_store::vacuum_after_upgrade (boost::filesystem::path const & path
if (vacuum_success)
{
// Need to close the database to release the file handle
mdb_env_sync (env.environment, true);
mdb_env_close (env.environment);
env.environment = nullptr;

// Replace the ledger file with the vacuumed one
boost::filesystem::rename (vacuum_path, path_a);

// Set up the environment again
env.init (error, path_a, lmdb_max_dbs, true);
auto options = nano::mdb_env::options::make ()
.set_config (lmdb_config_a)
.set_use_no_mem_init (true);
env.init (error, path_a, options);
if (!error)
{
auto transaction (tx_begin_read ());
Expand Down Expand Up @@ -1305,4 +1309,4 @@ bool nano::mdb_store::upgrade_counters::are_equal () const
}

// Explicitly instantiate
template class nano::block_store_partial<MDB_val, nano::mdb_store>;
template class nano::block_store_partial<MDB_val, nano::mdb_store>;
5 changes: 3 additions & 2 deletions nano/node/lmdb/lmdb.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <nano/lib/diagnosticsconfig.hpp>
#include <nano/lib/lmdbconfig.hpp>
#include <nano/lib/logger_mt.hpp>
#include <nano/lib/numbers.hpp>
#include <nano/node/lmdb/lmdb_env.hpp>
Expand Down Expand Up @@ -36,7 +37,7 @@ class mdb_store : public block_store_partial<MDB_val, mdb_store>
using block_store_partial::block_exists;
using block_store_partial::unchecked_put;

mdb_store (nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), int lmdb_max_dbs = 128, size_t batch_size = 512, bool backup_before_upgrade = false);
mdb_store (nano::logger_mt &, boost::filesystem::path const &, nano::txn_tracking_config const & txn_tracking_config_a = nano::txn_tracking_config{}, std::chrono::milliseconds block_processor_batch_max_time_a = std::chrono::milliseconds (5000), nano::lmdb_config const & lmdb_config_a = nano::lmdb_config{}, size_t batch_size = 512, bool backup_before_upgrade = false);
nano::write_transaction tx_begin_write (std::vector<nano::tables> const & tables_requiring_lock = {}, std::vector<nano::tables> const & tables_no_lock = {}) override;
nano::read_transaction tx_begin_read () override;

Expand Down Expand Up @@ -261,7 +262,7 @@ class mdb_store : public block_store_partial<MDB_val, mdb_store>

size_t count (nano::transaction const & transaction_a, tables table_a) const override;

bool vacuum_after_upgrade (boost::filesystem::path const & path_a, int lmdb_max_dbs);
bool vacuum_after_upgrade (boost::filesystem::path const & path_a, nano::lmdb_config const & lmdb_config_a);

class upgrade_counters
{
Expand Down
29 changes: 22 additions & 7 deletions nano/node/lmdb/lmdb_env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

#include <boost/filesystem/operations.hpp>

nano::mdb_env::mdb_env (bool & error_a, boost::filesystem::path const & path_a, int max_dbs_a, bool use_no_mem_init_a, size_t map_size_a)
nano::mdb_env::mdb_env (bool & error_a, boost::filesystem::path const & path_a, nano::mdb_env::options options_a)
{
init (error_a, path_a, max_dbs_a, use_no_mem_init_a, map_size_a);
init (error_a, path_a, options_a);
}

void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a, int max_dbs_a, bool use_no_mem_init_a, size_t map_size_a)
void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a, nano::mdb_env::options options_a)
{
boost::system::error_code error_mkdir, error_chmod;
if (path_a.has_parent_path ())
Expand All @@ -18,11 +18,11 @@ void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a
{
auto status1 (mdb_env_create (&environment));
release_assert (status1 == 0);
auto status2 (mdb_env_set_maxdbs (environment, max_dbs_a));
auto status2 (mdb_env_set_maxdbs (environment, options_a.config.max_databases));
release_assert (status2 == 0);
auto map_size = map_size_a;
auto map_size = options_a.config.map_size;
auto max_valgrind_map_size = 16 * 1024 * 1024;
if (running_within_valgrind () && map_size_a > max_valgrind_map_size)
if (running_within_valgrind () && map_size > max_valgrind_map_size)
{
// In order to run LMDB under Valgrind, the maximum map size must be smaller than half your available RAM
map_size = max_valgrind_map_size;
Expand All @@ -34,7 +34,20 @@ void nano::mdb_env::init (bool & error_a, boost::filesystem::path const & path_a
// MDB_NORDAHEAD will allow platforms that support it to load the DB in memory as needed.
// MDB_NOMEMINIT prevents zeroing malloc'ed pages. Can provide improvement for non-sensitive data but may make memory checkers noisy (e.g valgrind).
auto environment_flags = MDB_NOSUBDIR | MDB_NOTLS | MDB_NORDAHEAD;
if (!running_within_valgrind () && use_no_mem_init_a)
if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_safe)
{
environment_flags |= MDB_NOMETASYNC;
}
else if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_unsafe)
{
environment_flags |= MDB_NOSYNC;
}
else if (options_a.config.sync == nano::lmdb_config::sync_strategy::nosync_unsafe_large_memory)
{
environment_flags |= MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC;
}

if (!running_within_valgrind () && options_a.use_no_mem_init)
{
environment_flags |= MDB_NOMEMINIT;
}
Expand Down Expand Up @@ -69,6 +82,8 @@ nano::mdb_env::~mdb_env ()
{
if (environment != nullptr)
{
// Make sure the commits are flushed. This is a no-op unless MDB_NOSYNC is used.
mdb_env_sync (environment, true);
mdb_env_close (environment);
}
}
Expand Down
Loading

0 comments on commit 1585ede

Please sign in to comment.