20 #include <ripple/app/ledger/InboundLedgers.h>
21 #include <ripple/app/ledger/LedgerMaster.h>
22 #include <ripple/app/misc/NetworkOPs.h>
23 #include <ripple/app/rdb/backend/RelationalDBInterfaceSqlite.h>
24 #include <ripple/basics/ByteUtilities.h>
25 #include <ripple/basics/RangeSet.h>
26 #include <ripple/basics/chrono.h>
27 #include <ripple/basics/random.h>
28 #include <ripple/core/ConfigSections.h>
29 #include <ripple/nodestore/DummyScheduler.h>
30 #include <ripple/nodestore/impl/DatabaseShardImp.h>
31 #include <ripple/overlay/Overlay.h>
32 #include <ripple/overlay/predicates.h>
33 #include <ripple/protocol/HashPrefix.h>
34 #include <ripple/protocol/digest.h>
36 #include <boost/algorithm/string/predicate.hpp>
39 #include <sys/statvfs.h>
57 , avgShardFileSz_(ledgersPerShard_ *
kilobytes(192ull))
63 Throw<std::runtime_error>(
64 "Attempted to create DatabaseShardImp in reporting mode. Reporting "
65 "does not support shards. Remove shards info from config");
76 JLOG(
j_.
error()) <<
"already initialized";
82 JLOG(
j_.
error()) <<
"invalid configuration file settings";
88 using namespace boost::filesystem;
95 for (
auto const& path : paths)
99 if (!is_directory(path))
101 JLOG(
j_.
error()) << path <<
" must be a directory";
105 else if (!create_directories(path))
108 <<
"failed to create path: " + path.string();
120 ctx_ = std::make_unique<nudb::context>();
125 for (
auto const& path : paths)
127 for (
auto const& it : directory_iterator(path))
130 if (!is_directory(it))
134 auto const shardDir{it.path()};
135 auto dirName{shardDir.stem().string()};
137 dirName.begin(), dirName.end(), [](
auto c) {
138 return ::isdigit(static_cast<unsigned char>(c));
149 <<
"shard " << shardIndex
150 <<
" ignored, comes before earliest shard index "
159 <<
"shard " << shardIndex
160 <<
" previously failed database import, removing";
161 remove_all(shardDir);
165 auto shard{std::make_shared<Shard>(
166 app_, *
this, shardIndex, shardDir.parent_path(),
j_)};
170 shard->removeOnDestroy();
172 <<
"shard " << shardIndex <<
" removed, "
173 << (shard->isLegacy() ?
"legacy" :
"corrupted")
178 switch (shard->getState())
183 shards_.emplace(shardIndex, std::move(shard));
188 shards_.emplace(shardIndex, std::move(shard))
198 <<
"more than one shard being acquired";
202 shards_.emplace(shardIndex, std::move(shard));
208 <<
"shard " << shardIndex <<
" invalid state";
216 JLOG(
j_.
fatal()) <<
"Exception caught in function " << __func__
217 <<
". Error: " << e.
what();
240 return it->second->prepare();
255 JLOG(
j_.
debug()) <<
"no new shards to add";
263 auto const pathDesignation = [
this, shardIndex = *shardIndex]() {
268 if (!pathDesignation)
271 auto const needsHistoricalPath =
274 auto shard = [
this, shardIndex, needsHistoricalPath] {
276 return std::make_unique<Shard>(
287 auto const ledgerSeq{shard->prepare()};
290 shards_.emplace(*shardIndex, std::move(shard));
301 auto fail = [j =
j_, &shardIndexes](
304 auto multipleIndexPrequel = [&shardIndexes] {
307 shardIndexes.
begin(),
309 indexesAsString.
begin(),
310 [](uint32_t
const index) { return std::to_string(index); });
313 (shardIndexes.
size() > 1 ?
"s " :
" ") +
314 boost::algorithm::join(indexesAsString,
", ");
317 JLOG(j.error()) << (shardIndex ?
"shard " +
std::to_string(*shardIndex)
318 : multipleIndexPrequel())
323 if (shardIndexes.
empty())
324 return fail(
"invalid shard indexes");
330 return fail(
"cannot be stored at this time");
332 auto historicalShardsToPrepare = 0;
334 for (
auto const shardIndex : shardIndexes)
339 "comes before earliest shard index " +
350 return fail(
"invalid index", shardIndex);
357 return fail(
"invalid index", shardIndex);
361 return fail(
"is already stored", shardIndex);
365 "is already queued for import from the shard archive handler",
372 if (shard->index() == shardIndex)
374 "is being imported from the nodestore", shardIndex);
381 ++historicalShardsToPrepare;
388 return fail(
"maximum number of historical shards reached");
390 if (historicalShardsToPrepare)
395 return fail(
"insufficient storage space available");
398 if (
auto const recentShardsToPrepare =
399 shardIndexes.size() - historicalShardsToPrepare;
400 recentShardsToPrepare)
405 return fail(
"insufficient storage space available");
408 for (
auto const shardIndex : shardIndexes)
434 rs.insert(shardIndex);
446 boost::filesystem::path
const& srcDir)
450 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" " << msg;
458 using namespace boost::filesystem;
461 if (!is_directory(srcDir) || is_empty(srcDir))
464 "invalid source directory " + srcDir.string(),
471 std::string(
". Exception caught in function ") + __func__ +
472 ". Error: " + e.
what(),
485 return fail(
"already exists", lock);
489 return fail(
"was not prepared for import", lock);
491 auto const pathDesignation{
493 if (!pathDesignation)
494 return fail(
"failed to import", lock);
503 auto renameDir = [&, fname = __func__](path
const& src, path
const& dst) {
511 std::string(
". Exception caught in function ") + fname +
512 ". Error: " + e.
what(),
519 if (!renameDir(srcDir, dstDir))
523 auto shard{std::make_unique<Shard>(
524 app_, *
this, shardIndex, dstDir.parent_path(),
j_)};
530 renameDir(dstDir, srcDir);
534 auto const [it, inserted] = [&]() {
537 return shards_.emplace(shardIndex, std::move(shard));
543 renameDir(dstDir, srcDir);
561 auto const it{
shards_.find(shardIndex)};
568 switch (shard->getState())
573 if (shard->containsLedger(ledgerSeq))
586 JLOG(
j_.
error()) <<
"shard " << shardIndex <<
" " << msg;
590 auto ledger{std::make_shared<Ledger>(
595 if (ledger->info().seq != ledgerSeq)
598 "encountered invalid ledger sequence " +
std::to_string(ledgerSeq));
600 if (ledger->info().hash != hash)
603 "encountered invalid ledger hash " +
to_string(hash) +
608 if (!ledger->stateMap().fetchRoot(
609 SHAMapHash{ledger->info().accountHash},
nullptr))
612 "is missing root STATE node on hash " +
to_string(hash) +
616 if (ledger->info().txHash.isNonZero())
618 if (!ledger->txMap().fetchRoot(
622 "is missing root TXN node on hash " +
to_string(hash) +
632 auto const ledgerSeq{ledger->info().seq};
633 if (ledger->info().hash.isZero())
635 JLOG(
j_.
error()) <<
"zero ledger hash for ledger sequence "
639 if (ledger->info().accountHash.isZero())
641 JLOG(
j_.
error()) <<
"zero account hash for ledger sequence "
645 if (ledger->stateMap().getHash().isNonZero() &&
646 !ledger->stateMap().isValid())
648 JLOG(
j_.
error()) <<
"invalid state map for ledger sequence "
652 if (ledger->info().txHash.isNonZero() && !ledger->txMap().isValid())
654 JLOG(
j_.
error()) <<
"invalid transaction map for ledger sequence "
668 <<
"shard " << shardIndex <<
" is not being acquired";
672 auto const it{
shards_.find(shardIndex)};
676 <<
"shard " << shardIndex <<
" is not being acquired";
682 if (shard->containsLedger(ledgerSeq))
684 JLOG(
j_.
trace()) <<
"shard " << shardIndex <<
" ledger already stored";
707 for (
auto const& [_, shard] :
shards_)
717 for (
auto const& wptr : shards)
719 if (
auto const shard{wptr.lock()})
721 JLOG(
j_.
warn()) <<
" shard " << shard->index() <<
" unexpired";
753 JLOG(
j_.
error()) <<
"database import already in progress";
788 ledgerSeq = info->seq;
790 if (!ledger || ledgerSeq == 0)
792 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
793 " the SQLite database to import";
804 auto const earliestIndex = [&] {
811 return earliestIndex;
815 auto const latestLedgerSeq = loadLedger(
"desc");
816 if (!latestLedgerSeq)
819 auto const latestIndex = [&] {
829 if (latestIndex < earliestIndex)
831 JLOG(
j_.
error()) <<
"no suitable ledgers were found in"
832 " the SQLite database to import";
836 JLOG(
j_.
debug()) <<
"Importing ledgers for shards " << earliestIndex
837 <<
" through " << latestIndex;
844 earliestIndex, latestIndex, 0);
848 for (
std::uint32_t shardIndex = earliestIndex; shardIndex <= latestIndex;
854 auto const pathDesignation = [
this, shardIndex] {
858 auto const pathDesignation =
861 return pathDesignation;
864 if (!pathDesignation)
874 <<
"shard " << shardIndex <<
" already being acquired";
882 <<
"shard " << shardIndex <<
" already being imported";
889 JLOG(
j_.
debug()) <<
"shard " << shardIndex <<
" already stored";
900 auto const ledgerHashes{
903 if (ledgerHashes.size() !=
maxLedgers(shardIndex))
911 if (!source.fetchNodeObject(ledgerHashes.at(n).ledgerHash, n))
913 JLOG(
j_.
warn()) <<
"SQLite ledger sequence " << n
914 <<
" mismatches node store";
926 bool const needsHistoricalPath =
929 auto const path = needsHistoricalPath
934 auto shard{std::make_shared<Shard>(
app_, *
this, shardIndex, path,
j_)};
957 JLOG(
j_.
error()) <<
"shard " << shardIndex
958 <<
" failed to create temp marker file";
959 shard->removeOnDestroy();
968 while (
auto const ledgerSeq = shard->prepare())
974 if (!ledger || ledger->info().seq != ledgerSeq)
977 auto const result{shard->storeLedger(ledger, recentStored)};
982 if (!shard->setLedgerStored(ledger))
985 if (!lastLedgerHash && ledgerSeq == lastSeq)
986 lastLedgerHash = ledger->info().hash;
988 recentStored = std::move(ledger);
994 using namespace boost::filesystem;
1007 if (shard->storeNodeObject(nodeObject))
1015 remove_all(markerFile);
1017 JLOG(
j_.
debug()) <<
"shard " << shardIndex
1018 <<
" was successfully imported"
1019 " from the NodeStore";
1021 shards_.emplace(shardIndex, std::move(shard))
1035 JLOG(
j_.
fatal()) <<
"shard index " << shardIndex
1036 <<
". Exception caught in function "
1037 << __func__ <<
". Error: " << e.
what();
1044 JLOG(
j_.
error()) <<
"shard " << shardIndex
1045 <<
" failed to import from the NodeStore";
1046 shard->removeOnDestroy();
1075 return shard->getWriteLoad();
1092 <<
"shard " << shardIndex <<
" is not being acquired";
1096 auto const it{
shards_.find(shardIndex)};
1100 <<
"shard " << shardIndex <<
" is not being acquired";
1106 auto const nodeObject{
1108 if (shard->storeNodeObject(nodeObject))
1115 auto const ledgerSeq{srcLedger->info().seq};
1125 <<
"shard " << shardIndex <<
" is not being acquired";
1129 auto const it{
shards_.find(shardIndex)};
1133 <<
"shard " << shardIndex <<
" is not being acquired";
1139 auto const result{shard->storeLedger(srcLedger,
nullptr)};
1141 if (result.error || result.count == 0 || result.size == 0)
1163 for (
auto const& weak : shards)
1165 if (
auto const shard{weak.lock()}; shard && shard->isOpen())
1174 JLOG(
j_.
trace()) <<
"Open shards exceed configured limit of "
1185 return lhsShard->getLastUse() < rhsShard->getLastUse();
1188 for (
auto it{openFinals.
cbegin()};
1191 if ((*it)->tryClose())
1192 it = openFinals.
erase(it);
1215 currentShard[jss::storedSeqs] = shard->getStoredSeqs();
1217 ret[jss::currentShard] = currentShard;
1220 ret =
"Database import not running";
1249 get_if_exists<std::uint32_t>(section, name, shardDBValue);
1252 get_if_exists<std::uint32_t>(
1255 return shardDBValue == nodeDBValue;
1264 "ledgers_per_shard" +
"' values");
1270 "earliest_seq" +
"' values");
1273 using namespace boost::filesystem;
1274 if (!get_if_exists<path>(section,
"path",
dir_))
1275 return fail(
"'path' missing");
1280 Section const& historicalShardPaths =
1281 config.section(SECTION_HISTORICAL_SHARD_PATHS);
1283 auto values = historicalShardPaths.
values();
1285 std::sort(values.begin(), values.end());
1286 values.erase(
std::unique(values.begin(), values.end()), values.end());
1288 for (
auto const& s : values)
1290 auto const dir = path(s);
1294 "the 'path' cannot also be in the "
1295 "'historical_shard_path' section");
1303 backendName_ = get<std::string>(section,
"type",
"nudb");
1305 return fail(
"'type' value unsupported");
1320 auto const it{
shards_.find(shardIndex)};
1326 return shard->fetchNodeObject(hash, fetchReport);
1335 return std::nullopt;
1337 auto const maxShardIndex{[
this, validLedgerSeq]() {
1346 if (
shards_.size() >= maxNumShards)
1347 return std::nullopt;
1349 if (maxShardIndex < 1024 ||
1350 static_cast<float>(
shards_.size()) / maxNumShards > 0.5f)
1368 return std::nullopt;
1380 for (
int i = 0; i < 40; ++i)
1391 return std::nullopt;
1397 bool const writeSQLite,
1407 auto shard{wptr.lock()};
1410 JLOG(
j_.
debug()) <<
"Shard removed before being finalized";
1414 if (!shard->finalize(writeSQLite, expectedHash))
1431 if (shard->index() < boundaryIndex)
1435 shard->getDir().parent_path() ==
dir_)
1438 JLOG(
j_.
warn()) <<
"shard " << shard->index()
1439 <<
" is not stored at a historical path";
1445 assert(!boundaryIndex || shard->index() - boundaryIndex <= 1);
1449 if (shard->index() == boundaryIndex)
1454 if (shard->getDir().parent_path() !=
dir_)
1456 JLOG(
j_.
warn()) <<
"shard " << shard->index()
1457 <<
" is not stored at the path";
1485 for (
auto const& weak : shards)
1487 if (
auto const shard{weak.lock()}; shard)
1489 auto const [sz, fd] = shard->getFileInfo();
1512 JLOG(
j_.
warn()) <<
"maximum number of historical shards reached";
1523 <<
"maximum shard store size exceeds available storage space";
1547 auto const availableSpace =
1548 boost::filesystem::space(path).available;
1568 if (numShards <= shardCap)
1571 numShards -= shardCap;
1576 JLOG(
j_.
fatal()) <<
"Exception caught in function " << __func__
1577 <<
". Error: " << e.
what();
1589 if (!shard->setLedgerStored(ledger))
1599 if (
auto const it{
shards_.find(shard->index())}; it !=
shards_.end())
1609 <<
"shard " << shard->index() <<
" is no longer being acquired";
1633 shard->removeOnDestroy();
1663 shards_.begin(),
shards_.end(), [boundaryIndex](
auto const& entry) {
1664 return entry.first < boundaryIndex;
1677 auto const latestShardIndex =
1681 auto const removeShard = [
this](
std::uint32_t const shardIndex) ->
void {
1690 JLOG(
j_.
warn()) <<
"can't find shard to remove";
1695 JLOG(
j_.
warn()) <<
"can't find shard to remove";
1699 auto const keepShard = [
this, &lock, removeShard, separateHistoricalPath](
1703 JLOG(
j_.
error()) <<
"maximum number of historical shards reached";
1704 removeShard(shardIndex);
1707 if (separateHistoricalPath &&
1710 JLOG(
j_.
error()) <<
"insufficient storage space available";
1711 removeShard(shardIndex);
1720 auto const moveShard = [
this,
1722 auto it{
shards_.find(shardIndex)};
1725 JLOG(
j_.
warn()) <<
"can't find shard to move to historical path";
1729 auto& shard{it->second};
1734 if (!shard->tryClose())
1736 JLOG(
j_.
warn()) <<
"can't close shard to move to historical path";
1744 boost::filesystem::rename(
1749 JLOG(
j_.
error()) <<
"shard " << shardIndex
1750 <<
" failed to move to historical storage";
1755 shard = std::make_shared<Shard>(
app_, *
this, shardIndex, dst,
j_);
1760 JLOG(
j_.
error()) <<
"shard " << shardIndex
1761 <<
" failed to open in historical storage";
1762 shard->removeOnDestroy();
1768 bool const curNotSynched =
1775 if (curNotSynched || prevNotSynched)
1780 if (keepShard(*prev) && separateHistoricalPath)
1783 prev = std::nullopt;
1789 if (cur == latestShardIndex - 1)
1796 if (keepShard(*cur) && separateHistoricalPath)
1813 auto const isHistoricalShard = shardIndex < boundaryIndex;
1822 JLOG(
j_.
error()) <<
"maximum number of historical shards reached";
1824 return std::nullopt;
1828 JLOG(
j_.
error()) <<
"insufficient storage space available";
1830 return std::nullopt;
1836 boost::filesystem::path
1844 boost::filesystem::path historicalShardPath;
1853 if (potentialPaths.
empty())
1855 JLOG(
j_.
error()) <<
"failed to select a historical shard path";
1860 potentialPaths.
begin(),
1861 potentialPaths.
end(),
1862 &historicalShardPath,
1866 return historicalShardPath;
1881 struct statvfs buffer;
1882 if (statvfs(path.c_str(), &buffer))
1885 <<
"failed to acquire stats for 'historical_shard_path': "
1890 filesystemIDs[buffer.f_fsid].push_back(path.string());
1894 for (
auto const& entry : filesystemIDs)
1897 if (entry.second.size() > 1)
1902 <<
"The following paths correspond to the same filesystem: "
1903 << boost::algorithm::join(entry.second,
", ")
1904 <<
". Each configured historical storage path should"
1905 " be on a unique device or filesystem.";
1926 uniqueCapacities[boost::filesystem::space(path).available].push_back(
1929 for (
auto const& entry : uniqueCapacities)
1932 if (entry.second.size() > 1)
1937 <<
"Each of the following paths have " << entry.first
1938 <<
" bytes free, and may be located on the same device"
1940 << boost::algorithm::join(entry.second,
", ")
1941 <<
". Each configured historical storage path should"
1942 " be on a unique device or file system.";
1953 std::function<
bool(soci::session& session)>
const& callback)
1960 const uint32_t shardIndex,
1961 std::function<
bool(soci::session& session)>
const& callback)
1965 auto const it{
shards_.find(shardIndex)};
1969 it->second->callForLedgerSQL(callback);
1975 std::function<
bool(soci::session& session)>
const& callback)
1984 std::function<
bool(soci::session& session)>
const& callback)
1988 auto const it{
shards_.find(shardIndex)};
1992 it->second->callForTransactionSQL(callback);
2007 it =
shards_.lower_bound(*minShardIndex);
2011 for (; it != eit; it++)
2015 if (!visit(*it->second))
2030 minShardIndex, [&callback](
Shard& shard) ->
bool {
2042 minShardIndex, [&callback](
Shard& shard) ->
bool {
2063 for (; it != eit; it++)
2066 (!maxShardIndex || it->first <= *maxShardIndex))
2068 if (!visit(*it->second))
2101 auto shardInfo{std::make_unique<ShardInfo>()};
2102 for (
auto const& [_, shard] :
shards_)
2105 shard->index(), shard->getState(), shard->getPercentProgress());
2129 message, protocol::mtPEER_SHARD_INFO_V2)));
2145 if (section.empty())
2148 return std::make_unique<DatabaseShardImp>(app, scheduler, readThreads, j);