Files
rippled/include/xrpl/nodestore/WriteStats.h
Pratik Mankawde 216d75e2e5 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>
2026-07-27 18:37:00 +01:00

82 lines
2.5 KiB
C++

#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