Skip to main content

oxirs_vec/
advanced_caching_multilevel.rs

1//! Multi-level cache and invalidation utilities for the advanced caching system.
2//!
3//! Contains:
4//! - `MultiLevelCache` — combines memory + persistent caches
5//! - `MultiLevelCacheStats` — aggregated hit/miss statistics
6//! - `CacheInvalidator` — tag-indexed and namespace-indexed invalidation
7//! - `InvalidationStats` — invalidation tracking statistics
8
9use crate::advanced_caching::{CacheConfig, CacheEntry, CacheKey, CacheStats};
10use crate::advanced_caching_eviction::{MemoryCache, PersistentCache};
11use anyhow::Result;
12use std::collections::HashMap;
13use std::sync::{Arc, RwLock};
14use std::time::Duration;
15
16/// Type alias for complex tag index structure
17type TagIndex = Arc<RwLock<HashMap<String, HashMap<String, Vec<CacheKey>>>>>;
18
19/// Multi-level cache combining memory and persistent storage
20pub struct MultiLevelCache {
21    pub(super) memory_cache: Arc<RwLock<MemoryCache>>,
22    pub(super) persistent_cache: Option<Arc<PersistentCache>>,
23    #[allow(dead_code)]
24    pub(super) config: CacheConfig,
25    pub(super) stats: Arc<RwLock<MultiLevelCacheStats>>,
26}
27
28#[derive(Debug, Default, Clone)]
29pub struct MultiLevelCacheStats {
30    pub memory_hits: u64,
31    pub memory_misses: u64,
32    pub persistent_hits: u64,
33    pub persistent_misses: u64,
34    pub total_requests: u64,
35}
36
37impl MultiLevelCache {
38    pub fn new(config: CacheConfig) -> Result<Self> {
39        let memory_cache = Arc::new(RwLock::new(MemoryCache::new(config.clone())));
40
41        let persistent_cache = if config.enable_persistent {
42            Some(Arc::new(PersistentCache::new(config.clone())?))
43        } else {
44            None
45        };
46
47        Ok(Self {
48            memory_cache,
49            persistent_cache,
50            config,
51            stats: Arc::new(RwLock::new(MultiLevelCacheStats::default())),
52        })
53    }
54
55    /// Insert entry into cache
56    pub fn insert(&self, key: CacheKey, data: crate::Vector) -> Result<()> {
57        let entry = CacheEntry::new(data);
58
59        // Insert into memory cache
60        {
61            let mut memory = self.memory_cache.write().expect("lock poisoned");
62            memory.insert(key.clone(), entry.clone())?;
63        }
64
65        // Insert into persistent cache
66        if let Some(ref persistent) = self.persistent_cache {
67            persistent.store(&key, &entry)?;
68        }
69
70        Ok(())
71    }
72
73    /// Get entry from cache
74    pub fn get(&self, key: &CacheKey) -> Option<crate::Vector> {
75        self.update_stats_total();
76
77        // Try memory cache first
78        {
79            let mut memory = self.memory_cache.write().expect("lock poisoned");
80            if let Some(data) = memory.get(key) {
81                self.update_stats_memory_hit();
82                return Some(data.clone());
83            }
84        }
85
86        self.update_stats_memory_miss();
87
88        // Try persistent cache
89        if let Some(ref persistent) = self.persistent_cache {
90            if let Ok(Some(mut entry)) = persistent.load(key) {
91                self.update_stats_persistent_hit();
92
93                // Promote to memory cache
94                let data = entry.data.clone();
95                entry.touch();
96                if let Ok(mut memory) = self.memory_cache.write() {
97                    let _ = memory.insert(key.clone(), entry);
98                }
99
100                return Some(data);
101            }
102        }
103
104        self.update_stats_persistent_miss();
105        None
106    }
107
108    /// Remove entry from cache
109    pub fn remove(&self, key: &CacheKey) -> Result<()> {
110        // Remove from memory cache
111        {
112            let mut memory = self.memory_cache.write().expect("lock poisoned");
113            memory.remove(key);
114        }
115
116        // Remove from persistent cache
117        if let Some(ref persistent) = self.persistent_cache {
118            persistent.remove(key)?;
119        }
120
121        Ok(())
122    }
123
124    /// Clear all caches
125    pub fn clear(&self) -> Result<()> {
126        // Clear memory cache
127        {
128            let mut memory = self.memory_cache.write().expect("lock poisoned");
129            memory.clear();
130        }
131
132        // Clear persistent cache
133        if let Some(ref persistent) = self.persistent_cache {
134            persistent.clear()?;
135        }
136
137        // Reset stats
138        {
139            let mut stats = self.stats.write().expect("lock poisoned");
140            *stats = MultiLevelCacheStats::default();
141        }
142
143        Ok(())
144    }
145
146    /// Get cache statistics
147    pub fn get_stats(&self) -> MultiLevelCacheStats {
148        self.stats.read().expect("lock poisoned").clone()
149    }
150
151    /// Get memory cache statistics
152    pub fn get_memory_stats(&self) -> CacheStats {
153        let memory = self.memory_cache.read().expect("lock poisoned");
154        memory.stats()
155    }
156
157    // Stats update methods
158    fn update_stats_total(&self) {
159        let mut stats = self.stats.write().expect("lock poisoned");
160        stats.total_requests += 1;
161    }
162
163    fn update_stats_memory_hit(&self) {
164        let mut stats = self.stats.write().expect("lock poisoned");
165        stats.memory_hits += 1;
166    }
167
168    fn update_stats_memory_miss(&self) {
169        let mut stats = self.stats.write().expect("lock poisoned");
170        stats.memory_misses += 1;
171    }
172
173    fn update_stats_persistent_hit(&self) {
174        let mut stats = self.stats.write().expect("lock poisoned");
175        stats.persistent_hits += 1;
176    }
177
178    fn update_stats_persistent_miss(&self) {
179        let mut stats = self.stats.write().expect("lock poisoned");
180        stats.persistent_misses += 1;
181    }
182}
183
184// ---------------------------------------------------------------------------
185// CacheInvalidator
186// ---------------------------------------------------------------------------
187
188/// Cache invalidation utilities with indexing support
189pub struct CacheInvalidator {
190    cache: Arc<MultiLevelCache>,
191    tag_index: TagIndex, // tag_key -> tag_value -> keys
192    namespace_index: Arc<RwLock<HashMap<String, Vec<CacheKey>>>>, // namespace -> keys
193}
194
195impl CacheInvalidator {
196    pub fn new(cache: Arc<MultiLevelCache>) -> Self {
197        Self {
198            cache,
199            tag_index: Arc::new(RwLock::new(HashMap::new())),
200            namespace_index: Arc::new(RwLock::new(HashMap::new())),
201        }
202    }
203
204    /// Register a cache entry for invalidation tracking
205    pub fn register_entry(&self, key: &CacheKey, tags: &HashMap<String, String>) {
206        // Index by namespace
207        {
208            let mut ns_index = self.namespace_index.write().expect("lock poisoned");
209            ns_index
210                .entry(key.namespace.clone())
211                .or_default()
212                .push(key.clone());
213        }
214
215        // Index by tags
216        {
217            let mut tag_idx = self.tag_index.write().expect("lock poisoned");
218            for (tag_key, tag_value) in tags {
219                tag_idx
220                    .entry(tag_key.clone())
221                    .or_default()
222                    .entry(tag_value.clone())
223                    .or_default()
224                    .push(key.clone());
225            }
226        }
227    }
228
229    /// Unregister a cache entry from invalidation tracking
230    pub fn unregister_entry(&self, key: &CacheKey) {
231        // Remove from namespace index
232        {
233            let mut ns_index = self.namespace_index.write().expect("lock poisoned");
234            if let Some(keys) = ns_index.get_mut(&key.namespace) {
235                keys.retain(|k| k != key);
236                if keys.is_empty() {
237                    ns_index.remove(&key.namespace);
238                }
239            }
240        }
241
242        // Remove from tag index
243        {
244            let mut tag_idx = self.tag_index.write().expect("lock poisoned");
245            let mut tags_to_remove = Vec::new();
246
247            for (tag_key, tag_values) in tag_idx.iter_mut() {
248                let mut values_to_remove = Vec::new();
249
250                for (tag_value, keys) in tag_values.iter_mut() {
251                    keys.retain(|k| k != key);
252                    if keys.is_empty() {
253                        values_to_remove.push(tag_value.clone());
254                    }
255                }
256
257                for value in values_to_remove {
258                    tag_values.remove(&value);
259                }
260
261                if tag_values.is_empty() {
262                    tags_to_remove.push(tag_key.clone());
263                }
264            }
265
266            for tag in tags_to_remove {
267                tag_idx.remove(&tag);
268            }
269        }
270    }
271
272    /// Invalidate entries by tag
273    pub fn invalidate_by_tag(&self, tag_key: &str, tag_value: &str) -> Result<usize> {
274        let keys_to_invalidate = {
275            let tag_idx = self.tag_index.read().expect("lock poisoned");
276            tag_idx
277                .get(tag_key)
278                .and_then(|values| values.get(tag_value))
279                .cloned()
280                .unwrap_or_default()
281        };
282
283        let mut invalidated_count = 0;
284        for key in &keys_to_invalidate {
285            if self.cache.remove(key).is_ok() {
286                invalidated_count += 1;
287            }
288            self.unregister_entry(key);
289        }
290
291        Ok(invalidated_count)
292    }
293
294    /// Invalidate entries by namespace
295    pub fn invalidate_namespace(&self, namespace: &str) -> Result<usize> {
296        let keys_to_invalidate = {
297            let ns_index = self.namespace_index.read().expect("lock poisoned");
298            ns_index.get(namespace).cloned().unwrap_or_default()
299        };
300
301        let mut invalidated_count = 0;
302        for key in &keys_to_invalidate {
303            if self.cache.remove(key).is_ok() {
304                invalidated_count += 1;
305            }
306            self.unregister_entry(key);
307        }
308
309        Ok(invalidated_count)
310    }
311
312    /// Invalidate all expired entries
313    pub fn invalidate_expired(&self) -> Result<usize> {
314        // Memory cache cleans expired entries automatically during operations.
315        // For persistent cache, scan and remove expired files.
316        if let Some(ref persistent) = self.cache.persistent_cache {
317            return self.scan_and_remove_expired_files(persistent);
318        }
319        Ok(0)
320    }
321
322    /// Scan persistent cache directory and remove expired files
323    fn scan_and_remove_expired_files(&self, persistent_cache: &PersistentCache) -> Result<usize> {
324        let cache_dir = &persistent_cache.cache_dir;
325        let mut removed_count = 0;
326
327        if !cache_dir.exists() {
328            return Ok(0);
329        }
330
331        // Walk through all cache files
332        for entry in std::fs::read_dir(cache_dir)? {
333            let entry = entry?;
334            if entry.file_type()?.is_dir() {
335                // Recursively scan subdirectories
336                for sub_entry in std::fs::read_dir(entry.path())? {
337                    let sub_entry = sub_entry?;
338                    if sub_entry.file_type()?.is_file() {
339                        if let Some(file_name) = sub_entry.file_name().to_str() {
340                            if file_name.ends_with(".cache") {
341                                // Decode cache key from filename
342                                if let Some(cache_key) =
343                                    persistent_cache.decode_cache_key_from_filename(file_name)
344                                {
345                                    // Load the actual cache entry to check expiration
346                                    if let Ok(Some(loaded)) = persistent_cache.load(&cache_key) {
347                                        if loaded.is_expired() {
348                                            let _ = std::fs::remove_file(sub_entry.path());
349                                            removed_count += 1;
350                                        }
351                                    } else {
352                                        // Corrupted entry — remove it
353                                        let _ = std::fs::remove_file(sub_entry.path());
354                                        removed_count += 1;
355                                    }
356                                } else {
357                                    // Old format — fall back to file-age heuristic
358                                    if let Ok(metadata) = std::fs::metadata(sub_entry.path()) {
359                                        if let Ok(modified) = metadata.modified() {
360                                            let age = modified
361                                                .elapsed()
362                                                .unwrap_or(Duration::from_secs(0));
363                                            // Remove files older than 24 hours
364                                            if age > Duration::from_secs(24 * 3600) {
365                                                let _ = std::fs::remove_file(sub_entry.path());
366                                                removed_count += 1;
367                                            }
368                                        }
369                                    }
370                                }
371                            }
372                        }
373                    }
374                }
375            }
376        }
377
378        Ok(removed_count)
379    }
380
381    /// Get invalidation statistics
382    pub fn get_stats(&self) -> InvalidationStats {
383        let tag_idx = self.tag_index.read().expect("lock poisoned");
384        let ns_index = self.namespace_index.read().expect("lock poisoned");
385
386        let total_tag_entries = tag_idx
387            .values()
388            .flat_map(|values| values.values())
389            .map(|keys| keys.len())
390            .sum();
391
392        let total_namespace_entries = ns_index.values().map(|keys| keys.len()).sum();
393
394        InvalidationStats {
395            tracked_tags: tag_idx.len(),
396            tracked_namespaces: ns_index.len(),
397            total_tag_entries,
398            total_namespace_entries,
399        }
400    }
401}
402
403/// Statistics for cache invalidation tracking
404#[derive(Debug, Clone)]
405pub struct InvalidationStats {
406    pub tracked_tags: usize,
407    pub tracked_namespaces: usize,
408    pub total_tag_entries: usize,
409    pub total_namespace_entries: usize,
410}