rippled
Loading...
Searching...
No Matches
NuDBFactory.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpl/basics/contract.h>
21#include <xrpl/beast/core/LexicalCast.h>
22#include <xrpl/beast/utility/instrumentation.h>
23#include <xrpl/nodestore/Factory.h>
24#include <xrpl/nodestore/Manager.h>
25#include <xrpl/nodestore/detail/DecodedBlob.h>
26#include <xrpl/nodestore/detail/EncodedBlob.h>
27#include <xrpl/nodestore/detail/codec.h>
28
29#include <boost/filesystem.hpp>
30
31#include <nudb/nudb.hpp>
32
33#include <chrono>
34#include <cstdint>
35#include <cstdio>
36#include <exception>
37#include <memory>
38
39namespace ripple {
40namespace NodeStore {
41
42class NuDBBackend : public Backend
43{
44public:
45 // "appnum" is an application-defined constant stored in the header of a
46 // NuDB database. We used it to identify shard databases before that code
47 // was removed. For now, its only use is a sanity check that the database
48 // was created by xrpld.
49 static constexpr std::uint64_t appnum = 1;
50
52 size_t const keyBytes_;
56 nudb::store db_;
59
61 size_t keyBytes,
62 Section const& keyValues,
64 Scheduler& scheduler,
65 beast::Journal journal)
66 : j_(journal)
67 , keyBytes_(keyBytes)
69 , name_(get(keyValues, "path"))
70 , blockSize_(parseBlockSize(name_, keyValues, journal))
71 , deletePath_(false)
72 , scheduler_(scheduler)
73 {
74 if (name_.empty())
75 Throw<std::runtime_error>(
76 "nodestore: Missing path in NuDB backend");
77 }
78
80 size_t keyBytes,
81 Section const& keyValues,
83 Scheduler& scheduler,
84 nudb::context& context,
85 beast::Journal journal)
86 : j_(journal)
87 , keyBytes_(keyBytes)
89 , name_(get(keyValues, "path"))
90 , blockSize_(parseBlockSize(name_, keyValues, journal))
91 , db_(context)
92 , deletePath_(false)
93 , scheduler_(scheduler)
94 {
95 if (name_.empty())
96 Throw<std::runtime_error>(
97 "nodestore: Missing path in NuDB backend");
98 }
99
100 ~NuDBBackend() override
101 {
102 try
103 {
104 // close can throw and we don't want the destructor to throw.
105 close();
106 }
107 catch (nudb::system_error const&)
108 {
109 // Don't allow exceptions to propagate out of destructors.
110 // close() has already logged the error.
111 }
112 }
113
115 getName() override
116 {
117 return name_;
118 }
119
121 getBlockSize() const override
122 {
123 return blockSize_;
124 }
125
126 void
127 open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt)
128 override
129 {
130 using namespace boost::filesystem;
131 if (db_.is_open())
132 {
133 // LCOV_EXCL_START
134 UNREACHABLE(
135 "ripple::NodeStore::NuDBBackend::open : database is already "
136 "open");
137 JLOG(j_.error()) << "database is already open";
138 return;
139 // LCOV_EXCL_STOP
140 }
141 auto const folder = path(name_);
142 auto const dp = (folder / "nudb.dat").string();
143 auto const kp = (folder / "nudb.key").string();
144 auto const lp = (folder / "nudb.log").string();
145 nudb::error_code ec;
146 if (createIfMissing)
147 {
148 create_directories(folder);
149 nudb::create<nudb::xxhasher>(
150 dp,
151 kp,
152 lp,
153 appType,
154 uid,
155 salt,
156 keyBytes_,
158 0.50,
159 ec);
160 if (ec == nudb::errc::file_exists)
161 ec = {};
162 if (ec)
163 Throw<nudb::system_error>(ec);
164 }
165 db_.open(dp, kp, lp, ec);
166 if (ec)
167 Throw<nudb::system_error>(ec);
168
169 if (db_.appnum() != appnum)
170 Throw<std::runtime_error>("nodestore: unknown appnum");
171 db_.set_burst(burstSize_);
172 }
173
174 bool
175 isOpen() override
176 {
177 return db_.is_open();
178 }
179
180 void
181 open(bool createIfMissing) override
182 {
183 open(createIfMissing, appnum, nudb::make_uid(), nudb::make_salt());
184 }
185
186 void
187 close() override
188 {
189 if (db_.is_open())
190 {
191 nudb::error_code ec;
192 db_.close(ec);
193 if (ec)
194 {
195 // Log to make sure the nature of the error gets to the user.
196 JLOG(j_.fatal()) << "NuBD close() failed: " << ec.message();
197 Throw<nudb::system_error>(ec);
198 }
199
200 if (deletePath_)
201 {
202 boost::filesystem::remove_all(name_, ec);
203 if (ec)
204 {
205 JLOG(j_.fatal()) << "Filesystem remove_all of " << name_
206 << " failed with: " << ec.message();
207 }
208 }
209 }
210 }
211
212 Status
213 fetch(void const* key, std::shared_ptr<NodeObject>* pno) override
214 {
215 Status status;
216 pno->reset();
217 nudb::error_code ec;
218 db_.fetch(
219 key,
220 [key, pno, &status](void const* data, std::size_t size) {
221 nudb::detail::buffer bf;
222 auto const result = nodeobject_decompress(data, size, bf);
223 DecodedBlob decoded(key, result.first, result.second);
224 if (!decoded.wasOk())
225 {
226 status = dataCorrupt;
227 return;
228 }
229 *pno = decoded.createObject();
230 status = ok;
231 },
232 ec);
233 if (ec == nudb::error::key_not_found)
234 return notFound;
235 if (ec)
236 Throw<nudb::system_error>(ec);
237 return status;
238 }
239
242 {
244 results.reserve(hashes.size());
245 for (auto const& h : hashes)
246 {
248 Status status = fetch(h->begin(), &nObj);
249 if (status != ok)
250 results.push_back({});
251 else
252 results.push_back(nObj);
253 }
254
255 return {results, ok};
256 }
257
258 void
260 {
261 EncodedBlob e(no);
262 nudb::error_code ec;
263 nudb::detail::buffer bf;
264 auto const result = nodeobject_compress(e.getData(), e.getSize(), bf);
265 db_.insert(e.getKey(), result.first, result.second, ec);
266 if (ec && ec != nudb::error::key_exists)
267 Throw<nudb::system_error>(ec);
268 }
269
270 void
272 {
273 BatchWriteReport report;
274 report.writeCount = 1;
275 auto const start = std::chrono::steady_clock::now();
276 do_insert(no);
277 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
279 scheduler_.onBatchWrite(report);
280 }
281
282 void
283 storeBatch(Batch const& batch) override
284 {
285 BatchWriteReport report;
286 report.writeCount = batch.size();
287 auto const start = std::chrono::steady_clock::now();
288 for (auto const& e : batch)
289 do_insert(e);
290 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
292 scheduler_.onBatchWrite(report);
293 }
294
295 void
296 sync() override
297 {
298 }
299
300 void
302 {
303 auto const dp = db_.dat_path();
304 auto const kp = db_.key_path();
305 auto const lp = db_.log_path();
306 // auto const appnum = db_.appnum();
307 nudb::error_code ec;
308 db_.close(ec);
309 if (ec)
310 Throw<nudb::system_error>(ec);
311 nudb::visit(
312 dp,
313 [&](void const* key,
314 std::size_t key_bytes,
315 void const* data,
316 std::size_t size,
317 nudb::error_code&) {
318 nudb::detail::buffer bf;
319 auto const result = nodeobject_decompress(data, size, bf);
320 DecodedBlob decoded(key, result.first, result.second);
321 if (!decoded.wasOk())
322 {
323 ec = make_error_code(nudb::error::missing_value);
324 return;
325 }
326 f(decoded.createObject());
327 },
328 nudb::no_progress{},
329 ec);
330 if (ec)
331 Throw<nudb::system_error>(ec);
332 db_.open(dp, kp, lp, ec);
333 if (ec)
334 Throw<nudb::system_error>(ec);
335 }
336
337 int
338 getWriteLoad() override
339 {
340 return 0;
341 }
342
343 void
344 setDeletePath() override
345 {
346 deletePath_ = true;
347 }
348
349 void
350 verify() override
351 {
352 auto const dp = db_.dat_path();
353 auto const kp = db_.key_path();
354 auto const lp = db_.log_path();
355 nudb::error_code ec;
356 db_.close(ec);
357 if (ec)
358 Throw<nudb::system_error>(ec);
359 nudb::verify_info vi;
360 nudb::verify<nudb::xxhasher>(vi, dp, kp, 0, nudb::no_progress{}, ec);
361 if (ec)
362 Throw<nudb::system_error>(ec);
363 db_.open(dp, kp, lp, ec);
364 if (ec)
365 Throw<nudb::system_error>(ec);
366 }
367
368 int
369 fdRequired() const override
370 {
371 return 3;
372 }
373
374private:
375 static std::size_t
377 std::string const& name,
378 Section const& keyValues,
379 beast::Journal journal)
380 {
381 using namespace boost::filesystem;
382 auto const folder = path(name);
383 auto const kp = (folder / "nudb.key").string();
384
385 std::size_t const defaultSize =
386 nudb::block_size(kp); // Default 4K from NuDB
387 std::size_t blockSize = defaultSize;
388 std::string blockSizeStr;
389
390 if (!get_if_exists(keyValues, "nudb_block_size", blockSizeStr))
391 {
392 return blockSize; // Early return with default
393 }
394
395 try
396 {
397 std::size_t const parsedBlockSize =
398 beast::lexicalCastThrow<std::size_t>(blockSizeStr);
399
400 // Validate: must be power of 2 between 4K and 32K
401 if (parsedBlockSize < 4096 || parsedBlockSize > 32768 ||
402 (parsedBlockSize & (parsedBlockSize - 1)) != 0)
403 {
405 s << "Invalid nudb_block_size: " << parsedBlockSize
406 << ". Must be power of 2 between 4096 and 32768.";
407 Throw<std::runtime_error>(s.str());
408 }
409
410 JLOG(journal.info())
411 << "Using custom NuDB block size: " << parsedBlockSize
412 << " bytes";
413 return parsedBlockSize;
414 }
415 catch (std::exception const& e)
416 {
418 s << "Invalid nudb_block_size value: " << blockSizeStr
419 << ". Error: " << e.what();
420 Throw<std::runtime_error>(s.str());
421 }
422 }
423};
424
425//------------------------------------------------------------------------------
426
427class NuDBFactory : public Factory
428{
429private:
431
432public:
433 explicit NuDBFactory(Manager& manager) : manager_(manager)
434 {
435 manager_.insert(*this);
436 }
437
439 getName() const override
440 {
441 return "NuDB";
442 }
443
446 size_t keyBytes,
447 Section const& keyValues,
449 Scheduler& scheduler,
450 beast::Journal journal) override
451 {
453 keyBytes, keyValues, burstSize, scheduler, journal);
454 }
455
458 size_t keyBytes,
459 Section const& keyValues,
461 Scheduler& scheduler,
462 nudb::context& context,
463 beast::Journal journal) override
464 {
466 keyBytes, keyValues, burstSize, scheduler, context, journal);
467 }
468};
469
470void
472{
473 static NuDBFactory instance{manager};
474}
475
476} // namespace NodeStore
477} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
Stream fatal() const
Definition Journal.h:352
Stream error() const
Definition Journal.h:346
Stream info() const
Definition Journal.h:334
A backend used for the NodeStore.
Definition Backend.h:40
Parsed key/value blob into NodeObject components.
Definition DecodedBlob.h:39
std::shared_ptr< NodeObject > createObject()
Create a NodeObject from this data.
bool wasOk() const noexcept
Determine if the decoding was successful.
Definition DecodedBlob.h:46
Convert a NodeObject from in-memory to database format.
Definition EncodedBlob.h:56
void const * getKey() const noexcept
std::size_t getSize() const noexcept
void const * getData() const noexcept
Base class for backend factories.
Definition Factory.h:36
Singleton for managing NodeStore factories and back ends.
Definition Manager.h:32
virtual void insert(Factory &factory)=0
Add a factory.
void store(std::shared_ptr< NodeObject > const &no) override
Store a single object.
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)
Status fetch(void const *key, std::shared_ptr< NodeObject > *pno) override
Fetch a single object.
void open(bool createIfMissing) override
Open the backend.
std::pair< std::vector< std::shared_ptr< NodeObject > >, Status > fetchBatch(std::vector< uint256 const * > const &hashes) override
Fetch a batch synchronously.
static std::size_t parseBlockSize(std::string const &name, Section const &keyValues, beast::Journal journal)
static constexpr std::uint64_t appnum
void close() override
Close the backend.
NuDBBackend(size_t keyBytes, Section const &keyValues, std::size_t burstSize, Scheduler &scheduler, nudb::context &context, beast::Journal journal)
void storeBatch(Batch const &batch) override
Store a group of objects.
void do_insert(std::shared_ptr< NodeObject > const &no)
std::optional< std::size_t > getBlockSize() const override
Get the block size for backends that support it.
int fdRequired() const override
Returns the number of file descriptors the backend expects to need.
std::string getName() override
Get the human-readable name of this backend.
void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override
Open the backend.
void verify() override
Perform consistency checks on database.
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_
bool isOpen() override
Returns true is the database is open.
int getWriteLoad() override
Estimate the number of write operations pending.
void setDeletePath() override
Remove contents on disk upon destruction.
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.
std::string getName() const override
Retrieve the name of this factory.
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.
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:45
T empty(T... args)
T is_same_v
void registerNuDBFactory(Manager &manager)
std::pair< void const *, std::size_t > nodeobject_decompress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:110
std::pair< void const *, std::size_t > nodeobject_compress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:221
Status
Return codes from Backend operations.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
bool get_if_exists(Section const &section, std::string const &name, T &v)
std::error_code make_error_code(ripple::TokenCodecErrc e)
@ open
We haven't closed our ledger yet, but others might have.
@ no
Definition Steps.h:45
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
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)