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 <xrpld/nodestore/Factory.h>
21#include <xrpld/nodestore/Manager.h>
22#include <xrpld/nodestore/detail/DecodedBlob.h>
23#include <xrpld/nodestore/detail/EncodedBlob.h>
24#include <xrpld/nodestore/detail/codec.h>
25
26#include <xrpl/basics/contract.h>
27#include <xrpl/beast/core/LexicalCast.h>
28#include <xrpl/beast/utility/instrumentation.h>
29
30#include <boost/filesystem.hpp>
31
32#include <nudb/nudb.hpp>
33
34#include <chrono>
35#include <cstdint>
36#include <cstdio>
37#include <exception>
38#include <memory>
39
40namespace ripple {
41namespace NodeStore {
42
43class NuDBBackend : public Backend
44{
45public:
46 // "appnum" is an application-defined constant stored in the header of a
47 // NuDB database. We used it to identify shard databases before that code
48 // was removed. For now, its only use is a sanity check that the database
49 // was created by xrpld.
50 static constexpr std::uint64_t appnum = 1;
51
53 size_t const keyBytes_;
57 nudb::store db_;
60
62 size_t keyBytes,
63 Section const& keyValues,
65 Scheduler& scheduler,
66 beast::Journal journal)
67 : j_(journal)
68 , keyBytes_(keyBytes)
70 , name_(get(keyValues, "path"))
71 , blockSize_(parseBlockSize(name_, keyValues, journal))
72 , deletePath_(false)
73 , scheduler_(scheduler)
74 {
75 if (name_.empty())
76 Throw<std::runtime_error>(
77 "nodestore: Missing path in NuDB backend");
78 }
79
81 size_t keyBytes,
82 Section const& keyValues,
84 Scheduler& scheduler,
85 nudb::context& context,
86 beast::Journal journal)
87 : j_(journal)
88 , keyBytes_(keyBytes)
90 , name_(get(keyValues, "path"))
91 , blockSize_(parseBlockSize(name_, keyValues, journal))
92 , db_(context)
93 , deletePath_(false)
94 , scheduler_(scheduler)
95 {
96 if (name_.empty())
97 Throw<std::runtime_error>(
98 "nodestore: Missing path in NuDB backend");
99 }
100
101 ~NuDBBackend() override
102 {
103 try
104 {
105 // close can throw and we don't want the destructor to throw.
106 close();
107 }
108 catch (nudb::system_error const&)
109 {
110 // Don't allow exceptions to propagate out of destructors.
111 // close() has already logged the error.
112 }
113 }
114
116 getName() override
117 {
118 return name_;
119 }
120
122 getBlockSize() const override
123 {
124 return blockSize_;
125 }
126
127 void
128 open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt)
129 override
130 {
131 using namespace boost::filesystem;
132 if (db_.is_open())
133 {
134 // LCOV_EXCL_START
135 UNREACHABLE(
136 "ripple::NodeStore::NuDBBackend::open : database is already "
137 "open");
138 JLOG(j_.error()) << "database is already open";
139 return;
140 // LCOV_EXCL_STOP
141 }
142 auto const folder = path(name_);
143 auto const dp = (folder / "nudb.dat").string();
144 auto const kp = (folder / "nudb.key").string();
145 auto const lp = (folder / "nudb.log").string();
146 nudb::error_code ec;
147 if (createIfMissing)
148 {
149 create_directories(folder);
150 nudb::create<nudb::xxhasher>(
151 dp,
152 kp,
153 lp,
154 appType,
155 uid,
156 salt,
157 keyBytes_,
159 0.50,
160 ec);
161 if (ec == nudb::errc::file_exists)
162 ec = {};
163 if (ec)
164 Throw<nudb::system_error>(ec);
165 }
166 db_.open(dp, kp, lp, ec);
167 if (ec)
168 Throw<nudb::system_error>(ec);
169
170 if (db_.appnum() != appnum)
171 Throw<std::runtime_error>("nodestore: unknown appnum");
172 db_.set_burst(burstSize_);
173 }
174
175 bool
176 isOpen() override
177 {
178 return db_.is_open();
179 }
180
181 void
182 open(bool createIfMissing) override
183 {
184 open(createIfMissing, appnum, nudb::make_uid(), nudb::make_salt());
185 }
186
187 void
188 close() override
189 {
190 if (db_.is_open())
191 {
192 nudb::error_code ec;
193 db_.close(ec);
194 if (ec)
195 {
196 // Log to make sure the nature of the error gets to the user.
197 JLOG(j_.fatal()) << "NuBD close() failed: " << ec.message();
198 Throw<nudb::system_error>(ec);
199 }
200
201 if (deletePath_)
202 {
203 boost::filesystem::remove_all(name_, ec);
204 if (ec)
205 {
206 JLOG(j_.fatal()) << "Filesystem remove_all of " << name_
207 << " failed with: " << ec.message();
208 }
209 }
210 }
211 }
212
213 Status
214 fetch(void const* key, std::shared_ptr<NodeObject>* pno) override
215 {
216 Status status;
217 pno->reset();
218 nudb::error_code ec;
219 db_.fetch(
220 key,
221 [key, pno, &status](void const* data, std::size_t size) {
222 nudb::detail::buffer bf;
223 auto const result = nodeobject_decompress(data, size, bf);
224 DecodedBlob decoded(key, result.first, result.second);
225 if (!decoded.wasOk())
226 {
227 status = dataCorrupt;
228 return;
229 }
230 *pno = decoded.createObject();
231 status = ok;
232 },
233 ec);
234 if (ec == nudb::error::key_not_found)
235 return notFound;
236 if (ec)
237 Throw<nudb::system_error>(ec);
238 return status;
239 }
240
243 {
245 results.reserve(hashes.size());
246 for (auto const& h : hashes)
247 {
249 Status status = fetch(h->begin(), &nObj);
250 if (status != ok)
251 results.push_back({});
252 else
253 results.push_back(nObj);
254 }
255
256 return {results, ok};
257 }
258
259 void
261 {
262 EncodedBlob e(no);
263 nudb::error_code ec;
264 nudb::detail::buffer bf;
265 auto const result = nodeobject_compress(e.getData(), e.getSize(), bf);
266 db_.insert(e.getKey(), result.first, result.second, ec);
267 if (ec && ec != nudb::error::key_exists)
268 Throw<nudb::system_error>(ec);
269 }
270
271 void
273 {
274 BatchWriteReport report;
275 report.writeCount = 1;
276 auto const start = std::chrono::steady_clock::now();
277 do_insert(no);
278 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
280 scheduler_.onBatchWrite(report);
281 }
282
283 void
284 storeBatch(Batch const& batch) override
285 {
286 BatchWriteReport report;
287 report.writeCount = batch.size();
288 auto const start = std::chrono::steady_clock::now();
289 for (auto const& e : batch)
290 do_insert(e);
291 report.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
293 scheduler_.onBatchWrite(report);
294 }
295
296 void
297 sync() override
298 {
299 }
300
301 void
303 {
304 auto const dp = db_.dat_path();
305 auto const kp = db_.key_path();
306 auto const lp = db_.log_path();
307 // auto const appnum = db_.appnum();
308 nudb::error_code ec;
309 db_.close(ec);
310 if (ec)
311 Throw<nudb::system_error>(ec);
312 nudb::visit(
313 dp,
314 [&](void const* key,
315 std::size_t key_bytes,
316 void const* data,
317 std::size_t size,
318 nudb::error_code&) {
319 nudb::detail::buffer bf;
320 auto const result = nodeobject_decompress(data, size, bf);
321 DecodedBlob decoded(key, result.first, result.second);
322 if (!decoded.wasOk())
323 {
324 ec = make_error_code(nudb::error::missing_value);
325 return;
326 }
327 f(decoded.createObject());
328 },
329 nudb::no_progress{},
330 ec);
331 if (ec)
332 Throw<nudb::system_error>(ec);
333 db_.open(dp, kp, lp, ec);
334 if (ec)
335 Throw<nudb::system_error>(ec);
336 }
337
338 int
339 getWriteLoad() override
340 {
341 return 0;
342 }
343
344 void
345 setDeletePath() override
346 {
347 deletePath_ = true;
348 }
349
350 void
351 verify() override
352 {
353 auto const dp = db_.dat_path();
354 auto const kp = db_.key_path();
355 auto const lp = db_.log_path();
356 nudb::error_code ec;
357 db_.close(ec);
358 if (ec)
359 Throw<nudb::system_error>(ec);
360 nudb::verify_info vi;
361 nudb::verify<nudb::xxhasher>(vi, dp, kp, 0, nudb::no_progress{}, ec);
362 if (ec)
363 Throw<nudb::system_error>(ec);
364 db_.open(dp, kp, lp, ec);
365 if (ec)
366 Throw<nudb::system_error>(ec);
367 }
368
369 int
370 fdRequired() const override
371 {
372 return 3;
373 }
374
375private:
376 static std::size_t
378 std::string const& name,
379 Section const& keyValues,
380 beast::Journal journal)
381 {
382 using namespace boost::filesystem;
383 auto const folder = path(name);
384 auto const kp = (folder / "nudb.key").string();
385
386 std::size_t const defaultSize =
387 nudb::block_size(kp); // Default 4K from NuDB
388 std::size_t blockSize = defaultSize;
389 std::string blockSizeStr;
390
391 if (!get_if_exists(keyValues, "nudb_block_size", blockSizeStr))
392 {
393 return blockSize; // Early return with default
394 }
395
396 try
397 {
398 std::size_t const parsedBlockSize =
399 beast::lexicalCastThrow<std::size_t>(blockSizeStr);
400
401 // Validate: must be power of 2 between 4K and 32K
402 if (parsedBlockSize < 4096 || parsedBlockSize > 32768 ||
403 (parsedBlockSize & (parsedBlockSize - 1)) != 0)
404 {
406 s << "Invalid nudb_block_size: " << parsedBlockSize
407 << ". Must be power of 2 between 4096 and 32768.";
408 Throw<std::runtime_error>(s.str());
409 }
410
411 JLOG(journal.info())
412 << "Using custom NuDB block size: " << parsedBlockSize
413 << " bytes";
414 return parsedBlockSize;
415 }
416 catch (std::exception const& e)
417 {
419 s << "Invalid nudb_block_size value: " << blockSizeStr
420 << ". Error: " << e.what();
421 Throw<std::runtime_error>(s.str());
422 }
423 }
424};
425
426//------------------------------------------------------------------------------
427
428class NuDBFactory : public Factory
429{
430public:
432 {
433 Manager::instance().insert(*this);
434 }
435
436 ~NuDBFactory() override
437 {
438 Manager::instance().erase(*this);
439 }
440
442 getName() const override
443 {
444 return "NuDB";
445 }
446
449 size_t keyBytes,
450 Section const& keyValues,
452 Scheduler& scheduler,
453 beast::Journal journal) override
454 {
456 keyBytes, keyValues, burstSize, scheduler, journal);
457 }
458
461 size_t keyBytes,
462 Section const& keyValues,
464 Scheduler& scheduler,
465 nudb::context& context,
466 beast::Journal journal) override
467 {
469 keyBytes, keyValues, burstSize, scheduler, context, journal);
470 }
471};
472
474
475} // namespace NodeStore
476} // 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:57
void const * getKey() const noexcept
std::size_t getSize() const noexcept
void const * getData() const noexcept
Base class for backend factories.
Definition Factory.h:37
static Manager & instance()
Returns the instance of the manager singleton.
virtual void erase(Factory &factory)=0
Remove a factory.
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
static NuDBFactory nuDBFactory
std::pair< void const *, std::size_t > nodeobject_decompress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:111
std::pair< void const *, std::size_t > nodeobject_compress(void const *in, std::size_t in_size, BufferFactory &&bf)
Definition codec.h:222
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)