mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-30 02:20:39 +00:00
feat(nodestore): measure the NuDB write queue
NuDB serializes every insert behind one global mutex held for the whole call, so a caller cannot see how long it waited. Record instead the writer depth joined at and the wall time spent; with mean depth L and mean insert time W, Little's Law gives service time W/L and queueing W - W/L. That distinguishes a serialized write path from a saturated disk: measured on a dev box the device sat 89 percent idle while throughput stayed flat at 42k inserts per second. The accounting runs from a ScopeExit guard because the insert can allocate and therefore throw; leaking the depth would strand the gauge above zero for the life of the process. getWriteLoad also stops returning a hardcoded zero. It now reports writer depth, which is bounded by the writing-thread count and so stays far below the kMaxWriteLoadAcquire cutoff that gates history acquisition, where returning bytes or microseconds would have silently suppressed it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Types.h>
|
||||
#include <xrpl/nodestore/WriteStats.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -53,6 +54,18 @@ public:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get write-path statistics for backends that support it.
|
||||
*
|
||||
* Returns std::nullopt for backends that do not measure their writes.
|
||||
* @see WriteStats for how to derive queuing time from the result.
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<WriteStats>
|
||||
getWriteStats() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the backend.
|
||||
* @param createIfMissing Create the database files if necessary.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/WriteStats.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -84,6 +86,15 @@ public:
|
||||
virtual std::int32_t
|
||||
getWriteLoad() const = 0;
|
||||
|
||||
/**
|
||||
* Get backend write-path statistics, if the backend measures them.
|
||||
*
|
||||
* @return The statistics, or std::nullopt when the backend does not
|
||||
* measure its writes.
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<WriteStats>
|
||||
getWriteStats() const = 0;
|
||||
|
||||
/**
|
||||
* Store the object.
|
||||
*
|
||||
|
||||
81
include/xrpl/nodestore/WriteStats.h
Normal file
81
include/xrpl/nodestore/WriteStats.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::node_store {
|
||||
|
||||
/**
|
||||
* A snapshot of a backend's write-path behaviour.
|
||||
*
|
||||
* A backend that serializes its writes behind an internal lock cannot
|
||||
* report lock wait time directly, because the lock is private to it. This
|
||||
* type reports what an outside caller can measure - how many writers were
|
||||
* in flight and how long each write took - from which the queuing time
|
||||
* follows.
|
||||
*
|
||||
* With mean depth L, mean insert time W and arrival rate lambda, Little's
|
||||
* Law gives service time S = W / L and queuing time W - S. When S times
|
||||
* lambda approaches 1.0 the backend is consuming a whole core-equivalent
|
||||
* inside its critical section, which is the signature of a serialized
|
||||
* write path.
|
||||
*
|
||||
* caller ---+
|
||||
* caller ---+--> [ backend lock ] --> disk
|
||||
* caller ---+
|
||||
* |
|
||||
* concurrentWriters = queue length here
|
||||
* insertTotalUs / insertCount = time in the whole system (W)
|
||||
* depthSum / insertCount = mean depth (L)
|
||||
*
|
||||
* @note All fields except @ref concurrentWriters are cumulative for the
|
||||
* life of the backend, so a reader must difference successive
|
||||
* samples to get a rate. @ref concurrentWriters is instantaneous.
|
||||
* @note Sampled without a lock, so fields may be a few operations out of
|
||||
* step with each other. They are diagnostics, not accounting.
|
||||
*
|
||||
* Example - mean insert time and mean depth:
|
||||
* @code
|
||||
* if (auto const s = backend->getWriteStats(); s && s->insertCount)
|
||||
* {
|
||||
* double const meanUs = double(s->insertTotalUs) / s->insertCount;
|
||||
* double const meanDepth = double(s->depthSum) / s->insertCount;
|
||||
* double const serviceUs = meanUs / meanDepth; // Little's Law
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Example - edge case, an idle backend:
|
||||
* @code
|
||||
* // insertCount is 0, so every derived mean is undefined. Guard on it
|
||||
* // rather than publishing a division by zero.
|
||||
* @endcode
|
||||
*/
|
||||
struct WriteStats
|
||||
{
|
||||
/**
|
||||
* Writers inside the backend store call right now.
|
||||
*/
|
||||
std::uint64_t concurrentWriters = 0;
|
||||
|
||||
/**
|
||||
* Total completed inserts.
|
||||
*/
|
||||
std::uint64_t insertCount = 0;
|
||||
|
||||
/**
|
||||
* Summed wall time of all inserts, in microseconds.
|
||||
*/
|
||||
std::uint64_t insertTotalUs = 0;
|
||||
|
||||
/**
|
||||
* Longest single insert seen, in microseconds. A true maximum.
|
||||
*/
|
||||
std::uint64_t insertMaxUs = 0;
|
||||
|
||||
/**
|
||||
* Summed writer depth observed at each insert. Divided by
|
||||
* @ref insertCount this gives mean depth.
|
||||
*/
|
||||
std::uint64_t depthSum = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::node_store
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xrpl/nodestore/Database.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/WriteStats.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -89,6 +90,12 @@ public:
|
||||
return backend_->getWriteLoad();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<WriteStats>
|
||||
getWriteStats() const override
|
||||
{
|
||||
return backend_->getWriteStats();
|
||||
}
|
||||
|
||||
void
|
||||
importDatabase(Database& source) override
|
||||
{
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
#include <xrpl/nodestore/DatabaseRotating.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/WriteStats.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::node_store {
|
||||
@@ -51,6 +53,9 @@ public:
|
||||
std::int32_t
|
||||
getWriteLoad() const override;
|
||||
|
||||
[[nodiscard]] std::optional<WriteStats>
|
||||
getWriteStats() const override;
|
||||
|
||||
void
|
||||
importDatabase(Database& source) override;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user