rippled
Loading...
Searching...
No Matches
SHAMapStoreImp.cpp
1#include <xrpld/app/ledger/TransactionMaster.h>
2#include <xrpld/app/misc/SHAMapStoreImp.h>
3#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
4#include <xrpld/core/ConfigSections.h>
5
6#include <xrpl/beast/core/CurrentThreadName.h>
7#include <xrpl/nodestore/Scheduler.h>
8#include <xrpl/nodestore/detail/DatabaseRotatingImp.h>
9#include <xrpl/server/NetworkOPs.h>
10#include <xrpl/server/State.h>
11#include <xrpl/shamap/SHAMapMissingNode.h>
12
13#include <boost/algorithm/string/predicate.hpp>
14
15namespace xrpl {
16void
18{
20 initStateDB(sqlDb_, config, dbName);
21}
22
30
33{
35
36 return xrpl::setCanDelete(sqlDb_, canDelete);
37}
38
46
47void
53
54void
60
61//------------------------------------------------------------------------------
62
64 Application& app,
65 NodeStore::Scheduler& scheduler,
66 beast::Journal journal)
67 : app_(app)
68 , scheduler_(scheduler)
69 , journal_(journal)
70 , working_(true)
71 , canDelete_(std::numeric_limits<LedgerIndex>::max())
72{
73 Config& config{app.config()};
74
75 Section& section{config.section(ConfigSection::nodeDatabase())};
76 if (section.empty())
77 {
78 Throw<std::runtime_error>(
79 "Missing [" + ConfigSection::nodeDatabase() + "] entry in configuration file");
80 }
81
82 // RocksDB only. Use sensible defaults if no values specified.
83 if (boost::iequals(get(section, "type"), "RocksDB"))
84 {
85 if (!section.exists("cache_mb"))
86 {
87 section.set("cache_mb", std::to_string(config.getValueFor(SizedItem::hashNodeDBCache)));
88 }
89
90 if (!section.exists("filter_bits") && (config.NODE_SIZE >= 2))
91 section.set("filter_bits", "10");
92 }
93
94 get_if_exists(section, "online_delete", deleteInterval_);
95
97 {
98 // Configuration that affects the behavior of online delete
99 get_if_exists(section, "delete_batch", deleteBatch_);
100 std::uint32_t temp;
101 if (get_if_exists(section, "back_off_milliseconds", temp) ||
102 // Included for backward compatibility with an undocumented setting
103 get_if_exists(section, "backOff", temp))
104 {
106 }
107 if (get_if_exists(section, "age_threshold_seconds", temp))
109 if (get_if_exists(section, "recovery_wait_seconds", temp))
111
112 get_if_exists(section, "advisory_delete", advisoryDelete_);
113
114 auto const minInterval =
116 if (deleteInterval_ < minInterval)
117 {
118 Throw<std::runtime_error>(
119 "online_delete must be at least " + std::to_string(minInterval));
120 }
121
122 if (config.LEDGER_HISTORY > deleteInterval_)
123 {
124 Throw<std::runtime_error>(
125 "online_delete must not be less than ledger_history "
126 "(currently " +
127 std::to_string(config.LEDGER_HISTORY) + ")");
128 }
129
130 state_db_.init(config, dbName_);
131 dbPaths();
132 }
133}
134
137{
140
141 if (deleteInterval_)
142 {
143 SavedState state = state_db_.getState();
144 auto writableBackend = makeBackendRotating(state.writableDb);
145 auto archiveBackend = makeBackendRotating(state.archiveDb);
146 if (!state.writableDb.size())
147 {
148 state.writableDb = writableBackend->getName();
149 state.archiveDb = archiveBackend->getName();
150 state_db_.setState(state);
151 }
152
153 // Create NodeStore with two backends to allow online deletion of
154 // data
157 readThreads,
158 std::move(writableBackend),
159 std::move(archiveBackend),
160 nscfg,
162 fdRequired_ += dbr->fdRequired();
163 dbRotating_ = dbr.get();
164 db.reset(dynamic_cast<NodeStore::Database*>(dbr.release()));
165 }
166 else
167 {
171 readThreads,
172 nscfg,
174 fdRequired_ += db->fdRequired();
175 }
176 return db;
177}
178
179void
181{
182 {
184 newLedger_ = ledger;
185 working_ = true;
186 }
188}
189
190void
192{
193 if (!working_)
194 return;
195
197 rendezvous_.wait(lock, [&] { return !working_; });
198}
199
200int
202{
203 return fdRequired_;
204}
205
206bool
208{
209 // Copy a single record from node to dbRotating_
212 if (!(++nodeCount % checkHealthInterval_))
213 {
214 if (healthWait() == stopping)
215 return false;
216 }
217
218 return true;
219}
220
221void
223{
224 beast::setCurrentThreadName("SHAMapStore");
226 netOPs_ = &app_.getOPs();
228
229 if (advisoryDelete_)
231
232 while (true)
233 {
234 healthy_ = true;
235 std::shared_ptr<Ledger const> validatedLedger;
236
237 {
239 working_ = false;
241 if (stop_)
242 {
243 return;
244 }
245 cond_.wait(lock);
246 if (newLedger_)
247 {
248 validatedLedger = std::move(newLedger_);
249 }
250 else
251 continue;
252 }
253
254 LedgerIndex const validatedSeq = validatedLedger->header().seq;
255 if (!lastRotated)
256 {
257 lastRotated = validatedSeq;
258 state_db_.setLastRotated(lastRotated);
259 }
260
261 bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ &&
262 canDelete_ >= lastRotated - 1 && healthWait() == keepGoing;
263
264 // will delete up to (not including) lastRotated
265 if (readyToRotate)
266 {
267 JLOG(journal_.warn()) << "rotating validatedSeq " << validatedSeq << " lastRotated "
268 << lastRotated << " deleteInterval " << deleteInterval_
269 << " canDelete_ " << canDelete_ << " state "
270 << app_.getOPs().strOperatingMode(false) << " age "
272
273 clearPrior(lastRotated);
274 if (healthWait() == stopping)
275 return;
276
277 JLOG(journal_.debug()) << "copying ledger " << validatedSeq;
278 std::uint64_t nodeCount = 0;
279
280 try
281 {
282 validatedLedger->stateMap().snapShot(false)->visitNodes(
283 std::bind(
285 this,
286 std::ref(nodeCount),
287 std::placeholders::_1));
288 }
289 catch (SHAMapMissingNode const& e)
290 {
291 JLOG(journal_.error())
292 << "Missing node while copying ledger before rotate: " << e.what();
293 continue;
294 }
295
296 if (healthWait() == stopping)
297 return;
298 // Only log if we completed without a "health" abort
299 JLOG(journal_.debug())
300 << "copied ledger " << validatedSeq << " nodecount " << nodeCount;
301
302 JLOG(journal_.debug()) << "freshening caches";
304 if (healthWait() == stopping)
305 return;
306 // Only log if we completed without a "health" abort
307 JLOG(journal_.debug()) << validatedSeq << " freshened caches";
308
309 JLOG(journal_.trace()) << "Making a new backend";
310 auto newBackend = makeBackendRotating();
311 JLOG(journal_.debug()) << validatedSeq << " new backend " << newBackend->getName();
312
313 clearCaches(validatedSeq);
314 if (healthWait() == stopping)
315 return;
316
317 lastRotated = validatedSeq;
318
320 std::move(newBackend),
321 [&](std::string const& writableName, std::string const& archiveName) {
322 SavedState savedState;
323 savedState.writableDb = writableName;
324 savedState.archiveDb = archiveName;
325 savedState.lastRotated = lastRotated;
326 state_db_.setState(savedState);
327
328 clearCaches(validatedSeq);
329 });
330
331 JLOG(journal_.warn()) << "finished rotation " << validatedSeq;
332 }
333 }
334}
335
336void
338{
340 boost::filesystem::path dbPath = get(section, "path");
341
342 if (boost::filesystem::exists(dbPath))
343 {
344 if (!boost::filesystem::is_directory(dbPath))
345 {
346 journal_.error() << "node db path must be a directory. " << dbPath.string();
347 Throw<std::runtime_error>("node db path must be a directory.");
348 }
349 }
350 else
351 {
352 boost::filesystem::create_directories(dbPath);
353 }
354
355 SavedState state = state_db_.getState();
356
357 {
358 auto update = [&dbPath](std::string& sPath) {
359 if (sPath.empty())
360 return false;
361
362 // Check if configured "path" matches stored directory path
363 using namespace boost::filesystem;
364 auto const stored{path(sPath)};
365 if (stored.parent_path() == dbPath)
366 return false;
367
368 sPath = (dbPath / stored.filename()).string();
369 return true;
370 };
371
372 if (update(state.writableDb))
373 {
374 update(state.archiveDb);
375 state_db_.setState(state);
376 }
377 }
378
379 bool writableDbExists = false;
380 bool archiveDbExists = false;
381
383 for (boost::filesystem::directory_iterator it(dbPath);
384 it != boost::filesystem::directory_iterator();
385 ++it)
386 {
387 if (!state.writableDb.compare(it->path().string()))
388 writableDbExists = true;
389 else if (!state.archiveDb.compare(it->path().string()))
390 archiveDbExists = true;
391 else if (!dbPrefix_.compare(it->path().stem().string()))
392 pathsToDelete.push_back(it->path());
393 }
394
395 if ((!writableDbExists && state.writableDb.size()) ||
396 (!archiveDbExists && state.archiveDb.size()) || (writableDbExists != archiveDbExists) ||
397 state.writableDb.empty() != state.archiveDb.empty())
398 {
399 boost::filesystem::path stateDbPathName = app_.config().legacy("database_path");
400 stateDbPathName /= dbName_;
401 stateDbPathName += "*";
402
403 journal_.error() << "state db error:\n"
404 << " writableDbExists " << writableDbExists << " archiveDbExists "
405 << archiveDbExists << '\n'
406 << " writableDb '" << state.writableDb << "' archiveDb '"
407 << state.archiveDb << "\n\n"
408 << "The existing data is in a corrupted state.\n"
409 << "To resume operation, remove the files matching "
410 << stateDbPathName.string() << " and contents of the directory "
411 << get(section, "path") << '\n'
412 << "Optionally, you can move those files to another\n"
413 << "location if you wish to analyze or back up the data.\n"
414 << "However, there is no guarantee that the data in its\n"
415 << "existing form is usable.";
416
417 Throw<std::runtime_error>("state db error");
418 }
419
420 // The necessary directories exist. Now, remove any others.
421 for (boost::filesystem::path& p : pathsToDelete)
422 boost::filesystem::remove_all(p);
423}
424
427{
429 boost::filesystem::path newPath;
430
431 if (path.size())
432 {
433 newPath = path;
434 }
435 else
436 {
437 boost::filesystem::path p = get(section, "path");
438 p /= dbPrefix_;
439 p += ".%%%%";
440 newPath = boost::filesystem::unique_path(p);
441 }
442 section.set("path", newPath.string());
443
445 section,
449 backend->open();
450 return backend;
451}
452
453void
455 LedgerIndex lastRotated,
456 std::string const& TableName,
457 std::function<std::optional<LedgerIndex>()> const& getMinSeq,
458 std::function<void(LedgerIndex)> const& deleteBeforeSeq)
459{
460 XRPL_ASSERT(deleteInterval_, "xrpl::SHAMapStoreImp::clearSql : nonzero delete interval");
462
463 {
464 JLOG(journal_.trace()) << "Begin: Look up lowest value of: " << TableName;
465 auto m = getMinSeq();
466 JLOG(journal_.trace()) << "End: Look up lowest value of: " << TableName;
467 if (!m)
468 return;
469 min = *m;
470 }
471
472 if (min > lastRotated || healthWait() == stopping)
473 return;
474 if (min == lastRotated)
475 {
476 // Micro-optimization mainly to clarify logs
477 JLOG(journal_.trace()) << "Nothing to delete from " << TableName;
478 return;
479 }
480
481 JLOG(journal_.debug()) << "start deleting in: " << TableName << " from " << min << " to "
482 << lastRotated;
483 while (min < lastRotated)
484 {
485 min = std::min(lastRotated, min + deleteBatch_);
486 JLOG(journal_.trace()) << "Begin: Delete up to " << deleteBatch_
487 << " rows with LedgerSeq < " << min << " from: " << TableName;
488 deleteBeforeSeq(min);
489 JLOG(journal_.trace()) << "End: Delete up to " << deleteBatch_ << " rows with LedgerSeq < "
490 << min << " from: " << TableName;
491 if (healthWait() == stopping)
492 return;
493 if (min < lastRotated)
495 if (healthWait() == stopping)
496 return;
497 }
498 JLOG(journal_.debug()) << "finished deleting from: " << TableName;
499}
500
501void
503{
505 // Also clear the FullBelowCache so its generation counter is bumped.
506 // This prevents stale "full below" markers from persisting across
507 // backend rotation/online deletion and interfering with SHAMap sync.
509}
510
511void
519
520void
522{
523 // Do not allow ledgers to be acquired from the network
524 // that are about to be deleted.
525 minimumOnline_ = lastRotated + 1;
526 JLOG(journal_.trace()) << "Begin: Clear internal ledgers up to " << lastRotated;
527 ledgerMaster_->clearPriorLedgers(lastRotated);
528 JLOG(journal_.trace()) << "End: Clear internal ledgers up to " << lastRotated;
529 if (healthWait() == stopping)
530 return;
531
532 auto& db = app_.getRelationalDatabase();
533
534 clearSql(
535 lastRotated,
536 "Ledgers",
537 [&db]() -> std::optional<LedgerIndex> { return db.getMinLedgerSeq(); },
538 [&db](LedgerIndex min) -> void { db.deleteBeforeLedgerSeq(min); });
539 if (healthWait() == stopping)
540 return;
541
542 if (!app_.config().useTxTables())
543 return;
544
545 clearSql(
546 lastRotated,
547 "Transactions",
548 [&db]() -> std::optional<LedgerIndex> { return db.getTransactionsMinLedgerSeq(); },
549 [&db](LedgerIndex min) -> void { db.deleteTransactionsBeforeLedgerSeq(min); });
550 if (healthWait() == stopping)
551 return;
552
553 clearSql(
554 lastRotated,
555 "AccountTransactions",
556 [&db]() -> std::optional<LedgerIndex> { return db.getAccountTransactionsMinLedgerSeq(); },
557 [&db](LedgerIndex min) -> void { db.deleteAccountTransactionsBeforeLedgerSeq(min); });
558 if (healthWait() == stopping)
559 return;
560}
561
564{
568 while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_))
569 {
570 lock.unlock();
571 JLOG(journal_.warn()) << "Waiting " << recoveryWaitTime_.count()
572 << "s for node to stabilize. state: "
573 << app_.getOPs().strOperatingMode(mode, false) << ". age "
574 << age.count() << 's';
577 mode = netOPs_->getOperatingMode();
578 lock.lock();
579 }
580
581 return stop_ ? stopping : keepGoing;
582}
583
584void
586{
587 if (thread_.joinable())
588 {
589 {
591 stop_ = true;
593 }
594 thread_.join();
595 }
596}
597
600{
601 // minimumOnline_ with 0 value is equivalent to unknown/not set.
602 // Don't attempt to acquire ledgers if that value is unknown.
604 return minimumOnline_.load();
605 return app_.getLedgerMaster().minSqlSeq();
606}
607
608//------------------------------------------------------------------------------
609
612{
613 return std::make_unique<SHAMapStoreImp>(app, scheduler, journal);
614}
615
616} // namespace xrpl
T bind(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
virtual Config & config()=0
Holds unparsed configuration information.
void legacy(std::string const &section, std::string value)
Set a value that is not a key/value pair.
Section & section(std::string const &name)
Returns the section with the given name.
bool useTxTables() const
Definition Config.h:318
int getValueFor(SizedItem item, std::optional< std::size_t > node=std::nullopt) const
Retrieve the default value for the item at the specified node size.
Definition Config.cpp:1041
virtual std::shared_ptr< TreeNodeCache > getTreeNodeCache()=0
Return a pointer to the Family Tree Node Cache.
virtual std::shared_ptr< FullBelowCache > getFullBelowCache()=0
Return a pointer to the Family Full Below Cache.
std::optional< LedgerIndex > minSqlSeq()
std::chrono::seconds getValidatedLedgerAge()
void clearPriorLedgers(LedgerIndex seq)
void clearLedgerCachePrior(LedgerIndex seq)
beast::Journal journal(std::string const &name)
Definition Log.cpp:134
virtual OperatingMode getOperatingMode() const =0
virtual std::string strOperatingMode(OperatingMode const mode, bool const admin=false) const =0
virtual void rotate(std::unique_ptr< NodeStore::Backend > &&newBackend, std::function< void(std::string const &writableName, std::string const &archiveName)> const &f)=0
Rotates the backends.
Persistency layer for NodeObject.
Definition Database.h:31
std::shared_ptr< NodeObject > fetchNodeObject(uint256 const &hash, std::uint32_t ledgerSeq=0, FetchType fetchType=FetchType::synchronous, bool duplicate=false)
Fetch a node object.
Definition Database.cpp:209
static Manager & instance()
Returns the instance of the manager singleton.
virtual std::unique_ptr< Database > make_Database(std::size_t burstSize, Scheduler &scheduler, int readThreads, Section const &backendParameters, beast::Journal journal)=0
Construct a NodeStore database.
virtual std::unique_ptr< Backend > make_Backend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
Scheduling for asynchronous backend activity.
uint256 const & as_uint256() const
Definition SHAMapHash.h:24
void setState(SavedState const &state)
void init(BasicConfig const &config, std::string const &dbName)
LedgerIndex setCanDelete(LedgerIndex canDelete)
void clearSql(LedgerIndex lastRotated, std::string const &TableName, std::function< std::optional< LedgerIndex >()> const &getMinSeq, std::function< void(LedgerIndex)> const &deleteBeforeSeq)
delete from sqlite table in batches to not lock the db excessively.
bool copyNode(std::uint64_t &nodeCount, SHAMapTreeNode const &node)
std::unique_ptr< NodeStore::Backend > makeBackendRotating(std::string path=std::string())
std::atomic< bool > working_
std::condition_variable cond_
std::uint32_t deleteBatch_
std::atomic< LedgerIndex > minimumOnline_
std::chrono::seconds recoveryWaitTime_
If the node is out of sync during an online_delete healthWait() call, sleep the thread for this time,...
std::optional< LedgerIndex > minimumOnline() const override
The minimum ledger to try and maintain in our database.
static constexpr auto nodeStoreName_
std::uint32_t deleteInterval_
int fdRequired() const override
Returns the number of file descriptors that are needed.
static std::uint32_t const minimumDeletionInterval_
std::unique_ptr< NodeStore::Database > makeNodeStore(int readThreads) override
std::chrono::milliseconds backOff_
std::atomic< LedgerIndex > canDelete_
NodeStore::DatabaseRotating * dbRotating_
std::shared_ptr< Ledger const > newLedger_
std::string const dbName_
std::condition_variable rendezvous_
static std::uint32_t const minimumDeletionIntervalSA_
std::uint64_t const checkHealthInterval_
HealthResult healthWait()
void clearCaches(LedgerIndex validatedSeq)
std::chrono::seconds ageThreshold_
void rendezvous() const override
LedgerMaster * ledgerMaster_
beast::Journal const journal_
std::string const dbPrefix_
NodeStore::Scheduler & scheduler_
HealthResult
This is a health check for online deletion that waits until rippled is stable before returning.
void onLedgerClosed(std::shared_ptr< Ledger const > const &ledger) override
Called by LedgerMaster every time a ledger validates.
bool freshenCache(CacheInstance &cache)
void clearPrior(LedgerIndex lastRotated)
SHAMapStoreImp(Application &app, NodeStore::Scheduler &scheduler, beast::Journal journal)
SHAMapHash const & getHash() const
Return the hash of this node.
Holds a collection of configuration values.
Definition BasicConfig.h:24
virtual Logs & logs()=0
virtual RelationalDatabase & getRelationalDatabase()=0
virtual NetworkOPs & getOPs()=0
virtual TransactionMaster & getMasterTransaction()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual Family & getNodeFamily()=0
TaggedCache< uint256, Transaction > & getCache()
T compare(T... args)
T count(T... args)
T empty(T... args)
T is_same_v
T join(T... args)
T joinable(T... args)
T max(T... args)
T min(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void initStateDB(soci::session &session, BasicConfig const &config, std::string const &dbName)
initStateDB Opens a session with the State database.
Definition State.cpp:6
void setSavedState(soci::session &session, SavedState const &state)
setSavedState Saves the given state.
Definition State.cpp:83
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
void setLastRotated(soci::session &session, LedgerIndex seq)
setLastRotated Updates the last rotated ledger sequence.
Definition State.cpp:94
std::unique_ptr< SHAMapStore > make_SHAMapStore(Application &app, NodeStore::Scheduler &scheduler, beast::Journal journal)
SavedState getSavedState(soci::session &session)
getSavedState Returns the saved state.
Definition State.cpp:72
constexpr auto megabytes(T value) noexcept
bool get_if_exists(Section const &section, std::string const &name, T &v)
LedgerIndex setCanDelete(soci::session &session, LedgerIndex canDelete)
setCanDelete Updates the ledger sequence which can be deleted.
Definition State.cpp:64
OperatingMode
Specifies the mode under which the server believes it's operating.
Definition NetworkOPs.h:50
@ FULL
we have the ledger and can even validate
LedgerIndex getCanDelete(soci::session &session)
getCanDelete Returns the ledger sequence which can be deleted.
Definition State.cpp:55
T push_back(T... args)
T ref(T... args)
T reset(T... args)
T size(T... args)
T sleep_for(T... args)
static std::string nodeDatabase()
std::string writableDb
Definition State.h:13
LedgerIndex lastRotated
Definition State.h:15
std::string archiveDb
Definition State.h:14
T to_string(T... args)
T what(T... args)