Skip to main content

grafeo_common/memory/buffer/
tiered.rs

1//! Tiered storage: trait for data structures that can live in RAM or on disk.
2//!
3//! The [`TieredStore`] trait defines how a storage subsystem transitions
4//! between memory tiers. The [`BufferManager`](super::BufferManager) decides
5//! *when* to transition (based on memory pressure); this trait defines *how*.
6//!
7//! # Storage states
8//!
9//! ```text
10//!       ┌──────────────┐    spill()     ┌──────────────┐
11//!       │   InMemory   │ ────────────> │    OnDisk    │
12//!       │ (RAM, fast)  │               │ (mmap, warm) │
13//!       └──────────────┘               └──────┬───────┘
14//!              ▲                               │
15//!              └───────── reload() ────────────┘
16//! ```
17//!
18//! All states expose the same read interface. Callers never need to know
19//! which tier is active.
20
21use std::path::Path;
22
23use crate::utils::error::Result;
24
25/// The current storage tier of a data structure.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27#[non_exhaustive]
28pub enum StorageTier {
29    /// Fully in RAM. Fastest access for both reads and writes.
30    InMemory,
31    /// On disk, accessed via mmap. The OS page cache provides warm reads.
32    /// Mutations go through a WAL overlay.
33    OnDisk,
34    /// Not yet initialized (structure exists but has no data).
35    Uninitialized,
36}
37
38impl StorageTier {
39    /// Returns `true` if data is fully in RAM.
40    #[must_use]
41    pub fn is_in_memory(self) -> bool {
42        self == Self::InMemory
43    }
44
45    /// Returns `true` if data is served from disk (mmap).
46    #[must_use]
47    pub fn is_on_disk(self) -> bool {
48        self == Self::OnDisk
49    }
50}
51
52/// A storage subsystem that can transition between RAM and disk tiers.
53///
54/// Implementors manage their own data layout for both tiers. The
55/// [`BufferManager`](super::BufferManager) triggers transitions via the
56/// [`MemoryConsumer`](super::MemoryConsumer) trait; this trait provides
57/// the mechanics of persisting, mapping, and reloading data.
58///
59/// # Crate boundaries
60///
61/// This trait lives in `grafeo-common` so it can be referenced by both
62/// `grafeo-core` (section serializers) and `grafeo-engine` (orchestration).
63/// Implementations that need filesystem I/O (mmap, file creation) should
64/// live in `grafeo-engine`, not `grafeo-core`.
65pub trait TieredStore: Send + Sync {
66    /// Estimated RAM footprint in bytes if built entirely in memory.
67    ///
68    /// Returns 0 when the structure is on disk or uninitialized.
69    fn estimated_ram_bytes(&self) -> usize;
70
71    /// Current storage tier.
72    fn tier(&self) -> StorageTier;
73
74    /// Serializes in-memory state to the given path.
75    ///
76    /// After this call, [`open_mmap`](Self::open_mmap) can serve reads
77    /// from the file. This does NOT free RAM: the caller should drop the
78    /// in-memory representation separately (typically via the
79    /// [`MemoryConsumer::spill`](super::MemoryConsumer::spill) path).
80    ///
81    /// # Errors
82    ///
83    /// Returns an error if serialization or I/O fails.
84    fn persist(&self, path: &Path) -> Result<()>;
85
86    /// Switches to mmap-backed reads from a previously persisted file.
87    ///
88    /// Drops the in-memory data and serves reads through the OS page cache.
89    /// The tier transitions to [`StorageTier::OnDisk`].
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the file cannot be memory-mapped.
94    fn open_mmap(&self, path: &Path) -> Result<()>;
95
96    /// Reloads data from disk back into RAM.
97    ///
98    /// The tier transitions to [`StorageTier::InMemory`]. Used when memory
99    /// becomes available and faster access is desired.
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if deserialization fails.
104    fn reload_to_ram(&self) -> Result<()>;
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_storage_tier_predicates() {
113        assert!(StorageTier::InMemory.is_in_memory());
114        assert!(!StorageTier::InMemory.is_on_disk());
115
116        assert!(StorageTier::OnDisk.is_on_disk());
117        assert!(!StorageTier::OnDisk.is_in_memory());
118
119        assert!(!StorageTier::Uninitialized.is_in_memory());
120        assert!(!StorageTier::Uninitialized.is_on_disk());
121    }
122
123    #[test]
124    fn test_storage_tier_equality() {
125        assert_eq!(StorageTier::InMemory, StorageTier::InMemory);
126        assert_ne!(StorageTier::InMemory, StorageTier::OnDisk);
127        assert_ne!(StorageTier::OnDisk, StorageTier::Uninitialized);
128    }
129}