mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-29 18:10:34 +00:00
280 lines
8.3 KiB
C++
280 lines
8.3 KiB
C++
#include <xrpl/nodestore/Database.h>
|
|
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/basics/contract.h>
|
|
#include <xrpl/beast/core/CurrentThreadName.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/config/BasicConfig.h>
|
|
#include <xrpl/config/Constants.h>
|
|
#include <xrpl/json/json_forwards.h>
|
|
#include <xrpl/json/json_value.h>
|
|
#include <xrpl/nodestore/Backend.h>
|
|
#include <xrpl/nodestore/NodeObject.h>
|
|
#include <xrpl/nodestore/Scheduler.h>
|
|
#include <xrpl/nodestore/Types.h>
|
|
#include <xrpl/protocol/SystemParameters.h>
|
|
#include <xrpl/protocol/jss.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
namespace xrpl::NodeStore {
|
|
|
|
Database::Database(
|
|
Scheduler& scheduler,
|
|
int readThreads,
|
|
Section const& config,
|
|
beast::Journal journal)
|
|
: j_(journal)
|
|
, scheduler_(scheduler)
|
|
, earliestLedgerSeq_(get<std::uint32_t>(config, Keys::kEarliestSeq, kXrpLedgerEarliestSeq))
|
|
, requestBundle_(get<int>(config, Keys::kRqBundle, 4))
|
|
, readThreads_(std::max(1, readThreads))
|
|
{
|
|
XRPL_ASSERT(readThreads, "xrpl::NodeStore::Database::Database : nonzero threads input");
|
|
|
|
if (earliestLedgerSeq_ < 1)
|
|
Throw<std::runtime_error>("Invalid earliest_seq");
|
|
|
|
if (requestBundle_ < 1 || requestBundle_ > 64)
|
|
Throw<std::runtime_error>("Invalid rq_bundle");
|
|
|
|
for (int i = readThreads_.load(); i != 0; --i)
|
|
{
|
|
std::thread t(
|
|
[this](int i) {
|
|
runningThreads_++;
|
|
|
|
beast::setCurrentThreadName("db prefetch #" + std::to_string(i));
|
|
|
|
decltype(read_) read;
|
|
|
|
while (true)
|
|
{
|
|
{
|
|
std::unique_lock<std::mutex> lock(readLock_);
|
|
|
|
if (isStopping())
|
|
break;
|
|
|
|
if (read_.empty())
|
|
{
|
|
runningThreads_--;
|
|
readCondVar_.wait(lock);
|
|
runningThreads_++;
|
|
}
|
|
|
|
if (isStopping())
|
|
break;
|
|
|
|
// extract multiple object at a time to minimize the
|
|
// overhead of acquiring the mutex.
|
|
for (int cnt = 0; !read_.empty() && cnt != requestBundle_; ++cnt)
|
|
read.insert(read_.extract(read_.begin()));
|
|
}
|
|
|
|
for (auto it = read.begin(); it != read.end(); ++it)
|
|
{
|
|
XRPL_ASSERT(
|
|
!it->second.empty(),
|
|
"xrpl::NodeStore::Database::Database : non-empty "
|
|
"data");
|
|
|
|
auto const& hash = it->first;
|
|
auto const& data = it->second;
|
|
auto const seqn = data[0].first;
|
|
|
|
auto obj = fetchNodeObject(hash, seqn, FetchType::Async);
|
|
|
|
// This could be further optimized: if there are
|
|
// multiple requests for sequence numbers mapping to
|
|
// multiple databases by sorting requests such that all
|
|
// indices mapping to the same database are grouped
|
|
// together and serviced by a single read.
|
|
for (auto const& req : data)
|
|
{
|
|
req.second(
|
|
(seqn == req.first) || isSameDB(req.first, seqn)
|
|
? obj
|
|
: fetchNodeObject(hash, req.first, FetchType::Async));
|
|
}
|
|
}
|
|
|
|
read.clear();
|
|
}
|
|
|
|
--runningThreads_;
|
|
--readThreads_;
|
|
},
|
|
i);
|
|
t.detach();
|
|
}
|
|
}
|
|
|
|
Database::~Database()
|
|
{
|
|
// NOTE!
|
|
// Any derived class should call the stop() method in its
|
|
// destructor. Otherwise, occasionally, the derived class may
|
|
// crash during shutdown when its members are accessed by one of
|
|
// these threads after the derived class is destroyed but before
|
|
// this base class is destroyed.
|
|
stop();
|
|
}
|
|
|
|
bool
|
|
Database::isStopping() const
|
|
{
|
|
return readStopping_.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
void
|
|
Database::stop()
|
|
{
|
|
{
|
|
std::scoped_lock const lock(readLock_);
|
|
|
|
if (!readStopping_.exchange(true, std::memory_order_relaxed))
|
|
{
|
|
JLOG(j_.debug()) << "Clearing read queue because of stop request";
|
|
read_.clear();
|
|
readCondVar_.notify_all();
|
|
}
|
|
}
|
|
|
|
JLOG(j_.debug()) << "Waiting for stop request to complete...";
|
|
|
|
using namespace std::chrono;
|
|
|
|
auto const start = steady_clock::now();
|
|
|
|
while (readThreads_.load() != 0)
|
|
{
|
|
XRPL_ASSERT(
|
|
steady_clock::now() - start < 30s,
|
|
"xrpl::NodeStore::Database::stop : maximum stop duration");
|
|
std::this_thread::yield();
|
|
}
|
|
|
|
JLOG(j_.debug())
|
|
<< "Stop request completed in "
|
|
<< duration_cast<std::chrono::milliseconds>(steady_clock::now() - start).count()
|
|
<< " milliseconds";
|
|
}
|
|
|
|
void
|
|
Database::asyncFetch(
|
|
uint256 const& hash,
|
|
std::uint32_t ledgerSeq,
|
|
std::function<void(std::shared_ptr<NodeObject> const&)>&& cb)
|
|
{
|
|
std::scoped_lock const lock(readLock_);
|
|
|
|
if (!isStopping())
|
|
{
|
|
read_[hash].emplace_back(ledgerSeq, std::move(cb));
|
|
readCondVar_.notify_one();
|
|
}
|
|
}
|
|
|
|
void
|
|
Database::importInternal(Backend& dstBackend, Database& srcDB)
|
|
{
|
|
Batch batch;
|
|
batch.reserve(kBatchWritePreallocationSize);
|
|
auto storeBatch = [&, fname = __func__]() {
|
|
try
|
|
{
|
|
dstBackend.storeBatch(batch);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.error()) << "Exception caught in function " << fname << ". Error: " << e.what();
|
|
return;
|
|
}
|
|
|
|
std::uint64_t sz{0};
|
|
for (auto const& nodeObject : batch)
|
|
sz += nodeObject->getData().size();
|
|
storeStats(batch.size(), sz);
|
|
batch.clear();
|
|
};
|
|
|
|
srcDB.forEach([&](std::shared_ptr<NodeObject> nodeObject) {
|
|
XRPL_ASSERT(nodeObject, "xrpl::NodeStore::Database::importInternal : non-null node");
|
|
if (!nodeObject) // This should never happen
|
|
return;
|
|
|
|
batch.emplace_back(std::move(nodeObject));
|
|
if (batch.size() >= kBatchWritePreallocationSize)
|
|
storeBatch();
|
|
});
|
|
|
|
if (!batch.empty())
|
|
storeBatch();
|
|
}
|
|
|
|
// Perform a fetch and report the time it took
|
|
std::shared_ptr<NodeObject>
|
|
Database::fetchNodeObject(
|
|
uint256 const& hash,
|
|
std::uint32_t ledgerSeq,
|
|
FetchType fetchType,
|
|
bool duplicate)
|
|
{
|
|
FetchReport fetchReport(fetchType);
|
|
|
|
using namespace std::chrono;
|
|
auto const begin{steady_clock::now()};
|
|
|
|
auto nodeObject{fetchNodeObject(hash, ledgerSeq, fetchReport, duplicate)};
|
|
auto dur = steady_clock::now() - begin;
|
|
fetchDurationUs_ += duration_cast<microseconds>(dur).count();
|
|
if (nodeObject)
|
|
{
|
|
++fetchHitCount_;
|
|
fetchSz_ += nodeObject->getData().size();
|
|
}
|
|
++fetchTotalCount_;
|
|
|
|
fetchReport.elapsed = duration_cast<milliseconds>(dur);
|
|
scheduler_.onFetch(fetchReport);
|
|
return nodeObject;
|
|
}
|
|
|
|
void
|
|
Database::getCountsJson(json::Value& obj)
|
|
{
|
|
XRPL_ASSERT(obj.isObject(), "xrpl::NodeStore::Database::getCountsJson : valid input type");
|
|
|
|
{
|
|
std::unique_lock<std::mutex> const lock(readLock_);
|
|
obj["read_queue"] = static_cast<json::UInt>(read_.size());
|
|
}
|
|
|
|
obj["read_threads_total"] = readThreads_.load();
|
|
obj["read_threads_running"] = runningThreads_.load();
|
|
obj["read_request_bundle"] = requestBundle_;
|
|
|
|
obj[jss::node_writes] = std::to_string(storeCount_);
|
|
obj[jss::node_reads_total] = std::to_string(fetchTotalCount_);
|
|
obj[jss::node_reads_hit] = std::to_string(fetchHitCount_);
|
|
obj[jss::node_written_bytes] = std::to_string(storeSz_);
|
|
obj[jss::node_read_bytes] = std::to_string(fetchSz_);
|
|
obj[jss::node_reads_duration_us] = std::to_string(fetchDurationUs_);
|
|
}
|
|
|
|
} // namespace xrpl::NodeStore
|