dx_forge/
cache.rs

1//! Multi-layer Caching System
2//!
3//! Provides LRU caching for operations, blob content, and query results.
4
5use std::collections::HashMap;
6use std::hash::Hash;
7use std::sync::Arc;
8use parking_lot::RwLock;
9use serde::{Deserialize, Serialize};
10
11/// LRU Cache entry
12struct CacheEntry<V> {
13    value: V,
14    access_count: u64,
15    last_accessed: std::time::Instant,
16}
17
18/// LRU Cache implementation
19pub struct LruCache<K, V>
20where
21    K: Eq + Hash + Clone,
22    V: Clone,
23{
24    cache: Arc<RwLock<HashMap<K, CacheEntry<V>>>>,
25    capacity: usize,
26    hits: Arc<RwLock<u64>>,
27    misses: Arc<RwLock<u64>>,
28}
29
30impl<K, V> LruCache<K, V>
31where
32    K: Eq + Hash + Clone,
33    V: Clone,
34{
35    /// Create a new LRU cache with given capacity
36    pub fn new(capacity: usize) -> Self {
37        Self {
38            cache: Arc::new(RwLock::new(HashMap::new())),
39            capacity,
40            hits: Arc::new(RwLock::new(0)),
41            misses: Arc::new(RwLock::new(0)),
42        }
43    }
44
45    /// Get a value from the cache
46    pub fn get(&self, key: &K) -> Option<V> {
47        let mut cache = self.cache.write();
48        
49        if let Some(entry) = cache.get_mut(key) {
50            entry.access_count += 1;
51            entry.last_accessed = std::time::Instant::now();
52            *self.hits.write() += 1;
53            Some(entry.value.clone())
54        } else {
55            *self.misses.write() += 1;
56            None
57        }
58    }
59
60    /// Insert a value into the cache
61    pub fn insert(&self, key: K, value: V) {
62        let mut cache = self.cache.write();
63
64        // Check if we need to evict
65        if cache.len() >= self.capacity && !cache.contains_key(&key) {
66            self.evict_lru(&mut cache);
67        }
68
69        cache.insert(
70            key,
71            CacheEntry {
72                value,
73                access_count: 1,
74                last_accessed: std::time::Instant::now(),
75            },
76        );
77    }
78
79    /// Evict least recently used entry
80    fn evict_lru(&self, cache: &mut HashMap<K, CacheEntry<V>>) {
81        if let Some((key_to_remove, _)) = cache
82            .iter()
83            .min_by_key(|(_, entry)| entry.last_accessed)
84        {
85            let key = key_to_remove.clone();
86            cache.remove(&key);
87        }
88    }
89
90    /// Remove a value from the cache
91    pub fn remove(&self, key: &K) {
92        let mut cache = self.cache.write();
93        cache.remove(key);
94    }
95
96    /// Clear the entire cache
97    pub fn clear(&self) {
98        let mut cache = self.cache.write();
99        cache.clear();
100        *self.hits.write() = 0;
101        *self.misses.write() = 0;
102    }
103
104    /// Get cache statistics
105    pub fn stats(&self) -> CacheStats {
106        let hits = *self.hits.read();
107        let misses = *self.misses.read();
108        let total = hits + misses;
109        let hit_rate = if total > 0 {
110            hits as f64 / total as f64
111        } else {
112            0.0
113        };
114
115        CacheStats {
116            hits,
117            misses,
118            hit_rate,
119            size: self.cache.read().len(),
120            capacity: self.capacity,
121        }
122    }
123
124    /// Get current size
125    pub fn len(&self) -> usize {
126        self.cache.read().len()
127    }
128
129    /// Check if cache is empty
130    pub fn is_empty(&self) -> bool {
131        self.cache.read().is_empty()
132    }
133}
134
135/// Cache statistics
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct CacheStats {
138    pub hits: u64,
139    pub misses: u64,
140    pub hit_rate: f64,
141    pub size: usize,
142    pub capacity: usize,
143}
144
145/// Multi-layer cache manager
146pub struct CacheManager {
147    /// Cache for CRDT operations
148    operation_cache: LruCache<String, Vec<u8>>,
149    
150    /// Cache for blob content
151    blob_cache: LruCache<String, Vec<u8>>,
152    
153    /// Cache for query results
154    query_cache: LruCache<String, String>,
155}
156
157impl CacheManager {
158    /// Create a new cache manager
159    pub fn new() -> Self {
160        Self {
161            operation_cache: LruCache::new(1000),
162            blob_cache: LruCache::new(100),
163            query_cache: LruCache::new(500),
164        }
165    }
166
167    /// Create with custom capacities
168    pub fn with_capacities(op_capacity: usize, blob_capacity: usize, query_capacity: usize) -> Self {
169        Self {
170            operation_cache: LruCache::new(op_capacity),
171            blob_cache: LruCache::new(blob_capacity),
172            query_cache: LruCache::new(query_capacity),
173        }
174    }
175
176    /// Get operation from cache
177    pub fn get_operation(&self, key: &str) -> Option<Vec<u8>> {
178        self.operation_cache.get(&key.to_string())
179    }
180
181    /// Cache an operation
182    pub fn cache_operation(&self, key: String, data: Vec<u8>) {
183        self.operation_cache.insert(key, data);
184    }
185
186    /// Get blob from cache
187    pub fn get_blob(&self, hash: &str) -> Option<Vec<u8>> {
188        self.blob_cache.get(&hash.to_string())
189    }
190
191    /// Cache a blob
192    pub fn cache_blob(&self, hash: String, content: Vec<u8>) {
193        self.blob_cache.insert(hash, content);
194    }
195
196    /// Get query result from cache
197    pub fn get_query(&self, query: &str) -> Option<String> {
198        self.query_cache.get(&query.to_string())
199    }
200
201    /// Cache a query result
202    pub fn cache_query(&self, query: String, result: String) {
203        self.query_cache.insert(query, result);
204    }
205
206    /// Clear all caches
207    pub fn clear_all(&self) {
208        self.operation_cache.clear();
209        self.blob_cache.clear();
210        self.query_cache.clear();
211    }
212
213    /// Get combined statistics
214    pub fn get_stats(&self) -> CombinedStats {
215        CombinedStats {
216            operation_cache: self.operation_cache.stats(),
217            blob_cache: self.blob_cache.stats(),
218            query_cache: self.query_cache.stats(),
219        }
220    }
221
222    /// Print cache statistics
223    pub fn print_stats(&self) {
224        let stats = self.get_stats();
225        
226        println!("\n=== Cache Statistics ===\n");
227        
228        println!("Operation Cache:");
229        print_cache_stats(&stats.operation_cache);
230        
231        println!("\nBlob Cache:");
232        print_cache_stats(&stats.blob_cache);
233        
234        println!("\nQuery Cache:");
235        print_cache_stats(&stats.query_cache);
236        
237        let total_hits = stats.operation_cache.hits + stats.blob_cache.hits + stats.query_cache.hits;
238        let total_misses = stats.operation_cache.misses + stats.blob_cache.misses + stats.query_cache.misses;
239        let overall_hit_rate = if total_hits + total_misses > 0 {
240            total_hits as f64 / (total_hits + total_misses) as f64
241        } else {
242            0.0
243        };
244        
245        println!("\nOverall Hit Rate: {:.2}%", overall_hit_rate * 100.0);
246        println!();
247    }
248}
249
250impl Default for CacheManager {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256fn print_cache_stats(stats: &CacheStats) {
257    println!("  Size: {}/{}", stats.size, stats.capacity);
258    println!("  Hits: {}", stats.hits);
259    println!("  Misses: {}", stats.misses);
260    println!("  Hit Rate: {:.2}%", stats.hit_rate * 100.0);
261}
262
263/// Combined cache statistics
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct CombinedStats {
266    pub operation_cache: CacheStats,
267    pub blob_cache: CacheStats,
268    pub query_cache: CacheStats,
269}
270
271/// Global cache manager instance
272static GLOBAL_CACHE: once_cell::sync::Lazy<CacheManager> = 
273    once_cell::sync::Lazy::new(|| CacheManager::new());
274
275/// Get the global cache manager
276pub fn global_cache() -> &'static CacheManager {
277    &GLOBAL_CACHE
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_lru_cache() {
286        let cache = LruCache::new(3);
287
288        cache.insert("a", 1);
289        cache.insert("b", 2);
290        cache.insert("c", 3);
291
292        assert_eq!(cache.get(&"a"), Some(1));
293        assert_eq!(cache.get(&"b"), Some(2));
294        assert_eq!(cache.get(&"c"), Some(3));
295
296        // This should evict the least recently used (which is "a" again after get)
297        cache.insert("d", 4);
298        assert_eq!(cache.len(), 3);
299    }
300
301    #[test]
302    fn test_cache_stats() {
303        let cache = LruCache::new(10);
304
305        cache.insert("a", 1);
306        cache.insert("b", 2);
307
308        let _ = cache.get(&"a"); // hit
309        let _ = cache.get(&"a"); // hit
310        let _ = cache.get(&"c"); // miss
311
312        let stats = cache.stats();
313        assert_eq!(stats.hits, 2);
314        assert_eq!(stats.misses, 1);
315        assert_eq!(stats.size, 2);
316    }
317
318    #[test]
319    fn test_cache_manager() {
320        let manager = CacheManager::new();
321
322        manager.cache_operation("op1".to_string(), vec![1, 2, 3]);
323        manager.cache_blob("hash1".to_string(), vec![4, 5, 6]);
324        manager.cache_query("query1".to_string(), "result1".to_string());
325
326        assert_eq!(manager.get_operation("op1"), Some(vec![1, 2, 3]));
327        assert_eq!(manager.get_blob("hash1"), Some(vec![4, 5, 6]));
328        assert_eq!(manager.get_query("query1"), Some("result1".to_string()));
329    }
330}