somatize_runtime/cache/
tiered.rs1use somatize_core::cache::{CacheKey, CacheStore, CacheTier, EntryMeta};
2use somatize_core::error::Result;
3use somatize_core::value::Value;
4
5pub struct TieredCache {
10 tiers: Vec<(CacheTier, Box<dyn CacheStore>)>,
11}
12
13impl TieredCache {
14 pub fn new(tiers: Vec<(CacheTier, Box<dyn CacheStore>)>) -> Self {
17 Self { tiers }
18 }
19
20 pub fn memory_and_local(memory: Box<dyn CacheStore>, local: Box<dyn CacheStore>) -> Self {
22 Self {
23 tiers: vec![(CacheTier::Memory, memory), (CacheTier::Local, local)],
24 }
25 }
26}
27
28impl CacheStore for TieredCache {
29 fn get(&self, key: &CacheKey) -> Result<Option<Value>> {
30 for (i, (_, store)) in self.tiers.iter().enumerate() {
31 if let Some(value) = store.get(key)? {
32 for (_, faster_store) in &self.tiers[..i] {
34 let _ = faster_store.put(key, &value);
35 }
36 return Ok(Some(value));
37 }
38 }
39 Ok(None)
40 }
41
42 fn put(&self, key: &CacheKey, value: &Value) -> Result<()> {
43 for (_, store) in &self.tiers {
45 store.put(key, value)?;
46 }
47 Ok(())
48 }
49
50 fn exists(&self, key: &CacheKey) -> Result<bool> {
51 for (_, store) in &self.tiers {
52 if store.exists(key)? {
53 return Ok(true);
54 }
55 }
56 Ok(false)
57 }
58
59 fn remove(&self, key: &CacheKey) -> Result<()> {
60 for (_, store) in &self.tiers {
61 store.remove(key)?;
62 }
63 Ok(())
64 }
65
66 fn metadata(&self, key: &CacheKey) -> Result<Option<EntryMeta>> {
67 for (_, store) in &self.tiers {
68 if let Some(meta) = store.metadata(key)? {
69 return Ok(Some(meta));
70 }
71 }
72 Ok(None)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::cache::local::LocalCache;
80 use crate::cache::memory::MemoryCache;
81 use std::env;
82 use std::fs;
83 use std::path::PathBuf;
84
85 use std::sync::atomic::{AtomicU64, Ordering};
86 static COUNTER: AtomicU64 = AtomicU64::new(0);
87
88 fn temp_dir() -> PathBuf {
89 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
90 let dir = env::temp_dir().join(format!("soma_tiered_test_{}_{id}", std::process::id()));
91 let _ = fs::remove_dir_all(&dir);
92 dir
93 }
94
95 fn make_tiered() -> (TieredCache, PathBuf) {
96 let dir = temp_dir();
97 let memory = Box::new(MemoryCache::default());
98 let local = Box::new(LocalCache::new(&dir).unwrap());
99 (TieredCache::memory_and_local(memory, local), dir)
100 }
101
102 #[test]
103 fn put_writes_to_all_tiers() {
104 let (cache, dir) = make_tiered();
105 let key = CacheKey::hash_data(b"test");
106 let value = Value::tensor(vec![1.0, 2.0], vec![2]);
107
108 cache.put(&key, &value).unwrap();
109
110 assert!(cache.tiers[0].1.exists(&key).unwrap()); assert!(cache.tiers[1].1.exists(&key).unwrap()); let _ = fs::remove_dir_all(&dir);
115 }
116
117 #[test]
118 fn get_from_memory_first() {
119 let (cache, dir) = make_tiered();
120 let key = CacheKey::hash_data(b"test");
121 let value = Value::tensor(vec![1.0], vec![1]);
122
123 cache.put(&key, &value).unwrap();
124 let result = cache.get(&key).unwrap().unwrap();
125 assert_eq!(result, value);
126
127 let _ = fs::remove_dir_all(&dir);
128 }
129
130 #[test]
131 fn promotes_from_local_to_memory() {
132 let dir = temp_dir();
133 let memory = Box::new(MemoryCache::default());
134 let local = Box::new(LocalCache::new(&dir).unwrap());
135
136 let key = CacheKey::hash_data(b"local_only");
138 let value = Value::tensor(vec![42.0], vec![1]);
139 local.put(&key, &value).unwrap();
140
141 let tiered = TieredCache::memory_and_local(memory, local);
142
143 assert!(!tiered.tiers[0].1.exists(&key).unwrap());
145
146 let result = tiered.get(&key).unwrap().unwrap();
148 assert_eq!(result, value);
149
150 assert!(tiered.tiers[0].1.exists(&key).unwrap());
152
153 let _ = fs::remove_dir_all(&dir);
154 }
155
156 #[test]
157 fn miss_returns_none() {
158 let (cache, dir) = make_tiered();
159 assert!(cache.get(&CacheKey::hash_data(b"nope")).unwrap().is_none());
160 let _ = fs::remove_dir_all(&dir);
161 }
162
163 #[test]
164 fn remove_from_all_tiers() {
165 let (cache, dir) = make_tiered();
166 let key = CacheKey::hash_data(b"test");
167 cache.put(&key, &Value::Empty).unwrap();
168 cache.remove(&key).unwrap();
169
170 assert!(!cache.tiers[0].1.exists(&key).unwrap());
171 assert!(!cache.tiers[1].1.exists(&key).unwrap());
172
173 let _ = fs::remove_dir_all(&dir);
174 }
175}