1#if XRPL_ROCKSDB_AVAILABLE
2#include <xrpl/basics/ByteUtilities.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/basics/contract.h>
6#include <xrpl/basics/safe_cast.h>
7#include <xrpl/beast/core/CurrentThreadName.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/beast/utility/instrumentation.h>
10#include <xrpl/config/BasicConfig.h>
11#include <xrpl/config/Constants.h>
12#include <xrpl/nodestore/Backend.h>
13#include <xrpl/nodestore/Factory.h>
14#include <xrpl/nodestore/Manager.h>
15#include <xrpl/nodestore/NodeObject.h>
16#include <xrpl/nodestore/Scheduler.h>
17#include <xrpl/nodestore/Types.h>
18#include <xrpl/nodestore/detail/BatchWriter.h>
19#include <xrpl/nodestore/detail/DecodedBlob.h>
20#include <xrpl/nodestore/detail/EncodedBlob.h>
22#include <rocksdb/advanced_options.h>
23#include <rocksdb/cache.h>
24#include <rocksdb/compression_type.h>
25#include <rocksdb/convenience.h>
26#include <rocksdb/db.h>
27#include <rocksdb/env.h>
28#include <rocksdb/filter_policy.h>
29#include <rocksdb/iterator.h>
30#include <rocksdb/options.h>
31#include <rocksdb/slice.h>
32#include <rocksdb/table.h>
33#include <rocksdb/write_batch.h>
45class RocksDBEnv :
public rocksdb::EnvWrapper
48 RocksDBEnv() : EnvWrapper(rocksdb::Env::Default())
54 ThreadParams(
void (*f)(
void*),
void* a) : f(f), a(a)
63 threadEntry(
void* ptr)
65 ThreadParams
const*
const p(
reinterpret_cast<ThreadParams*
>(ptr));
71 static std::atomic<std::size_t> kN;
72 std::size_t
const id(++kN);
79 StartThread(
void (*f)(
void*),
void* a)
override
81 auto*
const p =
new ThreadParams(f, a);
82 EnvWrapper::StartThread(&RocksDBEnv::threadEntry, p);
91 std::atomic<bool> deletePath_;
94 beast::Journal journal;
95 size_t const keyBytes;
98 std::unique_ptr<rocksdb::DB> db;
99 int fdMinRequired = 2048;
100 rocksdb::Options options;
104 Section
const& keyValues,
105 Scheduler& scheduler,
106 beast::Journal journal,
108 : deletePath_(false), journal(journal), keyBytes(keyBytes), batch(*this, scheduler)
111 Throw<std::runtime_error>(
"Missing path in RocksDBFactory backend");
113 rocksdb::BlockBasedTableOptions tableOptions;
117 keyValues.exists(Keys::kHardSet) && get<bool>(keyValues, Keys::kHardSet);
119 if (keyValues.exists(Keys::kCacheMb))
121 auto size = get<int>(keyValues, Keys::kCacheMb);
123 if (!hardSet && size == 256)
126 tableOptions.block_cache = rocksdb::NewLRUCache(megabytes(size));
129 if (
auto const v = get<int>(keyValues, Keys::kFilterBits))
131 bool const filterBlocks = !keyValues.exists(Keys::kFilterFull) ||
132 (get<int>(keyValues, Keys::kFilterFull) == 0);
133 tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(v, filterBlocks));
136 if (
getIfExists(keyValues, Keys::kOpenFiles, options.max_open_files))
138 if (!hardSet && options.max_open_files == 2000)
139 options.max_open_files = 8000;
141 fdMinRequired = options.max_open_files + 128;
144 if (keyValues.exists(Keys::kFileSizeMb))
146 auto fileSizeMb = get<int>(keyValues, Keys::kFileSizeMb);
148 if (!hardSet && fileSizeMb == 8)
151 options.target_file_size_base = megabytes(fileSizeMb);
152 options.max_bytes_for_level_base = 5 * options.target_file_size_base;
153 options.write_buffer_size = 2 * options.target_file_size_base;
156 getIfExists(keyValues, Keys::kFileSizeMult, options.target_file_size_multiplier);
158 if (keyValues.exists(Keys::kBgThreads))
160 options.env->SetBackgroundThreads(
161 get<int>(keyValues, Keys::kBgThreads), rocksdb::Env::LOW);
164 if (keyValues.exists(Keys::kHighThreads))
166 auto const highThreads = get<int>(keyValues, Keys::kHighThreads);
167 options.env->SetBackgroundThreads(highThreads, rocksdb::Env::HIGH);
172 options.max_background_flushes = highThreads;
175 options.compression = rocksdb::kSnappyCompression;
177 getIfExists(keyValues, Keys::kBlockSize, tableOptions.block_size);
179 if (keyValues.exists(Keys::kUniversalCompaction) &&
180 (get<int>(keyValues, Keys::kUniversalCompaction) != 0))
182 options.compaction_style = rocksdb::kCompactionStyleUniversal;
183 options.min_write_buffer_number_to_merge = 2;
184 options.max_write_buffer_number = 6;
185 options.write_buffer_size = 6 * options.target_file_size_base;
188 if (keyValues.exists(Keys::kBbtOptions))
190 rocksdb::ConfigOptions const configOptions;
191 auto const s = rocksdb::GetBlockBasedTableOptionsFromString(
192 configOptions, tableOptions, get(keyValues, Keys::kBbtOptions), &tableOptions);
195 Throw<std::runtime_error>(
196 std::string(
"Unable to set RocksDB bbt_options: ") + s.ToString());
200 options.table_factory.reset(NewBlockBasedTableFactory(tableOptions));
202 if (keyValues.exists(Keys::kOptions))
205 rocksdb::GetOptionsFromString(options, get(keyValues, Keys::kOptions), &options);
208 Throw<std::runtime_error>(
209 std::string(
"Unable to set RocksDB options: ") + s.ToString());
214 rocksdb::GetStringFromDBOptions(&s1, options,
"; ");
215 rocksdb::GetStringFromColumnFamilyOptions(&s2, options,
"; ");
216 JLOG(journal.
debug()) <<
"RocksDB DBOptions: " << s1;
217 JLOG(journal.
debug()) <<
"RocksDB CFOptions: " << s2;
220 ~RocksDBBackend()
override
226 open(
bool createIfMissing)
override
232 "xrpl::node_store::RocksDBBackend::open : database is already "
234 JLOG(journal.
error()) <<
"database is already open";
238 rocksdb::DB* localDb =
nullptr;
239 options.create_if_missing = createIfMissing;
240 rocksdb::Status
const status = rocksdb::DB::Open(options, name, &localDb);
241 if (!
status.ok() || (localDb ==
nullptr))
243 Throw<std::runtime_error>(
252 return static_cast<bool>(db);
280 XRPL_ASSERT(db,
"xrpl::node_store::RocksDBBackend::fetch : non-null database");
285 rocksdb::ReadOptions
const options;
286 rocksdb::Slice
const slice(
reinterpret_cast<char const*
>(hash.data()), keyBytes);
290 rocksdb::Status
const getStatus = db->Get(options, slice, &
string);
294 DecodedBlob decoded(hash.data(),
string.data(),
string.size());
298 *pObject = decoded.createObject();
304 status = Status::DataCorrupt;
309 if (getStatus.IsCorruption())
311 status = Status::DataCorrupt;
313 else if (getStatus.IsNotFound())
315 status = Status::NotFound;
320 static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
322 JLOG(journal.
error()) << getStatus.ToString();
340 "xrpl::node_store::RocksDBBackend::storeBatch : non-null "
342 rocksdb::WriteBatch wb;
344 for (
auto const& e : batch)
346 EncodedBlob
const encoded(e);
349 rocksdb::Slice(
reinterpret_cast<char const*
>(encoded.getKey()), keyBytes),
351 reinterpret_cast<char const*
>(encoded.getData()), encoded.getSize()));
354 rocksdb::WriteOptions
const options;
356 auto ret = db->Write(options, &wb);
359 Throw<std::runtime_error>(
"storeBatch failed: " + ret.ToString());
370 XRPL_ASSERT(db,
"xrpl::node_store::RocksDBBackend::forEach : non-null database");
371 rocksdb::ReadOptions
const options;
375 for (it->SeekToFirst(); it->Valid(); it->Next())
377 if (it->key().size() == keyBytes)
379 DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size());
383 f(decoded.createObject());
388 JLOG(journal.
fatal()) <<
"Corrupt NodeObject #" << it->key().ToString(
true);
395 JLOG(journal.
fatal()) <<
"Bad key size = " << it->key().size();
401 getWriteLoad()
override
403 return batch.getWriteLoad();
407 setDeletePath()
override
415 writeBatch(Batch
const& batch)
override
424 fdRequired()
const override
426 return fdMinRequired;
432class RocksDBFactory :
public Factory
440 RocksDBFactory(Manager& manager) : manager_(manager)
442 manager_.insert(*
this);
445 [[nodiscard]] std::string
446 getName()
const override
451 std::unique_ptr<Backend>
454 Section
const& keyValues,
456 Scheduler& scheduler,
457 beast::Journal journal)
override
464registerRocksDBFactory(Manager& manager)
466 static RocksDBFactory
const kInstance{manager};
A backend used for the NodeStore.
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
void storeBatch(Backend &backend, Batch const &batch)
Status
Return codes from Backend operations.
bool getIfExists(Section const §ion, std::string const &name, T &v)
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
This callback does the actual writing.