xrpld
Loading...
Searching...
No Matches
RocksDBFactory.cpp
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>
21
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>
34
35#include <atomic>
36#include <cstddef>
37#include <filesystem>
38#include <functional>
39#include <memory>
40#include <stdexcept>
41#include <string>
42
43namespace xrpl::node_store {
44
45class RocksDBEnv : public rocksdb::EnvWrapper
46{
47public:
48 RocksDBEnv() : EnvWrapper(rocksdb::Env::Default())
49 {
50 }
51
52 struct ThreadParams
53 {
54 ThreadParams(void (*f)(void*), void* a) : f(f), a(a)
55 {
56 }
57
58 void (*f)(void*);
59 void* a;
60 };
61
62 static void
63 threadEntry(void* ptr)
64 {
65 ThreadParams const* const p(reinterpret_cast<ThreadParams*>(ptr));
66 auto const f = p->f;
67
68 void* a(p->a);
69 delete p;
70
71 static std::atomic<std::size_t> kN;
72 std::size_t const id(++kN);
74
75 f(a);
76 }
77
78 void
79 StartThread(void (*f)(void*), void* a) override
80 {
81 auto* const p = new ThreadParams(f, a);
82 EnvWrapper::StartThread(&RocksDBEnv::threadEntry, p);
83 }
84};
85
86//------------------------------------------------------------------------------
87
88class RocksDBBackend : public Backend, public BatchWriter::Callback
89{
90private:
91 std::atomic<bool> deletePath_;
92
93public:
94 beast::Journal journal;
95 size_t const keyBytes;
96 BatchWriter batch;
97 std::string name;
98 std::unique_ptr<rocksdb::DB> db;
99 int fdMinRequired = 2048;
100 rocksdb::Options options;
101
102 RocksDBBackend(
103 int keyBytes,
104 Section const& keyValues,
105 Scheduler& scheduler,
106 beast::Journal journal,
107 RocksDBEnv* env)
108 : deletePath_(false), journal(journal), keyBytes(keyBytes), batch(*this, scheduler)
109 {
110 if (!getIfExists(keyValues, Keys::kPath, name))
111 Throw<std::runtime_error>("Missing path in RocksDBFactory backend");
112
113 rocksdb::BlockBasedTableOptions tableOptions;
114 options.env = env;
115
116 bool const hardSet =
117 keyValues.exists(Keys::kHardSet) && get<bool>(keyValues, Keys::kHardSet);
118
119 if (keyValues.exists(Keys::kCacheMb))
120 {
121 auto size = get<int>(keyValues, Keys::kCacheMb);
122
123 if (!hardSet && size == 256)
124 size = 1024;
125
126 tableOptions.block_cache = rocksdb::NewLRUCache(megabytes(size));
127 }
128
129 if (auto const v = get<int>(keyValues, Keys::kFilterBits))
130 {
131 bool const filterBlocks = !keyValues.exists(Keys::kFilterFull) ||
132 (get<int>(keyValues, Keys::kFilterFull) == 0);
133 tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(v, filterBlocks));
134 }
135
136 if (getIfExists(keyValues, Keys::kOpenFiles, options.max_open_files))
137 {
138 if (!hardSet && options.max_open_files == 2000)
139 options.max_open_files = 8000;
140
141 fdMinRequired = options.max_open_files + 128;
142 }
143
144 if (keyValues.exists(Keys::kFileSizeMb))
145 {
146 auto fileSizeMb = get<int>(keyValues, Keys::kFileSizeMb);
147
148 if (!hardSet && fileSizeMb == 8)
149 fileSizeMb = 256;
150
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;
154 }
155
156 getIfExists(keyValues, Keys::kFileSizeMult, options.target_file_size_multiplier);
157
158 if (keyValues.exists(Keys::kBgThreads))
159 {
160 options.env->SetBackgroundThreads(
161 get<int>(keyValues, Keys::kBgThreads), rocksdb::Env::LOW);
162 }
163
164 if (keyValues.exists(Keys::kHighThreads))
165 {
166 auto const highThreads = get<int>(keyValues, Keys::kHighThreads);
167 options.env->SetBackgroundThreads(highThreads, rocksdb::Env::HIGH);
168
169 // If we have high-priority threads, presumably we want to
170 // use them for background flushes
171 if (highThreads > 0)
172 options.max_background_flushes = highThreads;
173 }
174
175 options.compression = rocksdb::kSnappyCompression;
176
177 getIfExists(keyValues, Keys::kBlockSize, tableOptions.block_size);
178
179 if (keyValues.exists(Keys::kUniversalCompaction) &&
180 (get<int>(keyValues, Keys::kUniversalCompaction) != 0))
181 {
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;
186 }
187
188 if (keyValues.exists(Keys::kBbtOptions))
189 {
190 rocksdb::ConfigOptions const configOptions;
191 auto const s = rocksdb::GetBlockBasedTableOptionsFromString(
192 configOptions, tableOptions, get(keyValues, Keys::kBbtOptions), &tableOptions);
193 if (!s.ok())
194 {
195 Throw<std::runtime_error>(
196 std::string("Unable to set RocksDB bbt_options: ") + s.ToString());
197 }
198 }
199
200 options.table_factory.reset(NewBlockBasedTableFactory(tableOptions));
201
202 if (keyValues.exists(Keys::kOptions))
203 {
204 auto const s =
205 rocksdb::GetOptionsFromString(options, get(keyValues, Keys::kOptions), &options);
206 if (!s.ok())
207 {
208 Throw<std::runtime_error>(
209 std::string("Unable to set RocksDB options: ") + s.ToString());
210 }
211 }
212
213 std::string s1, s2;
214 rocksdb::GetStringFromDBOptions(&s1, options, "; ");
215 rocksdb::GetStringFromColumnFamilyOptions(&s2, options, "; ");
216 JLOG(journal.debug()) << "RocksDB DBOptions: " << s1;
217 JLOG(journal.debug()) << "RocksDB CFOptions: " << s2;
218 }
219
220 ~RocksDBBackend() override
221 {
222 close();
223 }
224
225 void
226 open(bool createIfMissing) override
227 {
228 if (db)
229 {
230 // LCOV_EXCL_START
231 UNREACHABLE(
232 "xrpl::node_store::RocksDBBackend::open : database is already "
233 "open");
234 JLOG(journal.error()) << "database is already open";
235 return;
236 // LCOV_EXCL_STOP
237 }
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))
242 {
243 Throw<std::runtime_error>(
244 std::string("Unable to open/create RocksDB: ") + status.ToString());
245 }
246 db.reset(localDb);
247 }
248
249 bool
250 isOpen() override
251 {
252 return static_cast<bool>(db);
253 }
254
255 void
256 close() override
257 {
258 if (db)
259 {
260 db.reset();
261 if (deletePath_)
262 {
263 std::filesystem::path const dir = name;
265 }
266 }
267 }
268
270 getName() override
271 {
272 return name;
273 }
274
275 //--------------------------------------------------------------------------
276
277 Status
278 fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) override
279 {
280 XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::fetch : non-null database");
281 pObject->reset();
282
283 Status status = Status::Ok;
284
285 rocksdb::ReadOptions const options;
286 rocksdb::Slice const slice(reinterpret_cast<char const*>(hash.data()), keyBytes);
287
288 std::string string;
289
290 rocksdb::Status const getStatus = db->Get(options, slice, &string);
291
292 if (getStatus.ok())
293 {
294 DecodedBlob decoded(hash.data(), string.data(), string.size());
295
296 if (decoded.wasOk())
297 {
298 *pObject = decoded.createObject();
299 }
300 else
301 {
302 // Decoding failed, probably corrupted!
303 //
304 status = Status::DataCorrupt;
305 }
306 }
307 else
308 {
309 if (getStatus.IsCorruption())
310 {
311 status = Status::DataCorrupt;
312 }
313 else if (getStatus.IsNotFound())
314 {
315 status = Status::NotFound;
316 }
317 else
318 {
319 status = static_cast<Status>(
320 static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
321
322 JLOG(journal.error()) << getStatus.ToString();
323 }
324 }
325
326 return status;
327 }
328
329 void
330 store(std::shared_ptr<NodeObject> const& object) override
331 {
332 batch.store(object);
333 }
334
335 void
336 storeBatch(Batch const& batch) override
337 {
338 XRPL_ASSERT(
339 db,
340 "xrpl::node_store::RocksDBBackend::storeBatch : non-null "
341 "database");
342 rocksdb::WriteBatch wb;
343
344 for (auto const& e : batch)
345 {
346 EncodedBlob const encoded(e);
347
348 wb.Put(
349 rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey()), keyBytes),
350 rocksdb::Slice(
351 reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
352 }
353
354 rocksdb::WriteOptions const options;
355
356 auto ret = db->Write(options, &wb);
357
358 if (!ret.ok())
359 Throw<std::runtime_error>("storeBatch failed: " + ret.ToString());
360 }
361
362 void
363 sync() override
364 {
365 }
366
367 void
368 forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override
369 {
370 XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::forEach : non-null database");
371 rocksdb::ReadOptions const options;
372
373 std::unique_ptr<rocksdb::Iterator> it(db->NewIterator(options));
374
375 for (it->SeekToFirst(); it->Valid(); it->Next())
376 {
377 if (it->key().size() == keyBytes)
378 {
379 DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size());
380
381 if (decoded.wasOk())
382 {
383 f(decoded.createObject());
384 }
385 else
386 {
387 // Uh oh, corrupted data!
388 JLOG(journal.fatal()) << "Corrupt NodeObject #" << it->key().ToString(true);
389 }
390 }
391 else
392 {
393 // VFALCO NOTE What does it mean to find an
394 // incorrectly sized key? Corruption?
395 JLOG(journal.fatal()) << "Bad key size = " << it->key().size();
396 }
397 }
398 }
399
400 int
401 getWriteLoad() override
402 {
403 return batch.getWriteLoad();
404 }
405
406 void
407 setDeletePath() override
408 {
409 deletePath_ = true;
410 }
411
412 //--------------------------------------------------------------------------
413
414 void
415 writeBatch(Batch const& batch) override
416 {
417 storeBatch(batch);
418 }
419
423 [[nodiscard]] int
424 fdRequired() const override
425 {
426 return fdMinRequired;
427 }
428};
429
430//------------------------------------------------------------------------------
431
432class RocksDBFactory : public Factory
433{
434private:
435 Manager& manager_;
436
437public:
438 RocksDBEnv env;
439
440 RocksDBFactory(Manager& manager) : manager_(manager)
441 {
442 manager_.insert(*this);
443 }
444
445 [[nodiscard]] std::string
446 getName() const override
447 {
448 return "RocksDB";
449 }
450
451 std::unique_ptr<Backend>
452 createInstance(
453 size_t keyBytes,
454 Section const& keyValues,
455 std::size_t,
456 Scheduler& scheduler,
457 beast::Journal journal) override
458 {
459 return std::make_unique<RocksDBBackend>(keyBytes, keyValues, scheduler, journal, &env);
460 }
461};
462
463void
464registerRocksDBFactory(Manager& manager)
465{
466 static RocksDBFactory const kInstance{manager};
467}
468
469} // namespace xrpl::node_store
470
471#endif
Stream fatal() const
Definition Journal.h:368
Stream error() const
Definition Journal.h:362
Stream debug() const
Definition Journal.h:344
A backend used for the NodeStore.
Definition Backend.h:29
T make_unique(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
void storeBatch(Backend &backend, Batch const &batch)
Definition TestBase.h:85
Status
Return codes from Backend operations.
bool getIfExists(Section const &section, std::string const &name, T &v)
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition SociDB.cpp:90
T remove_all(T... args)
T reset(T... args)
This callback does the actual writing.
Definition BatchWriter.h:30
T to_string(T... args)