metrale_storage/tier/
mem.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Host-RAM tiers: [`VecSlotArena`] (hot) and [`MemSwapStore`] (cold).
4//!
5//! Owner: storage (tier).
6//! Invariants: none beyond the types.
7
8use std::collections::HashMap;
9
10use anyhow::{Result, bail};
11
12use crate::tier::traits::{SlotArena, SwapStore};
13
14/// 2026-09-25: [`SlotArena`] over one `Vec<u8>` of `slot_bytes * num_slots`
15/// bytes, allocated and zeroed in `new`. The hot tier of model-engine's
16/// host-RAM unified SSM stores.
17pub struct VecSlotArena {
18    buf: Vec<u8>,
19    slot_bytes: usize,
20    n: usize,
21}
22
23impl VecSlotArena {
24    pub fn new(slot_bytes: usize, num_slots: usize) -> Self {
25        Self {
26            buf: vec![0u8; slot_bytes * num_slots],
27            slot_bytes,
28            n: num_slots,
29        }
30    }
31}
32
33impl SlotArena for VecSlotArena {
34    fn slot_bytes(&self) -> usize {
35        self.slot_bytes
36    }
37    fn num_slots(&self) -> usize {
38        self.n
39    }
40    fn read_slot(&self, slot: usize, out: &mut [u8]) -> Result<()> {
41        if slot >= self.n || out.len() != self.slot_bytes {
42            bail!("VecSlotArena::read_slot({slot}) out of range / size mismatch");
43        }
44        let o = slot * self.slot_bytes;
45        out.copy_from_slice(&self.buf[o..o + self.slot_bytes]);
46        Ok(())
47    }
48    fn write_slot(&mut self, slot: usize, bytes: &[u8]) -> Result<()> {
49        if slot >= self.n || bytes.len() != self.slot_bytes {
50            bail!("VecSlotArena::write_slot({slot}) out of range / size mismatch");
51        }
52        let o = slot * self.slot_bytes;
53        self.buf[o..o + self.slot_bytes].copy_from_slice(bytes);
54        Ok(())
55    }
56}
57
58/// 2026-09-25: [`SwapStore`] over a `HashMap` of heap records, with no bound of
59/// its own. Model-engine's unified SSM store falls back to it when no swap
60/// file can be used.
61pub struct MemSwapStore {
62    recs: HashMap<usize, Vec<u8>>,
63    record_bytes: usize,
64}
65
66impl MemSwapStore {
67    pub fn new(record_bytes: usize) -> Self {
68        Self {
69            recs: HashMap::new(),
70            record_bytes,
71        }
72    }
73}
74
75impl SwapStore for MemSwapStore {
76    fn record_bytes(&self) -> usize {
77        self.record_bytes
78    }
79    fn write_record(&mut self, disk_slot: usize, bytes: &[u8]) -> Result<()> {
80        if bytes.len() != self.record_bytes {
81            bail!(
82                "MemSwapStore::write_record: {} bytes, expected {}",
83                bytes.len(),
84                self.record_bytes
85            );
86        }
87        self.recs.insert(disk_slot, bytes.to_vec());
88        Ok(())
89    }
90    fn read_record(&self, disk_slot: usize, out: &mut [u8]) -> Result<()> {
91        match self.recs.get(&disk_slot) {
92            Some(v) => {
93                out.copy_from_slice(v);
94                Ok(())
95            }
96            None => bail!("MemSwapStore: no record {disk_slot}"),
97        }
98    }
99    fn discard_record(&mut self, disk_slot: usize) {
100        self.recs.remove(&disk_slot);
101    }
102}