rippled
Loading...
Searching...
No Matches
NuDBFactory.cpp
1#include <xrpl/basics/contract.h>
2#include <xrpl/beast/core/LexicalCast.h>
3#include <xrpl/beast/utility/instrumentation.h>
4#include <xrpl/nodestore/Factory.h>
5#include <xrpl/nodestore/Manager.h>
6#include <xrpl/nodestore/detail/DecodedBlob.h>
7#include <xrpl/nodestore/detail/EncodedBlob.h>
8#include <xrpl/nodestore/detail/codec.h>
9
10#include <boost/filesystem.hpp>
11
12#include <nudb/nudb.hpp>
13
14#include <chrono>
15#include <cstdint>
16#include <cstdio>
17#include <exception>
18#include <memory>
19
20namespace xrpl {
21namespace NodeStore {
22
23class NuDBBackend : public Backend
24{
25public:
26 // "appnum" is an application-defined constant stored in the header of a
27 // NuDB database. We used it to identify shard databases before that code
28 // was removed. For now, its only use is a sanity check that the database
29 // was created by xrpld.
30 static constexpr std::uint64_t appnum = 1;
31
33 size_t const keyBytes_;
37 nudb::store db_;
40
42 size_t keyBytes,
43 Section const& keyValues,
45 Scheduler& scheduler,
46 beast::Journal journal)
47 : j_(journal)
48 , keyBytes_(keyBytes)
50 , name_(get(keyValues, "path"))
51 , blockSize_(parseBlockSize(name_, keyValues, journal))
52 , deletePath_(false)
53 , scheduler_(scheduler)
54 {
55 if (name_.empty())
56 Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
57 }
58
60 size_t keyBytes,
61 Section const& keyValues,
63 Scheduler& scheduler,
64 nudb::context& context,
65 beast::Journal journal)
66 : j_(journal)
67 , keyBytes_(keyBytes)
69 , name_(get(keyValues, "path"))
70 , blockSize_(parseBlockSize(name_, keyValues, journal))
71 , db_(context)
72 , deletePath_(false)
73 , scheduler_(scheduler)
74 {
75 if (name_.empty())
76 Throw<std::runtime_error>("nodestore: Missing path in NuDB backend");
77 }
78
79 ~NuDBBackend() override
80 {
81 try
82 {
83 // close can throw and we don't want the destructor to throw.
84 close();
85 }
86 catch (nudb::system_error const&) // NOLINT(bugprone-empty-catch)
87 {
88 // Don't allow exceptions to propagate out of destructors.
89 // close() has already logged the error.
90 }
91 }
92
94 getName() override
95 {
96 return name_;
97 }
98
100 getBlockSize() const override
101 {
102 return blockSize_;
103 }
104
105 void
106 open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
107 {
108 using namespace boost::filesystem;
109 if (db_.is_open())
110 {
111 // LCOV_EXCL_START
112 UNREACHABLE(
113 "xrpl::NodeStore::NuDBBackend::open : database is already "
114 "open");
115 JLOG(j_.error()) << "database is already open";
116 return;
117 // LCOV_EXCL_STOP
118 }
119 auto const folder = path(name_);
120 auto const dp = (folder / "nudb.dat").string();
121 auto const kp = (folder / "nudb.key").string();
122 auto const lp = (folder / "nudb.log").string();
123 nudb::error_code ec;
124 if (createIfMissing)
125 {
126 create_directories(folder);
127 nudb::create<nudb::xxhasher>(
128 dp, kp, lp, appType, uid, salt, keyBytes_, blockSize_, 0.50, ec);
129 if (ec == nudb::errc::file_exists)
130 ec = {};
131 if (ec)
132 Throw<nudb::system_error>(ec);
133 }
134 db_.open(dp, kp, lp, ec);
135 if (ec)
136 Throw<nudb::system_error>(ec);
137
138 if (db_.appnum() != appnum)
139 Throw<std::runtime_error>("nodestore: unknown appnum");
140 db_.set_burst(burstSize_);
141 }
142
143 bool
144 isOpen() override
145 {
146 return db_.is_open();
147 }
148
149 void
150 open(bool createIfMissing) override
151 {
152 open(createIfMissing, appnum, nudb::make_uid(), nudb::make_salt());
153 }
154
155 void
156 close() override
157 {
158 if (db_.is_open())
159 {
160 nudb::error_code ec;
161 db_.close(ec);
162 if (ec)
163 {
164 // Log to make sure the nature of the error gets to the user.
165 JLOG(j_.fatal()) << "NuBD close() failed: " << ec.message();
166 Throw<nudb::system_error>(ec);
167 }
168
169 if (deletePath_)
170 {
171 boost::filesystem::remove_all(name_, ec);
172 if (ec)
173 {
174 JLOG(j_.fatal())
175 << "Filesystem remove_all of " << name_ << " failed with: " << ec.message();
176 }
177 }
178 }
179 }
180
181 Status
182 fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pno) override
183 {
184 Status status = ok;
185 pno->reset();
186 nudb::error_code ec;
187 db_.fetch(
188 hash.data(),
189 [&hash, pno, &status](void const* data, std::size_t size) {
190 nudb::detail::buffer bf;
191 auto const result = nodeobject_decompress(data, size, bf);
192 DecodedBlob decoded(hash.data(), result.first, result.second);
193 if (!decoded.wasOk())
194 {
195 status = dataCorrupt;
196 return;
197 }
198 *pno = decoded.createObject();
199 status = ok;
200 },
201 ec);
202 if (ec == nudb::error::key_not_found)
203 return notFound;
204 if (ec)
205 Throw<nudb::system_error>(ec);
206 return status;
207 }
208
210 fetchBatch(std::vector<uint256> const& hashes) override
211 {
213 results.reserve(hashes.size());
214 for (auto const& h : hashes)
215 {
217 Status const status = fetch(h, &nObj);
218 if (status != ok)
219 {
220 results.push_back({});
221 }
222 else
223 {
224 results.push_back(nObj);
225 }
226 }
227
228 return {results, ok};
229 }
230
231 void
233 {
234 EncodedBlob const e(no);
235 nudb::error_code ec;
236 nudb::detail::buffer bf;
237 auto const result = nodeobject_compress(e.getData(), e.getSize(), bf);
238 db_.insert(e.getKey(), result.first, result.second, ec);
239 if (ec && ec != nudb::error::key_exists)
240 Throw<nudb::system_error>(ec);
241 }
242
243 void
245 {
246 BatchWriteReport report{};
247 report.writeCount = 1;
248 auto const start = std::chrono::steady_clock::now();
249 do_insert(no);
250 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
252 scheduler_.onBatchWrite(report);
253 }
254
255 void
256 storeBatch(Batch const& batch) override
257 {
258 BatchWriteReport report{};
259 report.writeCount = batch.size();
260 auto const start = std::chrono::steady_clock::now();
261 for (auto const& e : batch)
262 do_insert(e);
263 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
265 scheduler_.onBatchWrite(report);
266 }
267
268 void
269 sync() override
270 {
271 }
272
273 void
275 {
276 auto const dp = db_.dat_path();
277 auto const kp = db_.key_path();
278 auto const lp = db_.log_path();
279 // auto const appnum = db_.appnum();
280 nudb::error_code ec;
281 db_.close(ec);
282 if (ec)
283 Throw<nudb::system_error>(ec);
284 nudb::visit(
285 dp,
286 [&](void const* key,
287 std::size_t key_bytes,
288 void const* data,
289 std::size_t size,
290 nudb::error_code&) {
291 nudb::detail::buffer bf;
292 auto const result = nodeobject_decompress(data, size, bf);
293 DecodedBlob decoded(key, result.first, result.second);
294 if (!decoded.wasOk())
295 {
296 ec = make_error_code(nudb::error::missing_value);
297 return;
298 }
299 f(decoded.createObject());
300 },
301 nudb::no_progress{},
302 ec);
303 if (ec)
304 Throw<nudb::system_error>(ec);
305 db_.open(dp, kp, lp, ec);
306 if (ec)
307 Throw<nudb::system_error>(ec);
308 }
309
310 int
311 getWriteLoad() override
312 {
313 return 0;
314 }
315
316 void
317 setDeletePath() override
318 {
319 deletePath_ = true;
320 }
321
322 void
323 verify() override
324 {
325 auto const dp = db_.dat_path();
326 auto const kp = db_.key_path();
327 auto const lp = db_.log_path();
328 nudb::error_code ec;
329 db_.close(ec);
330 if (ec)
331 Throw<nudb::system_error>(ec);
332 nudb::verify_info vi;
333 nudb::verify<nudb::xxhasher>(vi, dp, kp, 0, nudb::no_progress{}, ec);
334 if (ec)
335 Throw<nudb::system_error>(ec);
336 db_.open(dp, kp, lp, ec);
337 if (ec)
338 Throw<nudb::system_error>(ec);
339 }
340
341 int
342 fdRequired() const override
343 {
344 return 3;
345 }
346
347private:
348 static std::size_t
349 parseBlockSize(std::string const& name, Section const& keyValues, beast::Journal journal)
350 {
351 using namespace boost::filesystem;
352 auto const folder = path(name);
353 auto const kp = (folder / "nudb.key").string();
354
355 std::size_t const defaultSize = nudb::block_size(kp); // Default 4K from NuDB
356 std::size_t const blockSize = defaultSize;
357 std::string blockSizeStr;
358
359 if (!get_if_exists(keyValues, "nudb_block_size", blockSizeStr))
360 {
361 return blockSize; // Early return with default
362 }
363
364 try
365 {
366 std::size_t const parsedBlockSize = beast::lexicalCastThrow<std::size_t>(blockSizeStr);
367
368 // Validate: must be power of 2 between 4K and 32K
369 if (parsedBlockSize < 4096 || parsedBlockSize > 32768 ||
370 (parsedBlockSize & (parsedBlockSize - 1)) != 0)
371 {
373 s << "Invalid nudb_block_size: " << parsedBlockSize
374 << ". Must be power of 2 between 4096 and 32768.";
375 Throw<std::runtime_error>(s.str());
376 }
377
378 JLOG(journal.info()) << "Using custom NuDB block size: " << parsedBlockSize << " bytes";
379 return parsedBlockSize;
380 }
381 catch (std::exception const& e)
382 {
384 s << "Invalid nudb_block_size value: " << blockSizeStr << ". Error: " << e.what();
385 Throw<std::runtime_error>(s.str());
386 }
387 }
388};
389
390//------------------------------------------------------------------------------
391
392class NuDBFactory : public Factory
393{
394private:
396
397public:
398 explicit NuDBFactory(Manager& manager) : manager_(manager)
399 {
400 manager_.insert(*this);
401 }
402
404 getName() const override
405 {
406 return "NuDB";
407 }
408
411 size_t keyBytes,
412 Section const& keyValues,
413 std::size_t burstSize,
414 Scheduler& scheduler,
415 beast::Journal journal) override
416 {
417 return std::make_unique<NuDBBackend>(keyBytes, keyValues, burstSize, scheduler, journal);
418 }
419
422 size_t keyBytes,
423 Section const& keyValues,
424 std::size_t burstSize,
425 Scheduler& scheduler,
426 nudb::context& context,
427 beast::Journal journal) override
428 {
430 keyBytes, keyValues, burstSize, scheduler, context, journal);
431 }
432};
433
434void
436{
437 static NuDBFactory const instance{manager};
438}
439
440} // namespace NodeStore
441} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream fatal() const
Definition Journal.h:325
Stream error() const
Definition Journal.h:319
Stream info() const
Definition Journal.h:307
A backend used for the NodeStore.
Definition Backend.h:20
Parsed key/value blob into NodeObject components.
Definition DecodedBlob.h:19
std::shared_ptr< NodeObject > createObject()
Create a NodeObject from this data.
bool wasOk() const noexcept
Determine if the decoding was successful.
Definition DecodedBlob.h:26
Convert a NodeObject from in-memory to database format.
Definition EncodedBlob.h:36
void const * getKey() const noexcept
Definition EncodedBlob.h:90
std::size_t getSize() const noexcept
Definition EncodedBlob.h:96
void const * getData() const noexcept
Base class for backend factories.
Definition Factory.h:16
Singleton for managing NodeStore factories and back ends.
Definition Manager.h:12
virtual void insert(Factory &factory)=0
Add a factory.
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)
std::size_t const blockSize_
void verify() override
Perform consistency checks on database.
Status fetch(uint256 const &hash, std::shared_ptr< NodeObject > *pno) override
Fetch a single object.
void storeBatch(Batch const &batch) override
Store a group of objects.
int getWriteLoad() override
Estimate the number of write operations pending.
std::optional< std::size_t > getBlockSize() const override
Get the block size for backends that support it.
static std::size_t parseBlockSize(std::string const &name, Section const &keyValues, beast::Journal journal)
std::pair< std::vector< std::shared_ptr< NodeObject > >, Status > fetchBatch(std::vector< uint256 > const &hashes) override
Fetch a batch synchronously.
int fdRequired() const override
Returns the number of file descriptors the backend expects to need.
void store(std::shared_ptr< NodeObject > const &no) override
Store a single object.
beast::Journal const j_
static constexpr std::uint64_t appnum
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, nudb::context &context, beast::Journal journal)
std::string getName() override
Get the human-readable name of this backend.
void do_insert(std::shared_ptr< NodeObject > const &no)
void open(bool createIfMissing) override
Open the backend.
void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
Open the backend.
std::size_t const burstSize_
void close() override
Close the backend.
void setDeletePath() override
Remove contents on disk upon destruction.
bool isOpen() override
Returns true is the database is open.
void for_each(std::function< void(std::shared_ptr< NodeObject >)> f) override
Visit every object in the database This is usually called during import.
std::atomic< bool > deletePath_
std::unique_ptr< Backend > createInstance(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, nudb::context &context, beast::Journal journal) override
Create an instance of this factory's backend.
NuDBFactory(Manager &manager)
std::unique_ptr< Backend > createInstance(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal) override
Create an instance of this factory's backend.
std::string getName() const override
Retrieve the name of this factory.
Scheduling for asynchronous backend activity.
virtual void onBatchWrite(BatchWriteReport const &report)=0
Reports the completion of a batch write Allows the scheduler to monitor the node store's performance.
Holds a collection of configuration values.
Definition BasicConfig.h:24
pointer data()
Definition base_uint.h:101
T empty(T... args)
T is_same_v
void registerNuDBFactory(Manager &manager)
std::pair< void const *, std::size_t > nodeobject_compress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:190
Status
Return codes from Backend operations.
std::pair< void const *, std::size_t > nodeobject_decompress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:86
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::error_code make_error_code(xrpl::TokenCodecErrc e)
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
@ open
We haven't closed our ledger yet, but others might have.
bool get_if_exists(Section const &section, std::string const &name, T &v)
@ no
Definition Steps.h:24
T push_back(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
T str(T... args)
Contains information about a batch write operation.
T what(T... args)