memory_budget/resizable.rs
1//! Contract every cache that wants to participate in the
2//! [`MemoryBudget`](crate::MemoryBudget) reapportionment must satisfy.
3
4/// Minimal interface a memory-budget-aware cache must expose.
5///
6/// All methods are `&self` because caches use interior mutability (a
7/// `Mutex<LruCache<...>>` or a `moka::sync::Cache`). The budget holds
8/// `Weak<dyn Resizable>` handles so a dropped cache is silently
9/// removed from the next tick.
10pub trait Resizable: Send + Sync {
11 /// Human-readable label for log lines and metrics.
12 fn name(&self) -> &str;
13
14 /// Current bytes held by the cache.
15 fn current_bytes(&self) -> u64;
16
17 /// Configured soft cap. Honoured eagerly (eviction-on-insert) and
18 /// at each tick by [`Resizable::set_max_bytes`].
19 fn max_bytes(&self) -> u64;
20
21 /// Update the soft cap. The cache may either evict synchronously
22 /// (small caches) or schedule eviction over the next few ops
23 /// (large caches). Either is acceptable as long as
24 /// [`Resizable::current_bytes`] converges to `new` within one
25 /// budget tick.
26 fn set_max_bytes(&self, new: u64);
27
28 /// Stats sample used by the reapportionment policy.
29 fn stats(&self) -> ResizableStats;
30}
31
32/// Point-in-time stats sample from a registered cache.
33///
34/// All counters are monotonic-since-construction. The policy compares
35/// the delta between consecutive snapshots, so the absolute values
36/// only need to be consistent within a single sample.
37#[derive(Clone, Copy, Debug, Default)]
38pub struct ResizableStats {
39 /// Successful lookups served from the cache.
40 pub hits: u64,
41 /// Lookups that missed and required falling back to the inner store.
42 pub misses: u64,
43 /// Total evictions over the cache's lifetime.
44 pub evictions: u64,
45}
46
47impl ResizableStats {
48 /// Fraction of `hits + misses` served from the cache. `None`
49 /// when no lookups have occurred (avoids division by zero and a
50 /// misleading 0.0 reading on idle caches).
51 #[must_use]
52 pub fn hit_rate(&self) -> Option<f64> {
53 let total = self.hits.checked_add(self.misses)?;
54 if total == 0 {
55 return None;
56 }
57 Some(self.hits as f64 / total as f64)
58 }
59}