mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 22:30:57 +00:00
The board and runbook had grown by append across eight work packages, so they read in the order the work was done rather than the order a node progresses. This is the coherence pass; it adds no new instrumentation. - Dashboard: 52 panels regrouped from two rows into nine that follow the fresh-start sequence — bootstrap, peer supply, sync state, acquire and SHAMap fetch, job queue, quorum and publish, terminal blockers, then back-fill and spans collapsed since they answer conditional questions. Layout only: no title, query or description changed. - Runbook: the flat step list becomes a decision tree branching on the observed symptom, with the amendment-block check first because it is terminal. Each branch names the panels, what healthy and unhealthy look like, and what to conclude. The existing steps are kept as the detail bodies. - Reference table: every signal name re-checked against the code and every named panel against the board; four stale panel references fixed. - Validation: every signal is now either asserted or covered by a note explaining why a five-node local cluster cannot produce it. Also fixes the write-latency signal, which was inert on a real node: the store duration was only recorded on the database-import path, while the two production store implementations did not time themselves, so an ordinary node reported a write count with no latency. Both now time the backend write, which is the disk work this signal exists to expose. Without it the "existing database syncs slower than a fresh one" diagnosis had no primary signal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
242 lines
6.9 KiB
C++
242 lines
6.9 KiB
C++
#include <xrpl/nodestore/detail/DatabaseRotatingImp.h>
|
|
|
|
#include <xrpl/basics/Blob.h>
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/basics/contract.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/config/BasicConfig.h>
|
|
#include <xrpl/nodestore/Backend.h>
|
|
#include <xrpl/nodestore/Database.h>
|
|
#include <xrpl/nodestore/DatabaseRotating.h>
|
|
#include <xrpl/nodestore/NodeObject.h>
|
|
#include <xrpl/nodestore/Scheduler.h>
|
|
#include <xrpl/nodestore/Types.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace xrpl::NodeStore {
|
|
|
|
DatabaseRotatingImp::DatabaseRotatingImp(
|
|
Scheduler& scheduler,
|
|
int readThreads,
|
|
std::shared_ptr<Backend> writableBackend,
|
|
std::shared_ptr<Backend> archiveBackend,
|
|
Section const& config,
|
|
beast::Journal j)
|
|
: DatabaseRotating(scheduler, readThreads, config, j)
|
|
, writableBackend_(std::move(writableBackend))
|
|
, archiveBackend_(std::move(archiveBackend))
|
|
{
|
|
if (writableBackend_)
|
|
fdRequired_ += writableBackend_->fdRequired();
|
|
if (archiveBackend_)
|
|
fdRequired_ += archiveBackend_->fdRequired();
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::rotate(
|
|
std::unique_ptr<NodeStore::Backend>&& newBackend,
|
|
std::function<void(std::string const& writableName, std::string const& archiveName)> const& f)
|
|
{
|
|
// Pass these two names to the callback function
|
|
std::string const newWritableBackendName = newBackend->getName();
|
|
std::string newArchiveBackendName;
|
|
// Hold on to current archive backend pointer until after the
|
|
// callback finishes. Only then will the archive directory be
|
|
// deleted.
|
|
std::shared_ptr<NodeStore::Backend> oldArchiveBackend;
|
|
std::uint64_t copyForwards = 0;
|
|
{
|
|
std::scoped_lock const lock(mutex_);
|
|
|
|
archiveBackend_->setDeletePath();
|
|
oldArchiveBackend = std::move(archiveBackend_);
|
|
|
|
archiveBackend_ = std::move(writableBackend_);
|
|
newArchiveBackendName = archiveBackend_->getName();
|
|
|
|
writableBackend_ = std::move(newBackend);
|
|
|
|
copyForwards = copyForwardCount_.exchange(0, std::memory_order_relaxed);
|
|
}
|
|
|
|
if (copyForwards > 0)
|
|
{
|
|
JLOG(j_.warn()) << "Rotating: copied forward " << copyForwards
|
|
<< " archive-served reads into the writable backend "
|
|
"during the rotation window";
|
|
}
|
|
|
|
f(newWritableBackendName, newArchiveBackendName);
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::setRotationInFlight(bool inFlight)
|
|
{
|
|
rotationInFlight_.store(inFlight, std::memory_order_release);
|
|
JLOG(j_.debug()) << "Rotating: copy-forward on archive reads "
|
|
<< (inFlight ? "enabled" : "disabled");
|
|
}
|
|
|
|
std::string
|
|
DatabaseRotatingImp::getName() const
|
|
{
|
|
std::scoped_lock const lock(mutex_);
|
|
return writableBackend_->getName();
|
|
}
|
|
|
|
std::int32_t
|
|
DatabaseRotatingImp::getWriteLoad() const
|
|
{
|
|
std::scoped_lock const lock(mutex_);
|
|
return writableBackend_->getWriteLoad();
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::importDatabase(Database& source)
|
|
{
|
|
auto const backend = [&] {
|
|
std::scoped_lock const lock(mutex_);
|
|
return writableBackend_;
|
|
}();
|
|
|
|
importInternal(*backend, source);
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::sync()
|
|
{
|
|
std::scoped_lock const lock(mutex_);
|
|
writableBackend_->sync();
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t)
|
|
{
|
|
auto nObj = NodeObject::createObject(type, std::move(data), hash);
|
|
|
|
auto const backend = [&] {
|
|
std::scoped_lock const lock(mutex_);
|
|
return writableBackend_;
|
|
}();
|
|
|
|
// Time only the backend write, which is the disk work. One clock pair per
|
|
// stored object, accumulated into an atomic that the metrics gauge reads on
|
|
// its own schedule, so nothing is added to the read path or per tree node.
|
|
auto const begin = std::chrono::steady_clock::now();
|
|
backend->store(nObj);
|
|
recordStoreDuration(std::chrono::steady_clock::now() - begin);
|
|
storeStats(1, nObj->getData().size());
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::sweep()
|
|
{
|
|
// Nothing to do.
|
|
}
|
|
|
|
std::shared_ptr<NodeObject>
|
|
DatabaseRotatingImp::fetchNodeObject(
|
|
uint256 const& hash,
|
|
std::uint32_t,
|
|
FetchReport& fetchReport,
|
|
bool duplicate)
|
|
{
|
|
auto fetch = [&](std::shared_ptr<Backend> const& backend) {
|
|
Status status = Status::Ok;
|
|
std::shared_ptr<NodeObject> nodeObject;
|
|
try
|
|
{
|
|
status = backend->fetch(hash, &nodeObject);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j_.fatal()) << "Exception, " << e.what();
|
|
rethrow();
|
|
}
|
|
|
|
switch (status)
|
|
{
|
|
case Status::Ok:
|
|
case Status::NotFound:
|
|
break;
|
|
case Status::DataCorrupt:
|
|
JLOG(j_.fatal()) << "Corrupt NodeObject #" << hash;
|
|
break;
|
|
default:
|
|
JLOG(j_.warn()) << "Unknown status=" << static_cast<int>(status);
|
|
break;
|
|
}
|
|
|
|
return nodeObject;
|
|
};
|
|
|
|
// See if the node object exists in the cache
|
|
std::shared_ptr<NodeObject> nodeObject;
|
|
|
|
auto [writable, archive] = [&] {
|
|
std::scoped_lock const lock(mutex_);
|
|
return std::make_pair(writableBackend_, archiveBackend_);
|
|
}();
|
|
|
|
// Try to fetch from the writable backend
|
|
nodeObject = fetch(writable);
|
|
if (!nodeObject)
|
|
{
|
|
// Otherwise try to fetch from the archive backend
|
|
nodeObject = fetch(archive);
|
|
if (nodeObject)
|
|
{
|
|
{
|
|
// Refresh the writable backend pointer
|
|
std::scoped_lock const lock(mutex_);
|
|
writable = writableBackend_;
|
|
}
|
|
|
|
// Update writable backend with data from the archive backend.
|
|
// While a rotation is in flight, ordinary (duplicate == false)
|
|
// reads served by the archive are copied forward too: the
|
|
// archive is about to be deleted, and a body canonicalized
|
|
// into the cache after the freshen getKeys() snapshot would
|
|
// otherwise survive only in RAM once the archive is dropped.
|
|
if (duplicate || rotationInFlight_.load(std::memory_order_acquire))
|
|
{
|
|
if (!duplicate)
|
|
copyForwardCount_.fetch_add(1, std::memory_order_relaxed);
|
|
writable->store(nodeObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nodeObject)
|
|
fetchReport.wasFound = true;
|
|
|
|
return nodeObject;
|
|
}
|
|
|
|
void
|
|
DatabaseRotatingImp::forEach(std::function<void(std::shared_ptr<NodeObject>)> f)
|
|
{
|
|
auto [writable, archive] = [&] {
|
|
std::scoped_lock const lock(mutex_);
|
|
return std::make_pair(writableBackend_, archiveBackend_);
|
|
}();
|
|
|
|
// Iterate the writable backend
|
|
writable->forEach(f);
|
|
|
|
// Iterate the archive backend
|
|
archive->forEach(f);
|
|
}
|
|
|
|
} // namespace xrpl::NodeStore
|