rippled
Loading...
Searching...
No Matches
SHAMapStoreImp.cpp
1#include <xrpld/app/ledger/TransactionMaster.h>
2#include <xrpld/app/misc/NetworkOPs.h>
3#include <xrpld/app/misc/SHAMapStoreImp.h>
4#include <xrpld/app/rdb/State.h>
5#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
6#include <xrpld/core/ConfigSections.h>
7
8#include <xrpl/beast/core/CurrentThreadName.h>
9#include <xrpl/nodestore/Scheduler.h>
10#include <xrpl/nodestore/detail/DatabaseRotatingImp.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 : app_(app)
65 , scheduler_(scheduler)
66 , journal_(journal)
67 , working_(true)
68 , canDelete_(std::numeric_limits<LedgerIndex>::max())
69{
70 Config& config{app.config()};
71
72 Section& section{config.section(ConfigSection::nodeDatabase())};
73 if (section.empty())
74 {
75 Throw<std::runtime_error>("Missing [" + ConfigSection::nodeDatabase() + "] entry in configuration file");
76 }
77
78 // RocksDB only. Use sensible defaults if no values specified.
79 if (boost::iequals(get(section, "type"), "RocksDB"))
80 {
81 if (!section.exists("cache_mb"))
82 {
83 section.set("cache_mb", std::to_string(config.getValueFor(SizedItem::hashNodeDBCache)));
84 }
85
86 if (!section.exists("filter_bits") && (config.NODE_SIZE >= 2))
87 section.set("filter_bits", "10");
88 }
89
90 get_if_exists(section, "online_delete", deleteInterval_);
91
93 {
94 // Configuration that affects the behavior of online delete
95 get_if_exists(section, "delete_batch", deleteBatch_);
96 std::uint32_t temp;
97 if (get_if_exists(section, "back_off_milliseconds", temp) ||
98 // Included for backward compatibility with an undocumented setting
99 get_if_exists(section, "backOff", temp))
100 {
102 }
103 if (get_if_exists(section, "age_threshold_seconds", temp))
105 if (get_if_exists(section, "recovery_wait_seconds", temp))
107
108 get_if_exists(section, "advisory_delete", advisoryDelete_);
109
110 auto const minInterval = config.standalone() ? minimumDeletionIntervalSA_ : minimumDeletionInterval_;
111 if (deleteInterval_ < minInterval)
112 {
113 Throw<std::runtime_error>("online_delete must be at least " + std::to_string(minInterval));
114 }
115
116 if (config.LEDGER_HISTORY > deleteInterval_)
117 {
118 Throw<std::runtime_error>(
119 "online_delete must not be less than ledger_history "
120 "(currently " +
121 std::to_string(config.LEDGER_HISTORY) + ")");
122 }
123
124 state_db_.init(config, dbName_);
125 dbPaths();
126 }
127}
128
131{
134
135 if (deleteInterval_)
136 {
137 SavedState state = state_db_.getState();
138 auto writableBackend = makeBackendRotating(state.writableDb);
139 auto archiveBackend = makeBackendRotating(state.archiveDb);
140 if (!state.writableDb.size())
141 {
142 state.writableDb = writableBackend->getName();
143 state.archiveDb = archiveBackend->getName();
144 state_db_.setState(state);
145 }
146
147 // Create NodeStore with two backends to allow online deletion of
148 // data
151 readThreads,
152 std::move(writableBackend),
153 std::move(archiveBackend),
154 nscfg,
156 fdRequired_ += dbr->fdRequired();
157 dbRotating_ = dbr.get();
158 db.reset(dynamic_cast<NodeStore::Database*>(dbr.release()));
159 }
160 else
161 {
165 readThreads,
166 nscfg,
168 fdRequired_ += db->fdRequired();
169 }
170 return db;
171}
172
173void
175{
176 {
178 newLedger_ = ledger;
179 working_ = true;
180 }
182}
183
184void
186{
187 if (!working_)
188 return;
189
191 rendezvous_.wait(lock, [&] { return !working_; });
192}
193
194int
196{
197 return fdRequired_;
198}
199
200bool
202{
203 // Copy a single record from node to dbRotating_
205 if (!(++nodeCount % checkHealthInterval_))
206 {
207 if (healthWait() == stopping)
208 return false;
209 }
210
211 return true;
212}
213
214void
216{
217 beast::setCurrentThreadName("SHAMapStore");
219 netOPs_ = &app_.getOPs();
221
222 if (advisoryDelete_)
224
225 while (true)
226 {
227 healthy_ = true;
228 std::shared_ptr<Ledger const> validatedLedger;
229
230 {
232 working_ = false;
234 if (stop_)
235 {
236 return;
237 }
238 cond_.wait(lock);
239 if (newLedger_)
240 {
241 validatedLedger = std::move(newLedger_);
242 }
243 else
244 continue;
245 }
246
247 LedgerIndex const validatedSeq = validatedLedger->header().seq;
248 if (!lastRotated)
249 {
250 lastRotated = validatedSeq;
251 state_db_.setLastRotated(lastRotated);
252 }
253
254 bool const readyToRotate =
255 validatedSeq >= lastRotated + deleteInterval_ && canDelete_ >= lastRotated - 1 && healthWait() == keepGoing;
256
257 // will delete up to (not including) lastRotated
258 if (readyToRotate)
259 {
260 JLOG(journal_.warn()) << "rotating validatedSeq " << validatedSeq << " lastRotated " << lastRotated
261 << " deleteInterval " << deleteInterval_ << " canDelete_ " << canDelete_ << " state "
262 << app_.getOPs().strOperatingMode(false) << " age "
264
265 clearPrior(lastRotated);
266 if (healthWait() == stopping)
267 return;
268
269 JLOG(journal_.debug()) << "copying ledger " << validatedSeq;
270 std::uint64_t nodeCount = 0;
271
272 try
273 {
274 validatedLedger->stateMap().snapShot(false)->visitNodes(
275 std::bind(&SHAMapStoreImp::copyNode, this, std::ref(nodeCount), std::placeholders::_1));
276 }
277 catch (SHAMapMissingNode const& e)
278 {
279 JLOG(journal_.error()) << "Missing node while copying ledger before rotate: " << e.what();
280 continue;
281 }
282
283 if (healthWait() == stopping)
284 return;
285 // Only log if we completed without a "health" abort
286 JLOG(journal_.debug()) << "copied ledger " << validatedSeq << " nodecount " << nodeCount;
287
288 JLOG(journal_.debug()) << "freshening caches";
290 if (healthWait() == stopping)
291 return;
292 // Only log if we completed without a "health" abort
293 JLOG(journal_.debug()) << validatedSeq << " freshened caches";
294
295 JLOG(journal_.trace()) << "Making a new backend";
296 auto newBackend = makeBackendRotating();
297 JLOG(journal_.debug()) << validatedSeq << " new backend " << newBackend->getName();
298
299 clearCaches(validatedSeq);
300 if (healthWait() == stopping)
301 return;
302
303 lastRotated = validatedSeq;
304
306 std::move(newBackend), [&](std::string const& writableName, std::string const& archiveName) {
307 SavedState savedState;
308 savedState.writableDb = writableName;
309 savedState.archiveDb = archiveName;
310 savedState.lastRotated = lastRotated;
311 state_db_.setState(savedState);
312
313 clearCaches(validatedSeq);
314 });
315
316 JLOG(journal_.warn()) << "finished rotation " << validatedSeq;
317 }
318 }
319}
320
321void
323{
325 boost::filesystem::path dbPath = get(section, "path");
326
327 if (boost::filesystem::exists(dbPath))
328 {
329 if (!boost::filesystem::is_directory(dbPath))
330 {
331 journal_.error() << "node db path must be a directory. " << dbPath.string();
332 Throw<std::runtime_error>("node db path must be a directory.");
333 }
334 }
335 else
336 {
337 boost::filesystem::create_directories(dbPath);
338 }
339
340 SavedState state = state_db_.getState();
341
342 {
343 auto update = [&dbPath](std::string& sPath) {
344 if (sPath.empty())
345 return false;
346
347 // Check if configured "path" matches stored directory path
348 using namespace boost::filesystem;
349 auto const stored{path(sPath)};
350 if (stored.parent_path() == dbPath)
351 return false;
352
353 sPath = (dbPath / stored.filename()).string();
354 return true;
355 };
356
357 if (update(state.writableDb))
358 {
359 update(state.archiveDb);
360 state_db_.setState(state);
361 }
362 }
363
364 bool writableDbExists = false;
365 bool archiveDbExists = false;
366
368 for (boost::filesystem::directory_iterator it(dbPath); it != boost::filesystem::directory_iterator(); ++it)
369 {
370 if (!state.writableDb.compare(it->path().string()))
371 writableDbExists = true;
372 else if (!state.archiveDb.compare(it->path().string()))
373 archiveDbExists = true;
374 else if (!dbPrefix_.compare(it->path().stem().string()))
375 pathsToDelete.push_back(it->path());
376 }
377
378 if ((!writableDbExists && state.writableDb.size()) || (!archiveDbExists && state.archiveDb.size()) ||
379 (writableDbExists != archiveDbExists) || state.writableDb.empty() != state.archiveDb.empty())
380 {
381 boost::filesystem::path stateDbPathName = app_.config().legacy("database_path");
382 stateDbPathName /= dbName_;
383 stateDbPathName += "*";
384
385 journal_.error() << "state db error:\n"
386 << " writableDbExists " << writableDbExists << " archiveDbExists " << archiveDbExists << '\n'
387 << " writableDb '" << state.writableDb << "' archiveDb '" << state.archiveDb << "\n\n"
388 << "The existing data is in a corrupted state.\n"
389 << "To resume operation, remove the files matching " << stateDbPathName.string()
390 << " and contents of the directory " << get(section, "path") << '\n'
391 << "Optionally, you can move those files to another\n"
392 << "location if you wish to analyze or back up the data.\n"
393 << "However, there is no guarantee that the data in its\n"
394 << "existing form is usable.";
395
396 Throw<std::runtime_error>("state db error");
397 }
398
399 // The necessary directories exist. Now, remove any others.
400 for (boost::filesystem::path& p : pathsToDelete)
401 boost::filesystem::remove_all(p);
402}
403
406{
408 boost::filesystem::path newPath;
409
410 if (path.size())
411 {
412 newPath = path;
413 }
414 else
415 {
416 boost::filesystem::path p = get(section, "path");
417 p /= dbPrefix_;
418 p += ".%%%%";
419 newPath = boost::filesystem::unique_path(p);
420 }
421 section.set("path", newPath.string());
422
424 section,
428 backend->open();
429 return backend;
430}
431
432void
434 LedgerIndex lastRotated,
435 std::string const& TableName,
436 std::function<std::optional<LedgerIndex>()> const& getMinSeq,
437 std::function<void(LedgerIndex)> const& deleteBeforeSeq)
438{
439 XRPL_ASSERT(deleteInterval_, "xrpl::SHAMapStoreImp::clearSql : nonzero delete interval");
441
442 {
443 JLOG(journal_.trace()) << "Begin: Look up lowest value of: " << TableName;
444 auto m = getMinSeq();
445 JLOG(journal_.trace()) << "End: Look up lowest value of: " << TableName;
446 if (!m)
447 return;
448 min = *m;
449 }
450
451 if (min > lastRotated || healthWait() == stopping)
452 return;
453 if (min == lastRotated)
454 {
455 // Micro-optimization mainly to clarify logs
456 JLOG(journal_.trace()) << "Nothing to delete from " << TableName;
457 return;
458 }
459
460 JLOG(journal_.debug()) << "start deleting in: " << TableName << " from " << min << " to " << lastRotated;
461 while (min < lastRotated)
462 {
463 min = std::min(lastRotated, min + deleteBatch_);
464 JLOG(journal_.trace()) << "Begin: Delete up to " << deleteBatch_ << " rows with LedgerSeq < " << min
465 << " from: " << TableName;
466 deleteBeforeSeq(min);
467 JLOG(journal_.trace()) << "End: Delete up to " << deleteBatch_ << " rows with LedgerSeq < " << min
468 << " from: " << TableName;
469 if (healthWait() == stopping)
470 return;
471 if (min < lastRotated)
473 if (healthWait() == stopping)
474 return;
475 }
476 JLOG(journal_.debug()) << "finished deleting from: " << TableName;
477}
478
479void
481{
483 // Also clear the FullBelowCache so its generation counter is bumped.
484 // This prevents stale "full below" markers from persisting across
485 // backend rotation/online deletion and interfering with SHAMap sync.
487}
488
489void
497
498void
500{
501 // Do not allow ledgers to be acquired from the network
502 // that are about to be deleted.
503 minimumOnline_ = lastRotated + 1;
504 JLOG(journal_.trace()) << "Begin: Clear internal ledgers up to " << lastRotated;
505 ledgerMaster_->clearPriorLedgers(lastRotated);
506 JLOG(journal_.trace()) << "End: Clear internal ledgers up to " << lastRotated;
507 if (healthWait() == stopping)
508 return;
509
510 SQLiteDatabase* const db = dynamic_cast<SQLiteDatabase*>(&app_.getRelationalDatabase());
511
512 if (!db)
513 Throw<std::runtime_error>("Failed to get relational database");
514
515 clearSql(
516 lastRotated,
517 "Ledgers",
518 [db]() -> std::optional<LedgerIndex> { return db->getMinLedgerSeq(); },
519 [db](LedgerIndex min) -> void { db->deleteBeforeLedgerSeq(min); });
520 if (healthWait() == stopping)
521 return;
522
523 if (!app_.config().useTxTables())
524 return;
525
526 clearSql(
527 lastRotated,
528 "Transactions",
529 [&db]() -> std::optional<LedgerIndex> { return db->getTransactionsMinLedgerSeq(); },
530 [&db](LedgerIndex min) -> void { db->deleteTransactionsBeforeLedgerSeq(min); });
531 if (healthWait() == stopping)
532 return;
533
534 clearSql(
535 lastRotated,
536 "AccountTransactions",
538 [&db](LedgerIndex min) -> void { db->deleteAccountTransactionsBeforeLedgerSeq(min); });
539 if (healthWait() == stopping)
540 return;
541}
542
545{
549 while (!stop_ && (mode != OperatingMode::FULL || age > ageThreshold_))
550 {
551 lock.unlock();
552 JLOG(journal_.warn()) << "Waiting " << recoveryWaitTime_.count()
553 << "s for node to stabilize. state: " << app_.getOPs().strOperatingMode(mode, false)
554 << ". age " << age.count() << 's';
557 mode = netOPs_->getOperatingMode();
558 lock.lock();
559 }
560
561 return stop_ ? stopping : keepGoing;
562}
563
564void
566{
567 if (thread_.joinable())
568 {
569 {
571 stop_ = true;
573 }
574 thread_.join();
575 }
576}
577
580{
581 // minimumOnline_ with 0 value is equivalent to unknown/not set.
582 // Don't attempt to acquire ledgers if that value is unknown.
584 return minimumOnline_.load();
585 return app_.getLedgerMaster().minSqlSeq();
586}
587
588//------------------------------------------------------------------------------
589
592{
593 return std::make_unique<SHAMapStoreImp>(app, scheduler, journal);
594}
595
596} // namespace xrpl
T bind(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:318
Stream debug() const
Definition Journal.h:300
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
Stream warn() const
Definition Journal.h:312
virtual Config & config()=0
virtual Logs & logs()=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:317
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:1015
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:202
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.
virtual std::optional< LedgerIndex > getMinLedgerSeq()=0
getMinLedgerSeq Returns the minimum ledger sequence in the Ledgers table.
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.
virtual std::optional< LedgerIndex > getTransactionsMinLedgerSeq()=0
getTransactionsMinLedgerSeq Returns the minimum ledger sequence stored in the Transactions table.
virtual void deleteAccountTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq)=0
deleteAccountTransactionsBeforeLedgerSeq Deletes all account transactions with a sequence number less...
virtual void deleteBeforeLedgerSeq(LedgerIndex ledgerSeq)=0
deleteBeforeLedgerSeq Deletes all ledgers with a sequence number less than or equal to the given ledg...
virtual std::optional< LedgerIndex > getAccountTransactionsMinLedgerSeq()=0
getAccountTransactionsMinLedgerSeq Returns the minimum ledger sequence stored in the AccountTransacti...
virtual void deleteTransactionsBeforeLedgerSeq(LedgerIndex ledgerSeq)=0
deleteTransactionsBeforeLedgerSeq Deletes all transactions with a sequence number less than or equal ...
Holds a collection of configuration values.
Definition BasicConfig.h:24
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
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:93
void setSavedState(soci::session &session, SavedState const &state)
setSavedState Saves the given state.
Definition State.cpp:82
void initStateDB(soci::session &session, BasicConfig const &config, std::string const &dbName)
initStateDB Opens a session with the State database.
Definition State.cpp:6
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:71
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:48
@ 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:15
LedgerIndex lastRotated
Definition State.h:17
std::string archiveDb
Definition State.h:16
T to_string(T... args)
T what(T... args)