20 #include <ripple/app/ledger/InboundLedgers.h>
21 #include <ripple/app/ledger/LedgerMaster.h>
22 #include <ripple/app/misc/NetworkOPs.h>
23 #include <ripple/basics/ByteUtilities.h>
24 #include <ripple/basics/chrono.h>
25 #include <ripple/basics/random.h>
26 #include <ripple/core/ConfigSections.h>
27 #include <ripple/nodestore/DummyScheduler.h>
28 #include <ripple/nodestore/impl/DatabaseShardImp.h>
29 #include <ripple/overlay/Overlay.h>
30 #include <ripple/overlay/predicates.h>
31 #include <ripple/protocol/HashPrefix.h>
33 #include <boost/algorithm/string/predicate.hpp>
56 , avgShardFileSz_(ledgersPerShard_ *
kilobytes(192))
72 JLOG(
j_.
error()) <<
"already initialized";
78 JLOG(
j_.
error()) <<
"invalid configuration file settings";
84 using namespace boost::filesystem;
87 if (!is_directory(
dir_))
89 JLOG(
j_.
error()) <<
"'path' must be a directory";
94 create_directories(
dir_);
96 ctx_ = std::make_unique<nudb::context>();
100 for (
auto const& d : directory_iterator(
dir_))
102 if (!is_directory(d))
106 auto dirName = d.path().stem().string();
107 if (!
std::all_of(dirName.begin(), dirName.end(), [](
auto c) {
108 return ::isdigit(static_cast<unsigned char>(c));
117 JLOG(
j_.
error()) <<
"shard " << shardIndex
118 <<
" comes before earliest shard index "
128 JLOG(
j_.
warn()) <<
"shard " << shardIndex
129 <<
" previously failed import, removing";
130 remove_all(shardDir);
135 std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
138 if (!shard->isLegacy())
142 shard->removeOnDestroy();
144 <<
"shard " << shardIndex <<
" removed, legacy shard";
148 if (shard->isFinal())
154 else if (shard->isBackendComplete())
156 auto const result{
shards_.emplace(
166 <<
"more than one shard being acquired";
180 <<
"exception " << e.
what() <<
" in function " << __func__;
192 boost::optional<std::uint32_t>
195 boost::optional<std::uint32_t> shardIndex;
204 return it->second.shard->prepare();
215 JLOG(
j_.
debug()) <<
"maximum storage size reached";
221 JLOG(
j_.
error()) <<
"insufficient storage space available";
231 JLOG(
j_.
debug()) <<
"no new shards to add";
239 auto shard{std::make_unique<Shard>(
app_, *
this, *shardIndex,
j_)};
243 auto const seq{shard->prepare()};
258 JLOG(j.error()) <<
"shard " << shardIndex <<
" " << msg;
265 return fail(
"cannot be stored at this time");
270 "comes before earliest shard index " +
279 return fail(
"has an invalid index");
290 JLOG(
j_.
debug()) <<
"shard " << shardIndex
291 <<
" is already stored or queued for import";
297 return fail(
"maximum storage size reached");
299 return fail(
"insufficient storage space available");
311 if (
auto const it{
shards_.find(shardIndex)};
340 boost::filesystem::path
const& srcDir)
342 using namespace boost::filesystem;
345 if (!is_directory(srcDir) || is_empty(srcDir))
347 JLOG(
j_.
error()) <<
"invalid source directory " << srcDir.string();
353 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
358 auto renameDir = [&](path
const& src, path
const& dst) {
366 <<
"exception " << e.
what() <<
" in function " << __func__;
378 if (
auto const it{
shards_.find(shardIndex)}; it ==
shards_.end() ||
381 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
389 if (!renameDir(srcDir, dstDir))
393 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
394 if (!shard->open(
scheduler_, *
ctx_) || !shard->isBackendComplete())
396 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
398 renameDir(dstDir, srcDir);
403 auto const it{
shards_.find(shardIndex)};
404 if (it ==
shards_.end() || it->second.shard ||
407 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" failed to import";
411 it->second.shard = std::move(shard);
427 shardInfo = it->second;
434 switch (shardInfo.
state)
439 if (shardInfo.
shard->containsLedger(seq))
447 auto nObj{
fetch(hash, seq)};
456 auto ledger{std::make_shared<Ledger>(
461 if (ledger->info().seq != seq)
466 if (ledger->info().hash != hash)
469 "encountered invalid ledger hash " +
to_string(hash) +
474 if (!ledger->stateMap().fetchRoot(
475 SHAMapHash{ledger->info().accountHash},
nullptr))
478 "is missing root STATE node on hash " +
to_string(hash) +
482 if (ledger->info().txHash.isNonZero())
484 if (!ledger->txMap().fetchRoot(
488 "is missing root TXN node on hash " +
to_string(hash) +
498 if (ledger->info().hash.isZero())
500 JLOG(
j_.
error()) <<
"zero ledger hash for ledger sequence "
501 << ledger->info().seq;
504 if (ledger->info().accountHash.isZero())
506 JLOG(
j_.
error()) <<
"zero account hash for ledger sequence "
507 << ledger->info().seq;
510 if (ledger->stateMap().getHash().isNonZero() &&
511 !ledger->stateMap().isValid())
513 JLOG(
j_.
error()) <<
"invalid state map for ledger sequence "
514 << ledger->info().seq;
517 if (ledger->info().txHash.isNonZero() && !ledger->txMap().isValid())
519 JLOG(
j_.
error()) <<
"invalid transaction map for ledger sequence "
520 << ledger->info().seq;
533 <<
"shard " << shardIndex <<
" is not being acquired";
538 shard = it->second.shard;
542 <<
"shard " << shardIndex <<
" is not being acquired";
578 for (
auto const& e : shards)
580 if (
auto shard{e.lock()}; shard)
581 shard->finalize(
true);
600 e.second.shard->stop();
615 JLOG(
j_.
error()) <<
"invalid source database";
622 auto loadLedger = [&](
bool ascendSort =
623 true) -> boost::optional<std::uint32_t> {
627 "WHERE LedgerSeq >= " +
629 " order by LedgerSeq " + (ascendSort ?
"asc" :
"desc") +
633 if (!ledger || seq == 0)
635 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
636 " the SQLite database to import";
643 auto seq{loadLedger()};
653 seq = loadLedger(
false);
662 if (latestIndex < earliestIndex)
664 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
665 " the SQLite database to import";
672 shardIndex <= latestIndex;
677 JLOG(
j_.
error()) <<
"maximum storage size reached";
683 JLOG(
j_.
error()) <<
"insufficient storage space available";
692 JLOG(
j_.
debug()) <<
"shard " << shardIndex <<
" already exists";
701 auto const numLedgers{
705 if (ledgerHashes.size() != numLedgers)
711 if (!source.
fetch(ledgerHashes[n].first, n))
713 JLOG(
j_.
warn()) <<
"SQLite ledger sequence " << n
714 <<
" mismatches node store";
725 auto shard{std::make_unique<Shard>(
app_, *
this, shardIndex,
j_)};
736 JLOG(
j_.
error()) <<
"shard " << shardIndex
737 <<
" failed to create temp marker file";
738 shard->removeOnDestroy();
746 boost::optional<uint256> lastLedgerHash;
748 while (
auto seq = shard->prepare())
751 if (!ledger || ledger->info().seq != seq)
764 if (!shard->store(ledger))
768 lastLedgerHash = ledger->info().hash;
770 recentStored = ledger;
773 using namespace boost::filesystem;
774 if (lastLedgerHash && shard->isBackendComplete())
781 s.
add256(*lastLedgerHash);
787 shard->getBackend()->store(nObj);
791 remove_all(markerFile);
793 JLOG(
j_.
debug()) <<
"shard " << shardIndex
794 <<
" was successfully imported";
796 auto const result{
shards_.emplace(
804 <<
" in function " << __func__;
805 shard->removeOnDestroy();
811 <<
"shard " << shardIndex <<
" failed to import";
812 shard->removeOnDestroy();
831 shard = it->second.shard;
836 return shard->getBackend()->getWriteLoad();
855 <<
"shard " << shardIndex <<
" is not being acquired";
860 shard = it->second.shard;
864 <<
"shard " << shardIndex <<
" is not being acquired";
869 auto [backend, pCache, nCache] = shard->getBackendAll();
872 pCache->canonicalize_replace_cache(hash, nObj);
873 backend->store(nObj);
884 return doFetch(hash, seq, *cache.first, *cache.second,
false);
898 object = cache.first->fetch(hash);
899 if (
object || cache.second->touch_if_exists(hash))
910 auto const seq{srcLedger->info().seq};
920 <<
"shard " << shardIndex <<
" is not being acquired";
925 shard = it->second.shard;
929 <<
"shard " << shardIndex <<
" is not being acquired";
934 if (shard->containsLedger(seq))
936 JLOG(
j_.
trace()) <<
"shard " << shardIndex <<
" ledger already stored";
941 auto [backend, pCache, nCache] = shard->getBackendAll();
943 *srcLedger, backend, pCache, nCache,
nullptr))
961 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end() &&
965 shard = it->second.shard;
983 shard = it->second.shard;
988 return shard->pCache()->getHitRate();
1007 for (
auto const& e : shards)
1009 if (
auto shard{e.lock()}; shard)
1034 get_if_exists<std::uint32_t>(
1035 section,
"earliest_seq", shardDBEarliestSeq);
1038 get_if_exists<std::uint32_t>(
1043 if (shardDBEarliestSeq != nodeDBEarliestSeq)
1047 "] define different 'earliest_seq' values");
1051 using namespace boost::filesystem;
1052 if (!get_if_exists<path>(section,
"path",
dir_))
1053 return fail(
"'path' missing");
1057 if (!get_if_exists<std::uint64_t>(section,
"max_size_gb", sz))
1058 return fail(
"'max_size_gb' missing");
1060 if ((sz << 30) < sz)
1061 return fail(
"'max_size_gb' overflow");
1065 return fail(
"'max_size_gb' must be at least 10");
1071 if (section.exists(
"ledgers_per_shard"))
1074 if (!config.standalone())
1075 return fail(
"'ledgers_per_shard' only honored in stand alone");
1079 return fail(
"'ledgers_per_shard' must be a multiple of 256");
1083 backendName_ = get<std::string>(section,
"type",
"nudb");
1085 return fail(
"'type' value unsupported");
1099 if (
auto const it{
shards_.find(shardIndex)};
1100 it !=
shards_.end() && it->second.shard)
1102 shard = it->second.shard;
1111 boost::optional<std::uint32_t>
1119 auto const maxShardIndex{[
this, validLedgerSeq]() {
1128 if (
shards_.size() >= maxNumShards)
1131 if (maxShardIndex < 1024 ||
1132 static_cast<float>(
shards_.size()) / maxNumShards > 0.5f)
1140 shardIndex <= maxShardIndex;
1160 for (
int i = 0; i < 40; ++i)
1177 assert(shardInfo.
shard);
1179 assert(shardInfo.
shard->isBackendComplete());
1182 auto const shardIndex{shardInfo.
shard->index()};
1185 taskQueue_->addTask([
this, shardIndex, writeSQLite]() {
1192 if (
auto const it{
shards_.find(shardIndex)}; it !=
shards_.end())
1193 shard = it->second.shard;
1196 JLOG(
j_.
error()) <<
"Unable to finalize shard " << shardIndex;
1201 if (!shard->finalize(writeSQLite))
1213 shard->removeOnDestroy();
1224 auto const it{
shards_.find(shardIndex)};
1237 protocol::TMPeerShardInfo message;
1239 message.set_nodepubkey(publicKey.data(), publicKey.size());
1242 message, protocol::mtPEER_SHARD_INFO)));
1266 for (
auto const& e : shards)
1268 if (
auto shard{e.lock()}; shard)
1270 auto [sz, fd] = shard->fileInfo();
1284 JLOG(
j_.
warn()) <<
"maximum storage size reached";
1290 <<
"maximum shard store size exceeds available storage space";
1302 rs.insert(e.second.shard->index());
1318 if (
auto const it{
shards_.find(shardIndex)};
1319 it !=
shards_.end() && it->second.shard)
1321 shard = it->second.shard;
1329 std::tie(std::ignore, pCache, nCache) = shard->getBackendAll();
1339 return boost::filesystem::space(
dir_).available;
1343 JLOG(
j_.
error()) <<
"exception " << e.
what() <<
" in function "
1356 if (!shard->store(ledger))
1361 shards_.erase(shard->index());
1369 shard->removeOnDestroy();
1373 else if (shard->isBackendComplete())
1377 if (
auto const it{
shards_.find(shard->index())}; it !=
shards_.end())
1388 <<
"shard " << shard->index() <<
" is no longer being acquired";
1409 if (section.empty())
1412 return std::make_unique<DatabaseShardImp>(
1413 app, parent,
"ShardStore", scheduler, readThreads, j);