reddb_server/runtime/memory_accounting.rs
1//! Live usage reporting into the shared accounting pool — ADR 0073 §2.
2//!
3//! Every governed pool already owns a counter for its own footprint: the page
4//! cache knows its resident slot count, the blob cache its `bytes_in_use`, the
5//! segments their `memory_bytes` atomics, the WAL queue its buffered bytes.
6//! This module is the seam that reads those counters and stores them into the
7//! one shared pool, so `red.stats` can show per-pool `share_bytes` /
8//! `used_bytes` against a single total.
9//!
10//! **Pull, not push.** The alternative — every subsystem holding an
11//! `Arc<MemoryAccounting>` and updating it inline — would thread the pool into
12//! the page-cache hit path and the segment insert path, which is exactly the
13//! per-operation cost ADR 0073 §3 forbids. Instead the pools keep the atomics
14//! they already maintain and a reader samples them. A refresh is a handful of
15//! relaxed loads plus one read lock per segment; the read hot paths gain
16//! nothing at all.
17//!
18//! Sizing and accounting only: a pool over its share shows up here and is not
19//! acted on. Admission enforcement is the next slice (ADR 0073 §4).
20
21use crate::runtime::RedDBRuntime;
22use crate::storage::memory_pools::MemoryPool;
23
24impl RedDBRuntime {
25 /// Sample every governed pool and store its footprint into the shared
26 /// accounting. Called before the `red.stats` budget section is rendered.
27 ///
28 /// Cheap by construction: no allocation, no new lock on any read hot path,
29 /// and each pool contributes counters it was already keeping.
30 pub fn refresh_memory_accounting(&self) {
31 let accounting = self.memory_accounting();
32 let store = self.db().store();
33
34 accounting.report(MemoryPool::PageCache, store.page_cache_bytes_in_use());
35 accounting.report(
36 MemoryPool::BlobCacheL1,
37 self.result_blob_cache().ram_bytes_in_use(),
38 );
39 accounting.report(MemoryPool::SegmentArena, store.segment_memory_bytes());
40 accounting.report(
41 MemoryPool::IndexMemory,
42 self.index_store_ref().memory_bytes(),
43 );
44 // Paged stores keep a resident group-commit writer and queue; embedded
45 // single-file stores append WAL payloads synchronously to the artifact
46 // and retain no live WAL buffer between mutations.
47 accounting.report(MemoryPool::WalBuffers, store.wal_buffer_bytes_in_use());
48 accounting.observe_total_used(accounting.total_used_bytes());
49 }
50}