decdn_cache/policy/mod.rs
1//! Pluggable cache admission and eviction policy traits (ADR 040).
2//!
3//! The engine owns the store and every safety exemption (pins, probe-holds,
4//! deny/takedown). A policy is a pure decision function fed observations: it
5//! ranks eligible candidates or classifies an admission, and never reaches
6//! into the store.
7use crate::{EvictionCandidates, Hash};
8use std::collections::HashMap;
9
10pub mod lru;
11pub mod sketch;
12pub mod tinylfu;
13
14/// Where a freshly admitted blob is stored.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Segment {
17 /// One-hit-wonder holding area, capped and evicted first.
18 Probation,
19 /// Promoted, full-citizen cache.
20 Main,
21}
22
23/// A policy's admission verdict for a miss about to be served.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AdmissionDecision {
26 /// Keep the blob, in this segment.
27 Store {
28 /// Which segment the blob enters on admission.
29 segment: Segment,
30 },
31 /// Reserved: serve without storing. No shipped impl returns it; the engine
32 /// treats it as `Store { Probation }` until a pass-through leg exists.
33 PassThrough,
34}
35
36/// Inputs available to an admission decision.
37#[derive(Debug, Clone)]
38pub struct AdmissionContext {
39 /// The blob being admitted.
40 pub hash: Hash,
41 /// Total blob size when known ahead of the fill, else `None`.
42 pub known_size: Option<u64>,
43}
44
45/// Decides whether a miss about to be served is worth keeping, and where.
46pub trait AdmissionPolicy: Send + Sync + std::fmt::Debug {
47 /// Judge one miss. Called on the serve path, so it must not block.
48 fn admit(&self, ctx: &AdmissionContext) -> AdmissionDecision;
49}
50
51/// Everything a policy needs to plan one eviction sweep. The engine assembles
52/// it and never interprets the policy's semantics — `candidates` is already
53/// stripped of pins and probe-holds (a deny-listed hash stays a candidate even
54/// when pinned: deny wins over pin), and `segments` is the generic
55/// `hash -> Segment` membership the engine tracks with no meaning attached.
56#[derive(Debug)]
57pub struct EvictionContext<'a> {
58 /// Eligible hashes with their last-access `Instant` (pin- and hold-filtered;
59 /// deny-listed hashes stay).
60 pub candidates: &'a EvictionCandidates,
61 /// Whole-store per-hash byte size.
62 pub sizes: &'a HashMap<Hash, u64>,
63 /// Generic segment membership the engine tracks (absent = `Segment::Main`).
64 pub segments: &'a HashMap<Hash, Segment>,
65 /// Effective footprint the sweep is deciding against.
66 pub total_bytes: u64,
67 /// Evict down to this many bytes.
68 pub target_bytes: u64,
69 /// Maximum releases this sweep.
70 pub budget: u64,
71 /// Configured cache size; a policy sizes its own caps from it (LRU ignores).
72 pub cache_bytes: u64,
73}
74
75/// The single sweep-time decision: what leaves, and what graduates to a new
76/// segment. `LruEviction` returns `promote` empty.
77#[derive(Debug, Default)]
78pub struct EvictionPlan {
79 /// Release these hashes, in order.
80 pub evict: Vec<Hash>,
81 /// Move these hashes to a new segment (e.g. `Probation -> Main`).
82 pub promote: Vec<(Hash, Segment)>,
83}
84
85/// Decides what the engine releases when the store is over its target.
86pub trait EvictionPolicy: Send + Sync + std::fmt::Debug {
87 /// The one sweep-time decision: what leaves, what graduates. A policy that
88 /// ranks by frequency reads the shared [`FrequencyEstimator`] directly — the
89 /// engine feeds that estimator on every serve. `candidates` is already
90 /// stripped of pins and probe-holds by the engine; a deny-listed hash stays
91 /// a candidate even when pinned (deny wins over pin).
92 fn plan(&self, ctx: &EvictionContext<'_>) -> EvictionPlan;
93}
94
95/// Decayed frequency estimate shared by admission and eviction.
96pub trait FrequencyEstimator: Send + Sync + std::fmt::Debug {
97 /// Record one access to `hash`. The engine calls this on every serve.
98 fn observe(&self, hash: Hash);
99 /// The current decayed access count for `hash`.
100 fn estimate(&self, hash: Hash) -> u32;
101}
102
103pub use lru::{AlwaysAdmit, LruEviction};
104pub use tinylfu::{ProbationAdmission, TinyLfuEstimator, TinyLfuEviction};