spark_runtime/prefix_cache/tier_evict.rs
1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! [`TierEvict`] — what an SSM spill-tier eviction decided to do with the
4//! victim. Split out of `prefix_cache.rs` (500-LoC cap).
5
6/// Outcome of [`crate::prefix_cache::PrefixCache::evict_snapshot_to_tier`].
7///
8/// The spill side of the tier has a cost gate (`ATLAS_SSM_SPILL_MIN_TOKENS`)
9/// mirroring the fault-in side's `ATLAS_SSM_FAULT_MIN_TOKENS`, and the gate has
10/// to be applied at VICTIM SELECTION, not at the byte move. Gating only the
11/// byte move would leave the index entry marked `tiered` — findable by
12/// `lookup_tiered` — with no blob behind it, so every warm turn would pay a
13/// full-blob allocation plus a `store.get` only to learn it misses.
14///
15/// Both arms free the victim's HBM slot, so the caller's invariant "a reclaim
16/// always yields a slot" is preserved either way.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum TierEvict {
19 /// Move the victim's bytes to the tier and keep its index entry findable
20 /// (`tiered = true`) so a warm turn faults it back instead of recomputing.
21 Spill {
22 /// The freed HBM snapshot slot (spill FROM here, then free it).
23 slot: usize,
24 /// Tier key = the entry's prefix hash.
25 key: u64,
26 /// Victim depth in tokens — what the spill's cost buys back.
27 depth: usize,
28 },
29 /// Too shallow to be worth a spill: the index entry was REMOVED, no tier
30 /// key exists, and `tier_spills` was not incremented. Identical to the
31 /// pre-tier drop path.
32 Drop {
33 /// The freed HBM snapshot slot.
34 slot: usize,
35 /// Victim depth in tokens (for the gate-skip log line).
36 depth: usize,
37 },
38}
39
40impl TierEvict {
41 /// The freed HBM slot, whichever arm was taken.
42 pub fn slot(&self) -> usize {
43 match *self {
44 TierEvict::Spill { slot, .. } | TierEvict::Drop { slot, .. } => slot,
45 }
46 }
47}