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/// **Deprecated (Phase 8c, 0.5.42):** the [`crate::storage::section::Section`]
55/// trait subsumed this trait's responsibilities — its `swap_to_mmap` and
56/// `reload_to_ram` methods cover the same lifecycle, and every wired
57/// section type (LPG, RDF, Vector, Ring, Compact) implements `Section`,
58/// not `TieredStore`. This trait was never implemented anywhere; it is
59/// kept for one release as a no-op to avoid breaking downstream callers
60/// who may have prepared their own impls. Migrate to `Section` for new
61/// code; the standalone trait will be removed in 0.6.0.
62///
63/// Implementors manage their own data layout for both tiers. The
64/// [`BufferManager`](super::BufferManager) triggers transitions via the
65/// [`MemoryConsumer`](super::MemoryConsumer) trait; this trait provides
66/// the mechanics of persisting, mapping, and reloading data.
67///
68/// # Crate boundaries
69///
70/// This trait lives in `grafeo-common` so it can be referenced by both
71/// `grafeo-core` (section serializers) and `grafeo-engine` (orchestration).
72/// Implementations that need filesystem I/O (mmap, file creation) should
73/// live in `grafeo-engine`, not `grafeo-core`.
74#[deprecated(
75    since = "0.5.42",
76    note = "Use the `Section` trait (with `swap_to_mmap` / `reload_to_ram`) and `MemoryConsumer` instead. \
77            This trait was never implemented anywhere; it will be removed in 0.6.0."
78)]
79pub trait TieredStore: Send + Sync {
80    /// Estimated RAM footprint in bytes if built entirely in memory.
81    ///
82    /// Returns 0 when the structure is on disk or uninitialized.
83    fn estimated_ram_bytes(&self) -> usize;
84
85    /// Current storage tier.
86    fn tier(&self) -> StorageTier;
87
88    /// Serializes in-memory state to the given path.
89    ///
90    /// After this call, [`open_mmap`](Self::open_mmap) can serve reads
91    /// from the file. This does NOT free RAM: the caller should drop the
92    /// in-memory representation separately (typically via the
93    /// [`MemoryConsumer::spill`](super::MemoryConsumer::spill) path).
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if serialization or I/O fails.
98    fn persist(&self, path: &Path) -> Result<()>;
99
100    /// Switches to mmap-backed reads from a previously persisted file.
101    ///
102    /// Drops the in-memory data and serves reads through the OS page cache.
103    /// The tier transitions to [`StorageTier::OnDisk`].
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if the file cannot be memory-mapped.
108    fn open_mmap(&self, path: &Path) -> Result<()>;
109
110    /// Reloads data from disk back into RAM.
111    ///
112    /// The tier transitions to [`StorageTier::InMemory`]. Used when memory
113    /// becomes available and faster access is desired.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if deserialization fails.
118    fn reload_to_ram(&self) -> Result<()>;
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_storage_tier_predicates() {
127        assert!(StorageTier::InMemory.is_in_memory());
128        assert!(!StorageTier::InMemory.is_on_disk());
129
130        assert!(StorageTier::OnDisk.is_on_disk());
131        assert!(!StorageTier::OnDisk.is_in_memory());
132
133        assert!(!StorageTier::Uninitialized.is_in_memory());
134        assert!(!StorageTier::Uninitialized.is_on_disk());
135    }
136
137    #[test]
138    fn test_storage_tier_equality() {
139        assert_eq!(StorageTier::InMemory, StorageTier::InMemory);
140        assert_ne!(StorageTier::InMemory, StorageTier::OnDisk);
141        assert_ne!(StorageTier::OnDisk, StorageTier::Uninitialized);
142    }
143}