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}