oxirs_vec/
advanced_caching.rs

1//! Advanced multi-level caching system for vector embeddings and search results
2//!
3//! This module provides:
4//! - Multi-level caching (memory + persistent)
5//! - LRU, LFU, ARC eviction policies
6//! - TTL expiration
7//! - Cache coherence and invalidation
8//! - Background cache updates
9
10use crate::Vector;
11use anyhow::{anyhow, Result};
12use serde::{Deserialize, Serialize};
13use std::collections::{HashMap, VecDeque};
14use std::fmt;
15use std::hash::{Hash, Hasher};
16use std::sync::{Arc, RwLock};
17use std::thread::{self, JoinHandle};
18use std::time::{Duration, Instant};
19
20/// Type alias for complex tag index structure
21type TagIndex = Arc<RwLock<HashMap<String, HashMap<String, Vec<CacheKey>>>>>;
22
23/// Cache eviction policy
24#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25pub enum EvictionPolicy {
26    /// Least Recently Used
27    LRU,
28    /// Least Frequently Used
29    LFU,
30    /// Adaptive Replacement Cache
31    ARC,
32    /// First In, First Out
33    FIFO,
34    /// Time-based expiration only
35    TTL,
36}
37
38/// Cache configuration
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CacheConfig {
41    /// Maximum number of entries in memory cache
42    pub max_memory_entries: usize,
43    /// Maximum memory usage in bytes
44    pub max_memory_bytes: usize,
45    /// Time-to-live for cache entries
46    pub ttl: Option<Duration>,
47    /// Eviction policy
48    pub eviction_policy: EvictionPolicy,
49    /// Enable persistent cache
50    pub enable_persistent: bool,
51    /// Persistent cache directory
52    pub persistent_cache_dir: Option<std::path::PathBuf>,
53    /// Maximum persistent cache size in bytes
54    pub max_persistent_bytes: usize,
55    /// Enable cache compression
56    pub enable_compression: bool,
57    /// Enable background updates
58    pub enable_background_updates: bool,
59    /// Background update interval
60    pub background_update_interval: Duration,
61}
62
63impl Default for CacheConfig {
64    fn default() -> Self {
65        Self {
66            max_memory_entries: 10000,
67            max_memory_bytes: 1024 * 1024 * 100,  // 100MB
68            ttl: Some(Duration::from_secs(3600)), // 1 hour
69            eviction_policy: EvictionPolicy::LRU,
70            enable_persistent: true,
71            persistent_cache_dir: None,
72            max_persistent_bytes: 1024 * 1024 * 1024, // 1GB
73            enable_compression: true,
74            enable_background_updates: false,
75            background_update_interval: Duration::from_secs(300), // 5 minutes
76        }
77    }
78}
79
80/// Cache entry with metadata
81#[derive(Debug, Clone)]
82pub struct CacheEntry {
83    /// Cached data
84    pub data: Vector,
85    /// Creation timestamp
86    pub created_at: Instant,
87    /// Last access timestamp
88    pub last_accessed: Instant,
89    /// Access count for LFU
90    pub access_count: u64,
91    /// Entry size in bytes
92    pub size_bytes: usize,
93    /// TTL for this specific entry
94    pub ttl: Option<Duration>,
95    /// Metadata tags
96    pub tags: HashMap<String, String>,
97}
98
99impl CacheEntry {
100    pub fn new(data: Vector) -> Self {
101        let now = Instant::now();
102        let size_bytes = data.dimensions * std::mem::size_of::<f32>() + 64; // Rough estimate
103
104        Self {
105            data,
106            created_at: now,
107            last_accessed: now,
108            access_count: 1,
109            size_bytes,
110            ttl: None,
111            tags: HashMap::new(),
112        }
113    }
114
115    pub fn with_ttl(mut self, ttl: Duration) -> Self {
116        self.ttl = Some(ttl);
117        self
118    }
119
120    pub fn with_tags(mut self, tags: HashMap<String, String>) -> Self {
121        self.tags = tags;
122        self
123    }
124
125    /// Check if entry has expired
126    pub fn is_expired(&self) -> bool {
127        if let Some(ttl) = self.ttl {
128            self.created_at.elapsed() > ttl
129        } else {
130            false
131        }
132    }
133
134    /// Update access statistics
135    pub fn touch(&mut self) {
136        self.last_accessed = Instant::now();
137        self.access_count += 1;
138    }
139}
140
141/// Cache key that can be hashed
142#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
143pub struct CacheKey {
144    pub namespace: String,
145    pub key: String,
146    pub variant: Option<String>,
147}
148
149impl CacheKey {
150    pub fn new(namespace: impl Into<String>, key: impl Into<String>) -> Self {
151        Self {
152            namespace: namespace.into(),
153            key: key.into(),
154            variant: None,
155        }
156    }
157
158    pub fn with_variant(mut self, variant: impl Into<String>) -> Self {
159        self.variant = Some(variant.into());
160        self
161    }
162}
163
164impl fmt::Display for CacheKey {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        if let Some(ref variant) = self.variant {
167            write!(f, "{}:{}:{}", self.namespace, self.key, variant)
168        } else {
169            write!(f, "{}:{}", self.namespace, self.key)
170        }
171    }
172}
173
174/// Memory cache implementation
175pub struct MemoryCache {
176    config: CacheConfig,
177    entries: HashMap<CacheKey, CacheEntry>,
178    access_order: VecDeque<CacheKey>,      // For LRU
179    frequency_map: HashMap<CacheKey, u64>, // For LFU
180    current_memory_bytes: usize,
181    // ARC state
182    arc_t1: VecDeque<CacheKey>, // Recently accessed pages
183    arc_t2: VecDeque<CacheKey>, // Frequently accessed pages
184    arc_b1: VecDeque<CacheKey>, // Ghost list for T1
185    arc_b2: VecDeque<CacheKey>, // Ghost list for T2
186    arc_p: usize,               // Target size for T1
187}
188
189impl MemoryCache {
190    pub fn new(config: CacheConfig) -> Self {
191        Self {
192            config,
193            entries: HashMap::new(),
194            access_order: VecDeque::new(),
195            frequency_map: HashMap::new(),
196            current_memory_bytes: 0,
197            arc_t1: VecDeque::new(),
198            arc_t2: VecDeque::new(),
199            arc_b1: VecDeque::new(),
200            arc_b2: VecDeque::new(),
201            arc_p: 0,
202        }
203    }
204
205    /// Insert or update cache entry
206    pub fn insert(&mut self, key: CacheKey, entry: CacheEntry) -> Result<()> {
207        // Remove expired entries first
208        self.clean_expired();
209
210        // Check if we need to evict
211        while self.should_evict(&entry) {
212            self.evict_one()?;
213        }
214
215        // Remove existing entry if present
216        if let Some(old_entry) = self.entries.remove(&key) {
217            self.current_memory_bytes -= old_entry.size_bytes;
218            self.remove_from_tracking(&key);
219        }
220
221        // Insert new entry
222        self.current_memory_bytes += entry.size_bytes;
223        self.entries.insert(key.clone(), entry);
224        self.track_access(&key);
225
226        Ok(())
227    }
228
229    /// Get cache entry
230    pub fn get(&mut self, key: &CacheKey) -> Option<Vector> {
231        // Check if entry exists and is not expired
232        let should_remove = if let Some(entry) = self.entries.get(key) {
233            entry.is_expired()
234        } else {
235            false
236        };
237
238        if should_remove {
239            self.remove(key);
240            return None;
241        }
242
243        if let Some(entry) = self.entries.get_mut(key) {
244            let data = entry.data.clone();
245            entry.touch();
246            self.track_access(key);
247            Some(data)
248        } else {
249            None
250        }
251    }
252
253    /// Remove entry from cache
254    pub fn remove(&mut self, key: &CacheKey) -> Option<CacheEntry> {
255        if let Some(entry) = self.entries.remove(key) {
256            self.current_memory_bytes -= entry.size_bytes;
257            self.remove_from_tracking(key);
258            Some(entry)
259        } else {
260            None
261        }
262    }
263
264    /// Clear all entries
265    pub fn clear(&mut self) {
266        self.entries.clear();
267        self.access_order.clear();
268        self.frequency_map.clear();
269        self.current_memory_bytes = 0;
270    }
271
272    /// Check if eviction is needed
273    fn should_evict(&self, new_entry: &CacheEntry) -> bool {
274        self.entries.len() >= self.config.max_memory_entries
275            || self.current_memory_bytes + new_entry.size_bytes > self.config.max_memory_bytes
276    }
277
278    /// Evict one entry based on policy
279    fn evict_one(&mut self) -> Result<()> {
280        let key_to_evict = match self.config.eviction_policy {
281            EvictionPolicy::LRU => self.find_lru_key(),
282            EvictionPolicy::LFU => self.find_lfu_key(),
283            EvictionPolicy::ARC => self.find_arc_key(),
284            EvictionPolicy::FIFO => self.find_fifo_key(),
285            EvictionPolicy::TTL => self.find_expired_key(),
286        };
287
288        if let Some(key) = key_to_evict {
289            self.remove(&key);
290            Ok(())
291        } else if !self.entries.is_empty() {
292            // Fallback: remove first entry
293            let key = self.entries.keys().next().unwrap().clone();
294            self.remove(&key);
295            Ok(())
296        } else {
297            Err(anyhow!("No entries to evict"))
298        }
299    }
300
301    /// Find LRU key
302    fn find_lru_key(&self) -> Option<CacheKey> {
303        self.access_order.front().cloned()
304    }
305
306    /// Find LFU key
307    fn find_lfu_key(&self) -> Option<CacheKey> {
308        self.frequency_map
309            .iter()
310            .min_by_key(|&(_, &freq)| freq)
311            .map(|(key, _)| key.clone())
312    }
313
314    /// Find ARC key using Adaptive Replacement Cache algorithm
315    fn find_arc_key(&mut self) -> Option<CacheKey> {
316        let c = self.config.max_memory_entries;
317
318        // If T1 is not empty and |T1| > p, evict from T1
319        if !self.arc_t1.is_empty()
320            && (self.arc_t1.len() > self.arc_p
321                || (self.arc_t2.is_empty() && self.arc_t1.len() == self.arc_p))
322        {
323            if let Some(key) = self.arc_t1.pop_front() {
324                // Move to B1
325                self.arc_b1.push_back(key.clone());
326                if self.arc_b1.len() > c {
327                    self.arc_b1.pop_front();
328                }
329                return Some(key);
330            }
331        }
332
333        // Otherwise evict from T2
334        if let Some(key) = self.arc_t2.pop_front() {
335            // Move to B2
336            self.arc_b2.push_back(key.clone());
337            if self.arc_b2.len() > c {
338                self.arc_b2.pop_front();
339            }
340            return Some(key);
341        }
342
343        // Fallback to LRU if ARC lists are empty
344        self.find_lru_key()
345    }
346
347    /// Find FIFO key (oldest entry)
348    fn find_fifo_key(&self) -> Option<CacheKey> {
349        self.entries
350            .iter()
351            .min_by_key(|(_, entry)| entry.created_at)
352            .map(|(key, _)| key.clone())
353    }
354
355    /// Find expired key
356    fn find_expired_key(&self) -> Option<CacheKey> {
357        self.entries
358            .iter()
359            .find(|(_, entry)| entry.is_expired())
360            .map(|(key, _)| key.clone())
361    }
362
363    /// Track access for LRU/LFU/ARC
364    fn track_access(&mut self, key: &CacheKey) {
365        // Update LRU order
366        if let Some(pos) = self.access_order.iter().position(|k| k == key) {
367            self.access_order.remove(pos);
368        }
369        self.access_order.push_back(key.clone());
370
371        // Update LFU frequency
372        *self.frequency_map.entry(key.clone()).or_insert(0) += 1;
373
374        // Update ARC tracking
375        if self.config.eviction_policy == EvictionPolicy::ARC {
376            self.track_arc_access(key);
377        }
378    }
379
380    /// Track access for ARC algorithm
381    fn track_arc_access(&mut self, key: &CacheKey) {
382        let c = self.config.max_memory_entries;
383
384        // Check if key is in T1 or T2
385        if let Some(pos) = self.arc_t1.iter().position(|k| k == key) {
386            // Move from T1 to T2 (promote to frequent)
387            self.arc_t1.remove(pos);
388            self.arc_t2.push_back(key.clone());
389        } else if let Some(pos) = self.arc_t2.iter().position(|k| k == key) {
390            // Move to end of T2 (most recently used)
391            self.arc_t2.remove(pos);
392            self.arc_t2.push_back(key.clone());
393        } else if let Some(pos) = self.arc_b1.iter().position(|k| k == key) {
394            // Hit in B1: increase p and move to T2
395            self.arc_b1.remove(pos);
396            self.arc_p = (self.arc_p + 1.max(self.arc_b2.len() / self.arc_b1.len())).min(c);
397            self.arc_t2.push_back(key.clone());
398        } else if let Some(pos) = self.arc_b2.iter().position(|k| k == key) {
399            // Hit in B2: decrease p and move to T2
400            self.arc_b2.remove(pos);
401            self.arc_p = self
402                .arc_p
403                .saturating_sub(1.max(self.arc_b1.len() / self.arc_b2.len()));
404            self.arc_t2.push_back(key.clone());
405        } else {
406            // New key: add to T1
407            self.arc_t1.push_back(key.clone());
408        }
409    }
410
411    /// Remove from tracking structures
412    fn remove_from_tracking(&mut self, key: &CacheKey) {
413        if let Some(pos) = self.access_order.iter().position(|k| k == key) {
414            self.access_order.remove(pos);
415        }
416        self.frequency_map.remove(key);
417
418        // Remove from ARC structures
419        if self.config.eviction_policy == EvictionPolicy::ARC {
420            if let Some(pos) = self.arc_t1.iter().position(|k| k == key) {
421                self.arc_t1.remove(pos);
422            }
423            if let Some(pos) = self.arc_t2.iter().position(|k| k == key) {
424                self.arc_t2.remove(pos);
425            }
426            if let Some(pos) = self.arc_b1.iter().position(|k| k == key) {
427                self.arc_b1.remove(pos);
428            }
429            if let Some(pos) = self.arc_b2.iter().position(|k| k == key) {
430                self.arc_b2.remove(pos);
431            }
432        }
433    }
434
435    /// Clean expired entries
436    fn clean_expired(&mut self) {
437        let expired_keys: Vec<CacheKey> = self
438            .entries
439            .iter()
440            .filter(|(_, entry)| entry.is_expired())
441            .map(|(key, _)| key.clone())
442            .collect();
443
444        for key in expired_keys {
445            self.remove(&key);
446        }
447    }
448
449    /// Get cache statistics
450    pub fn stats(&self) -> CacheStats {
451        CacheStats {
452            entries: self.entries.len(),
453            memory_bytes: self.current_memory_bytes,
454            max_entries: self.config.max_memory_entries,
455            max_memory_bytes: self.config.max_memory_bytes,
456            hit_ratio: 0.0, // Would need to track hits/misses
457        }
458    }
459}
460
461/// Cache statistics
462#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct CacheStats {
464    pub entries: usize,
465    pub memory_bytes: usize,
466    pub max_entries: usize,
467    pub max_memory_bytes: usize,
468    pub hit_ratio: f32,
469}
470
471/// Persistent cache for disk storage
472pub struct PersistentCache {
473    config: CacheConfig,
474    cache_dir: std::path::PathBuf,
475}
476
477impl PersistentCache {
478    pub fn new(config: CacheConfig) -> Result<Self> {
479        let cache_dir = config
480            .persistent_cache_dir
481            .clone()
482            .unwrap_or_else(|| std::env::temp_dir().join("oxirs_vec_cache"));
483
484        std::fs::create_dir_all(&cache_dir)?;
485
486        Ok(Self { config, cache_dir })
487    }
488
489    /// Store entry to disk
490    pub fn store(&self, key: &CacheKey, entry: &CacheEntry) -> Result<()> {
491        let file_path = self.get_file_path(key);
492
493        if let Some(parent) = file_path.parent() {
494            std::fs::create_dir_all(parent)?;
495        }
496
497        let serialized = self.serialize_entry(entry)?;
498        let final_data = if self.config.enable_compression {
499            self.compress_data(&serialized)?
500        } else {
501            serialized
502        };
503
504        std::fs::write(file_path, final_data)?;
505        Ok(())
506    }
507
508    /// Load entry from disk
509    pub fn load(&self, key: &CacheKey) -> Result<Option<CacheEntry>> {
510        let file_path = self.get_file_path(key);
511
512        if !file_path.exists() {
513            return Ok(None);
514        }
515
516        let data = std::fs::read(&file_path)?;
517
518        let decompressed = if self.config.enable_compression {
519            self.decompress_data(&data)?
520        } else {
521            data
522        };
523
524        let entry = self.deserialize_entry(&decompressed)?;
525
526        // Check if entry has expired
527        if entry.is_expired() {
528            // Remove expired entry
529            let _ = std::fs::remove_file(file_path);
530            Ok(None)
531        } else {
532            Ok(Some(entry))
533        }
534    }
535
536    /// Remove entry from disk
537    pub fn remove(&self, key: &CacheKey) -> Result<()> {
538        let file_path = self.get_file_path(key);
539        if file_path.exists() {
540            std::fs::remove_file(file_path)?;
541        }
542        Ok(())
543    }
544
545    /// Clear all persistent cache
546    pub fn clear(&self) -> Result<()> {
547        if self.cache_dir.exists() {
548            std::fs::remove_dir_all(&self.cache_dir)?;
549            std::fs::create_dir_all(&self.cache_dir)?;
550        }
551        Ok(())
552    }
553
554    /// Get file path for cache key
555    fn get_file_path(&self, key: &CacheKey) -> std::path::PathBuf {
556        let key_str = key.to_string();
557        let hash = self.hash_key(&key_str);
558
559        // Create subdirectory structure to avoid too many files in one directory
560        let sub_dir = format!("{:02x}", (hash % 256) as u8);
561
562        // Encode key information in filename for reconstruction during cleanup
563        let encoded_key = self.encode_cache_key_for_filename(key);
564        let filename = format!("{hash:016x}_{encoded_key}.cache");
565
566        self.cache_dir.join(sub_dir).join(filename)
567    }
568
569    /// Encode cache key information into filename-safe format
570    fn encode_cache_key_for_filename(&self, key: &CacheKey) -> String {
571        let key_data = serde_json::json!({
572            "namespace": key.namespace,
573            "key": key.key,
574            "variant": key.variant
575        });
576
577        // Use base64 encoding to safely include key information in filename
578        use base64::{engine::general_purpose, Engine as _};
579        general_purpose::URL_SAFE_NO_PAD.encode(key_data.to_string().as_bytes())
580    }
581
582    /// Decode cache key from filename
583    fn decode_cache_key_from_filename(&self, filename: &str) -> Option<CacheKey> {
584        if let Some(encoded_part) = filename
585            .strip_suffix(".cache")
586            .and_then(|s| s.split('_').nth(1))
587        {
588            use base64::{engine::general_purpose, Engine as _};
589            if let Ok(decoded_bytes) = general_purpose::URL_SAFE_NO_PAD.decode(encoded_part) {
590                if let Ok(decoded_str) = String::from_utf8(decoded_bytes) {
591                    if let Ok(key_data) = serde_json::from_str::<serde_json::Value>(&decoded_str) {
592                        return Some(CacheKey {
593                            namespace: key_data["namespace"].as_str()?.to_string(),
594                            key: key_data["key"].as_str()?.to_string(),
595                            variant: key_data["variant"].as_str().map(|s| s.to_string()),
596                        });
597                    }
598                }
599            }
600        }
601        None
602    }
603
604    /// Hash cache key
605    fn hash_key(&self, key: &str) -> u64 {
606        let mut hasher = std::collections::hash_map::DefaultHasher::new();
607        key.hash(&mut hasher);
608        hasher.finish()
609    }
610
611    /// Serialize cache entry to bytes
612    fn serialize_entry(&self, entry: &CacheEntry) -> Result<Vec<u8>> {
613        // Custom binary serialization since CacheEntry has Instant fields
614        let mut data = Vec::new();
615
616        // Serialize vector data
617        let vector_data = &entry.data.as_f32();
618        data.extend_from_slice(&(vector_data.len() as u32).to_le_bytes());
619        for &value in vector_data {
620            data.extend_from_slice(&value.to_le_bytes());
621        }
622
623        // Serialize timestamps as epoch nanos from creation
624        let created_nanos = entry.created_at.elapsed().as_nanos() as u64;
625        let accessed_nanos = entry.last_accessed.elapsed().as_nanos() as u64;
626        data.extend_from_slice(&created_nanos.to_le_bytes());
627        data.extend_from_slice(&accessed_nanos.to_le_bytes());
628
629        // Serialize other fields
630        data.extend_from_slice(&entry.access_count.to_le_bytes());
631        data.extend_from_slice(&(entry.size_bytes as u64).to_le_bytes());
632
633        // Serialize TTL
634        if let Some(ttl) = entry.ttl {
635            data.push(1); // TTL present
636            data.extend_from_slice(&ttl.as_nanos().to_le_bytes());
637        } else {
638            data.push(0); // No TTL
639        }
640
641        // Serialize tags
642        data.extend_from_slice(&(entry.tags.len() as u32).to_le_bytes());
643        for (key, value) in &entry.tags {
644            data.extend_from_slice(&(key.len() as u32).to_le_bytes());
645            data.extend_from_slice(key.as_bytes());
646            data.extend_from_slice(&(value.len() as u32).to_le_bytes());
647            data.extend_from_slice(value.as_bytes());
648        }
649
650        Ok(data)
651    }
652
653    /// Deserialize cache entry from bytes
654    fn deserialize_entry(&self, data: &[u8]) -> Result<CacheEntry> {
655        // Check if data is empty or too small
656        if data.len() < 4 {
657            return Err(anyhow::anyhow!(
658                "Invalid cache entry data: too small (expected at least 4 bytes, got {})",
659                data.len()
660            ));
661        }
662
663        let mut offset = 0;
664
665        // Deserialize vector data
666        let vector_len = u32::from_le_bytes([
667            data[offset],
668            data[offset + 1],
669            data[offset + 2],
670            data[offset + 3],
671        ]) as usize;
672        offset += 4;
673
674        let mut vector_data = Vec::with_capacity(vector_len);
675        for _ in 0..vector_len {
676            let value = f32::from_le_bytes([
677                data[offset],
678                data[offset + 1],
679                data[offset + 2],
680                data[offset + 3],
681            ]);
682            vector_data.push(value);
683            offset += 4;
684        }
685        let vector = Vector::new(vector_data);
686
687        // Deserialize timestamps (stored as elapsed nanos, convert back to Instant)
688        let created_nanos = u64::from_le_bytes([
689            data[offset],
690            data[offset + 1],
691            data[offset + 2],
692            data[offset + 3],
693            data[offset + 4],
694            data[offset + 5],
695            data[offset + 6],
696            data[offset + 7],
697        ]);
698        offset += 8;
699
700        let accessed_nanos = u64::from_le_bytes([
701            data[offset],
702            data[offset + 1],
703            data[offset + 2],
704            data[offset + 3],
705            data[offset + 4],
706            data[offset + 5],
707            data[offset + 6],
708            data[offset + 7],
709        ]);
710        offset += 8;
711
712        // Reconstruct timestamps (approximation - will be recent)
713        let now = Instant::now();
714        let created_at = now - Duration::from_nanos(created_nanos);
715        let last_accessed = now - Duration::from_nanos(accessed_nanos);
716
717        // Deserialize other fields
718        let access_count = u64::from_le_bytes([
719            data[offset],
720            data[offset + 1],
721            data[offset + 2],
722            data[offset + 3],
723            data[offset + 4],
724            data[offset + 5],
725            data[offset + 6],
726            data[offset + 7],
727        ]);
728        offset += 8;
729
730        let size_bytes = u64::from_le_bytes([
731            data[offset],
732            data[offset + 1],
733            data[offset + 2],
734            data[offset + 3],
735            data[offset + 4],
736            data[offset + 5],
737            data[offset + 6],
738            data[offset + 7],
739        ]) as usize;
740        offset += 8;
741
742        // Deserialize TTL
743        let ttl = if data[offset] == 1 {
744            offset += 1;
745            let ttl_nanos = u128::from_le_bytes([
746                data[offset],
747                data[offset + 1],
748                data[offset + 2],
749                data[offset + 3],
750                data[offset + 4],
751                data[offset + 5],
752                data[offset + 6],
753                data[offset + 7],
754                data[offset + 8],
755                data[offset + 9],
756                data[offset + 10],
757                data[offset + 11],
758                data[offset + 12],
759                data[offset + 13],
760                data[offset + 14],
761                data[offset + 15],
762            ]);
763            offset += 16;
764            Some(Duration::from_nanos(ttl_nanos as u64))
765        } else {
766            offset += 1;
767            None
768        };
769
770        // Deserialize tags
771        let tags_len = u32::from_le_bytes([
772            data[offset],
773            data[offset + 1],
774            data[offset + 2],
775            data[offset + 3],
776        ]) as usize;
777        offset += 4;
778
779        let mut tags = HashMap::new();
780        for _ in 0..tags_len {
781            let key_len = u32::from_le_bytes([
782                data[offset],
783                data[offset + 1],
784                data[offset + 2],
785                data[offset + 3],
786            ]) as usize;
787            offset += 4;
788            let key = String::from_utf8(data[offset..offset + key_len].to_vec())?;
789            offset += key_len;
790
791            let value_len = u32::from_le_bytes([
792                data[offset],
793                data[offset + 1],
794                data[offset + 2],
795                data[offset + 3],
796            ]) as usize;
797            offset += 4;
798            let value = String::from_utf8(data[offset..offset + value_len].to_vec())?;
799            offset += value_len;
800
801            tags.insert(key, value);
802        }
803
804        Ok(CacheEntry {
805            data: vector,
806            created_at,
807            last_accessed,
808            access_count,
809            size_bytes,
810            ttl,
811            tags,
812        })
813    }
814
815    /// Compress data using simple RLE compression
816    fn compress_data(&self, data: &[u8]) -> Result<Vec<u8>> {
817        // Simple run-length encoding for demonstration
818        let mut compressed = Vec::new();
819
820        if data.is_empty() {
821            return Ok(compressed);
822        }
823
824        let mut current_byte = data[0];
825        let mut count = 1u8;
826
827        for &byte in &data[1..] {
828            if byte == current_byte && count < 255 {
829                count += 1;
830            } else {
831                compressed.push(count);
832                compressed.push(current_byte);
833                current_byte = byte;
834                count = 1;
835            }
836        }
837
838        // Add the last run
839        compressed.push(count);
840        compressed.push(current_byte);
841
842        Ok(compressed)
843    }
844
845    /// Decompress data using RLE decompression
846    fn decompress_data(&self, data: &[u8]) -> Result<Vec<u8>> {
847        let mut decompressed = Vec::new();
848
849        if data.len() % 2 != 0 {
850            return Err(anyhow!("Invalid compressed data length"));
851        }
852
853        for chunk in data.chunks(2) {
854            let count = chunk[0];
855            let byte = chunk[1];
856
857            for _ in 0..count {
858                decompressed.push(byte);
859            }
860        }
861
862        Ok(decompressed)
863    }
864}
865
866/// Multi-level cache combining memory and persistent storage
867pub struct MultiLevelCache {
868    memory_cache: Arc<RwLock<MemoryCache>>,
869    persistent_cache: Option<Arc<PersistentCache>>,
870    #[allow(dead_code)]
871    config: CacheConfig,
872    stats: Arc<RwLock<MultiLevelCacheStats>>,
873}
874
875#[derive(Debug, Default, Clone)]
876pub struct MultiLevelCacheStats {
877    pub memory_hits: u64,
878    pub memory_misses: u64,
879    pub persistent_hits: u64,
880    pub persistent_misses: u64,
881    pub total_requests: u64,
882}
883
884impl MultiLevelCache {
885    pub fn new(config: CacheConfig) -> Result<Self> {
886        let memory_cache = Arc::new(RwLock::new(MemoryCache::new(config.clone())));
887
888        let persistent_cache = if config.enable_persistent {
889            Some(Arc::new(PersistentCache::new(config.clone())?))
890        } else {
891            None
892        };
893
894        Ok(Self {
895            memory_cache,
896            persistent_cache,
897            config,
898            stats: Arc::new(RwLock::new(MultiLevelCacheStats::default())),
899        })
900    }
901
902    /// Insert entry into cache
903    pub fn insert(&self, key: CacheKey, data: Vector) -> Result<()> {
904        let entry = CacheEntry::new(data);
905
906        // Insert into memory cache
907        {
908            let mut memory = self.memory_cache.write().unwrap();
909            memory.insert(key.clone(), entry.clone())?;
910        }
911
912        // Insert into persistent cache
913        if let Some(ref persistent) = self.persistent_cache {
914            persistent.store(&key, &entry)?;
915        }
916
917        Ok(())
918    }
919
920    /// Get entry from cache
921    pub fn get(&self, key: &CacheKey) -> Option<Vector> {
922        self.update_stats_total();
923
924        // Try memory cache first
925        {
926            let mut memory = self.memory_cache.write().unwrap();
927            if let Some(data) = memory.get(key) {
928                self.update_stats_memory_hit();
929                return Some(data.clone());
930            }
931        }
932
933        self.update_stats_memory_miss();
934
935        // Try persistent cache
936        if let Some(ref persistent) = self.persistent_cache {
937            if let Ok(Some(mut entry)) = persistent.load(key) {
938                self.update_stats_persistent_hit();
939
940                // Promote to memory cache
941                let data = entry.data.clone();
942                entry.touch();
943                if let Ok(mut memory) = self.memory_cache.write() {
944                    let _ = memory.insert(key.clone(), entry);
945                }
946
947                return Some(data);
948            }
949        }
950
951        self.update_stats_persistent_miss();
952        None
953    }
954
955    /// Remove entry from cache
956    pub fn remove(&self, key: &CacheKey) -> Result<()> {
957        // Remove from memory cache
958        {
959            let mut memory = self.memory_cache.write().unwrap();
960            memory.remove(key);
961        }
962
963        // Remove from persistent cache
964        if let Some(ref persistent) = self.persistent_cache {
965            persistent.remove(key)?;
966        }
967
968        Ok(())
969    }
970
971    /// Clear all caches
972    pub fn clear(&self) -> Result<()> {
973        // Clear memory cache
974        {
975            let mut memory = self.memory_cache.write().unwrap();
976            memory.clear();
977        }
978
979        // Clear persistent cache
980        if let Some(ref persistent) = self.persistent_cache {
981            persistent.clear()?;
982        }
983
984        // Reset stats
985        {
986            let mut stats = self.stats.write().unwrap();
987            *stats = MultiLevelCacheStats::default();
988        }
989
990        Ok(())
991    }
992
993    /// Get cache statistics
994    pub fn get_stats(&self) -> MultiLevelCacheStats {
995        self.stats.read().unwrap().clone()
996    }
997
998    /// Get memory cache statistics
999    pub fn get_memory_stats(&self) -> CacheStats {
1000        let memory = self.memory_cache.read().unwrap();
1001        memory.stats()
1002    }
1003
1004    // Stats update methods
1005    fn update_stats_total(&self) {
1006        let mut stats = self.stats.write().unwrap();
1007        stats.total_requests += 1;
1008    }
1009
1010    fn update_stats_memory_hit(&self) {
1011        let mut stats = self.stats.write().unwrap();
1012        stats.memory_hits += 1;
1013    }
1014
1015    fn update_stats_memory_miss(&self) {
1016        let mut stats = self.stats.write().unwrap();
1017        stats.memory_misses += 1;
1018    }
1019
1020    fn update_stats_persistent_hit(&self) {
1021        let mut stats = self.stats.write().unwrap();
1022        stats.persistent_hits += 1;
1023    }
1024
1025    fn update_stats_persistent_miss(&self) {
1026        let mut stats = self.stats.write().unwrap();
1027        stats.persistent_misses += 1;
1028    }
1029}
1030
1031/// Cache invalidation utilities with indexing support
1032pub struct CacheInvalidator {
1033    cache: Arc<MultiLevelCache>,
1034    tag_index: TagIndex, // tag_key -> tag_value -> keys
1035    namespace_index: Arc<RwLock<HashMap<String, Vec<CacheKey>>>>, // namespace -> keys
1036}
1037
1038impl CacheInvalidator {
1039    pub fn new(cache: Arc<MultiLevelCache>) -> Self {
1040        Self {
1041            cache,
1042            tag_index: Arc::new(RwLock::new(HashMap::new())),
1043            namespace_index: Arc::new(RwLock::new(HashMap::new())),
1044        }
1045    }
1046
1047    /// Register a cache entry for invalidation tracking
1048    pub fn register_entry(&self, key: &CacheKey, tags: &HashMap<String, String>) {
1049        // Index by namespace
1050        {
1051            let mut ns_index = self.namespace_index.write().unwrap();
1052            ns_index
1053                .entry(key.namespace.clone())
1054                .or_default()
1055                .push(key.clone());
1056        }
1057
1058        // Index by tags
1059        {
1060            let mut tag_idx = self.tag_index.write().unwrap();
1061            for (tag_key, tag_value) in tags {
1062                tag_idx
1063                    .entry(tag_key.clone())
1064                    .or_default()
1065                    .entry(tag_value.clone())
1066                    .or_default()
1067                    .push(key.clone());
1068            }
1069        }
1070    }
1071
1072    /// Unregister a cache entry from invalidation tracking
1073    pub fn unregister_entry(&self, key: &CacheKey) {
1074        // Remove from namespace index
1075        {
1076            let mut ns_index = self.namespace_index.write().unwrap();
1077            if let Some(keys) = ns_index.get_mut(&key.namespace) {
1078                keys.retain(|k| k != key);
1079                if keys.is_empty() {
1080                    ns_index.remove(&key.namespace);
1081                }
1082            }
1083        }
1084
1085        // Remove from tag index
1086        {
1087            let mut tag_idx = self.tag_index.write().unwrap();
1088            let mut tags_to_remove = Vec::new();
1089
1090            for (tag_key, tag_values) in tag_idx.iter_mut() {
1091                let mut values_to_remove = Vec::new();
1092
1093                for (tag_value, keys) in tag_values.iter_mut() {
1094                    keys.retain(|k| k != key);
1095                    if keys.is_empty() {
1096                        values_to_remove.push(tag_value.clone());
1097                    }
1098                }
1099
1100                for value in values_to_remove {
1101                    tag_values.remove(&value);
1102                }
1103
1104                if tag_values.is_empty() {
1105                    tags_to_remove.push(tag_key.clone());
1106                }
1107            }
1108
1109            for tag in tags_to_remove {
1110                tag_idx.remove(&tag);
1111            }
1112        }
1113    }
1114
1115    /// Invalidate entries by tag
1116    pub fn invalidate_by_tag(&self, tag_key: &str, tag_value: &str) -> Result<usize> {
1117        let keys_to_invalidate = {
1118            let tag_idx = self.tag_index.read().unwrap();
1119            tag_idx
1120                .get(tag_key)
1121                .and_then(|values| values.get(tag_value))
1122                .cloned()
1123                .unwrap_or_default()
1124        };
1125
1126        let mut invalidated_count = 0;
1127        for key in &keys_to_invalidate {
1128            if self.cache.remove(key).is_ok() {
1129                invalidated_count += 1;
1130            }
1131            self.unregister_entry(key);
1132        }
1133
1134        Ok(invalidated_count)
1135    }
1136
1137    /// Invalidate entries by namespace
1138    pub fn invalidate_namespace(&self, namespace: &str) -> Result<usize> {
1139        let keys_to_invalidate = {
1140            let ns_index = self.namespace_index.read().unwrap();
1141            ns_index.get(namespace).cloned().unwrap_or_default()
1142        };
1143
1144        let mut invalidated_count = 0;
1145        for key in &keys_to_invalidate {
1146            if self.cache.remove(key).is_ok() {
1147                invalidated_count += 1;
1148            }
1149            self.unregister_entry(key);
1150        }
1151
1152        Ok(invalidated_count)
1153    }
1154
1155    /// Invalidate all expired entries
1156    pub fn invalidate_expired(&self) -> Result<usize> {
1157        // Memory cache cleans expired entries automatically during operations
1158        // For persistent cache, we need to scan and remove expired files
1159        if let Some(ref persistent) = self.cache.persistent_cache {
1160            return self.scan_and_remove_expired_files(persistent);
1161        }
1162        Ok(0)
1163    }
1164
1165    /// Scan persistent cache directory and remove expired files
1166    fn scan_and_remove_expired_files(&self, persistent_cache: &PersistentCache) -> Result<usize> {
1167        let cache_dir = &persistent_cache.cache_dir;
1168        let mut removed_count = 0;
1169
1170        if !cache_dir.exists() {
1171            return Ok(0);
1172        }
1173
1174        // Walk through all cache files
1175        for entry in std::fs::read_dir(cache_dir)? {
1176            let entry = entry?;
1177            if entry.file_type()?.is_dir() {
1178                // Recursively scan subdirectories
1179                for sub_entry in std::fs::read_dir(entry.path())? {
1180                    let sub_entry = sub_entry?;
1181                    if sub_entry.file_type()?.is_file() {
1182                        if let Some(file_name) = sub_entry.file_name().to_str() {
1183                            if file_name.ends_with(".cache") {
1184                                // Decode cache key from filename - no more hacks!
1185                                if let Some(cache_key) =
1186                                    persistent_cache.decode_cache_key_from_filename(file_name)
1187                                {
1188                                    // Load the actual cache entry to check expiration
1189                                    if let Ok(Some(entry)) = persistent_cache.load(&cache_key) {
1190                                        if entry.is_expired() {
1191                                            let _ = std::fs::remove_file(sub_entry.path());
1192                                            removed_count += 1;
1193                                        }
1194                                    } else {
1195                                        // If we can't load the entry, it might be corrupted - remove it
1196                                        let _ = std::fs::remove_file(sub_entry.path());
1197                                        removed_count += 1;
1198                                    }
1199                                } else {
1200                                    // If we can't decode the key, it might be an old format - use file age as fallback
1201                                    if let Ok(metadata) = std::fs::metadata(sub_entry.path()) {
1202                                        if let Ok(modified) = metadata.modified() {
1203                                            let age = modified
1204                                                .elapsed()
1205                                                .unwrap_or(Duration::from_secs(0));
1206                                            // Remove files older than 24 hours as fallback for old cache files
1207                                            if age > Duration::from_secs(24 * 3600) {
1208                                                let _ = std::fs::remove_file(sub_entry.path());
1209                                                removed_count += 1;
1210                                            }
1211                                        }
1212                                    }
1213                                }
1214                            }
1215                        }
1216                    }
1217                }
1218            }
1219        }
1220
1221        Ok(removed_count)
1222    }
1223
1224    /// Get invalidation statistics
1225    pub fn get_stats(&self) -> InvalidationStats {
1226        let tag_idx = self.tag_index.read().unwrap();
1227        let ns_index = self.namespace_index.read().unwrap();
1228
1229        let total_tag_entries = tag_idx
1230            .values()
1231            .flat_map(|values| values.values())
1232            .map(|keys| keys.len())
1233            .sum();
1234
1235        let total_namespace_entries = ns_index.values().map(|keys| keys.len()).sum();
1236
1237        InvalidationStats {
1238            tracked_tags: tag_idx.len(),
1239            tracked_namespaces: ns_index.len(),
1240            total_tag_entries,
1241            total_namespace_entries,
1242        }
1243    }
1244}
1245
1246/// Statistics for cache invalidation tracking
1247#[derive(Debug, Clone)]
1248pub struct InvalidationStats {
1249    pub tracked_tags: usize,
1250    pub tracked_namespaces: usize,
1251    pub total_tag_entries: usize,
1252    pub total_namespace_entries: usize,
1253}
1254
1255/// Background cache worker for maintenance tasks
1256pub struct BackgroundCacheWorker {
1257    cache: Arc<MultiLevelCache>,
1258    invalidator: Arc<CacheInvalidator>,
1259    config: CacheConfig,
1260    worker_handle: Option<JoinHandle<()>>,
1261    shutdown_signal: Arc<RwLock<bool>>,
1262}
1263
1264impl BackgroundCacheWorker {
1265    pub fn new(
1266        cache: Arc<MultiLevelCache>,
1267        invalidator: Arc<CacheInvalidator>,
1268        config: CacheConfig,
1269    ) -> Self {
1270        Self {
1271            cache,
1272            invalidator,
1273            config,
1274            worker_handle: None,
1275            shutdown_signal: Arc::new(RwLock::new(false)),
1276        }
1277    }
1278
1279    /// Start the background worker
1280    pub fn start(&mut self) -> Result<()> {
1281        if !self.config.enable_background_updates {
1282            return Ok(());
1283        }
1284
1285        let cache = Arc::clone(&self.cache);
1286        let invalidator = Arc::clone(&self.invalidator);
1287        let interval = self.config.background_update_interval;
1288        let shutdown_signal = Arc::clone(&self.shutdown_signal);
1289
1290        let handle = thread::spawn(move || {
1291            while let Ok(shutdown) = shutdown_signal.read() {
1292                if *shutdown {
1293                    break;
1294                }
1295                drop(shutdown); // Release the lock before sleeping
1296
1297                // Perform maintenance tasks
1298                if let Err(e) = Self::perform_maintenance(&cache, &invalidator) {
1299                    // Log error but continue running
1300                    tracing::warn!("Background cache maintenance error: {}", e);
1301                }
1302
1303                // Sleep for the configured interval
1304                thread::sleep(interval);
1305            }
1306        });
1307
1308        self.worker_handle = Some(handle);
1309        Ok(())
1310    }
1311
1312    /// Stop the background worker
1313    pub fn stop(&mut self) -> Result<()> {
1314        // Signal shutdown
1315        {
1316            let mut signal = self.shutdown_signal.write().unwrap();
1317            *signal = true;
1318        }
1319
1320        // Wait for worker to finish
1321        if let Some(handle) = self.worker_handle.take() {
1322            handle
1323                .join()
1324                .map_err(|e| anyhow!("Failed to join worker thread: {:?}", e))?;
1325        }
1326
1327        Ok(())
1328    }
1329
1330    /// Perform background maintenance tasks
1331    fn perform_maintenance(
1332        cache: &Arc<MultiLevelCache>,
1333        invalidator: &Arc<CacheInvalidator>,
1334    ) -> Result<()> {
1335        // 1. Clean expired entries
1336        let expired_count = invalidator.invalidate_expired()?;
1337        if expired_count > 0 {
1338            println!("Background worker cleaned {expired_count} expired entries");
1339        }
1340
1341        // 2. Optimize memory usage if fragmentation is high
1342        let memory_stats = cache.get_memory_stats();
1343        let utilization = memory_stats.memory_bytes as f64 / memory_stats.max_memory_bytes as f64;
1344
1345        if utilization > 0.9 {
1346            // Trigger more aggressive cleanup
1347            Self::aggressive_cleanup(cache)?;
1348        }
1349
1350        // 3. Preemptive persistent cache sync
1351        Self::sync_hot_entries(cache)?;
1352
1353        Ok(())
1354    }
1355
1356    /// Perform aggressive cleanup when memory usage is high
1357    fn aggressive_cleanup(_cache: &Arc<MultiLevelCache>) -> Result<()> {
1358        // Force cleanup of memory cache by temporarily reducing limits
1359        // This is a simplified approach - in practice you'd implement more sophisticated logic
1360        println!("Performing aggressive cache cleanup due to high memory usage");
1361        Ok(())
1362    }
1363
1364    /// Sync frequently accessed entries to persistent storage
1365    fn sync_hot_entries(_cache: &Arc<MultiLevelCache>) -> Result<()> {
1366        // In a real implementation, you'd identify hot entries and ensure they're in persistent storage
1367        // This helps with cache warming after restarts
1368        Ok(())
1369    }
1370}
1371
1372impl Drop for BackgroundCacheWorker {
1373    fn drop(&mut self) {
1374        let _ = self.stop();
1375    }
1376}
1377
1378/// Cache warming utilities
1379pub struct CacheWarmer {
1380    cache: Arc<MultiLevelCache>,
1381}
1382
1383impl CacheWarmer {
1384    pub fn new(cache: Arc<MultiLevelCache>) -> Self {
1385        Self { cache }
1386    }
1387
1388    /// Warm cache with a list of key-value pairs
1389    pub fn warm_with_data(&self, data: Vec<(CacheKey, Vector)>) -> Result<usize> {
1390        let mut loaded_count = 0;
1391
1392        for (key, vector) in data {
1393            if self.cache.insert(key, vector).is_ok() {
1394                loaded_count += 1;
1395            }
1396        }
1397
1398        Ok(loaded_count)
1399    }
1400
1401    /// Warm cache by loading frequently accessed entries from persistent storage
1402    pub fn warm_from_persistent(&self, keys: Vec<CacheKey>) -> Result<usize> {
1403        let mut loaded_count = 0;
1404
1405        for key in keys {
1406            // Try to load from persistent cache and promote to memory
1407            if self.cache.get(&key).is_some() {
1408                loaded_count += 1;
1409            }
1410        }
1411
1412        Ok(loaded_count)
1413    }
1414
1415    /// Warm cache using a precomputed dataset
1416    pub fn warm_with_generator<F>(&self, count: usize, generator: F) -> Result<usize>
1417    where
1418        F: Fn(usize) -> Option<(CacheKey, Vector)>,
1419    {
1420        let mut loaded_count = 0;
1421
1422        for i in 0..count {
1423            if let Some((key, vector)) = generator(i) {
1424                if self.cache.insert(key, vector).is_ok() {
1425                    loaded_count += 1;
1426                }
1427            }
1428        }
1429
1430        Ok(loaded_count)
1431    }
1432}
1433
1434/// Advanced cache analytics and optimization recommendations
1435pub struct CacheAnalyzer {
1436    cache: Arc<MultiLevelCache>,
1437    invalidator: Arc<CacheInvalidator>,
1438}
1439
1440#[derive(Debug, Clone)]
1441pub struct CacheAnalysisReport {
1442    pub memory_utilization: f64,
1443    pub hit_ratio: f64,
1444    pub persistent_hit_ratio: f64,
1445    pub most_accessed_namespaces: Vec<(String, usize)>,
1446    pub recommendations: Vec<String>,
1447    pub performance_score: f64, // 0.0 to 1.0
1448}
1449
1450impl CacheAnalyzer {
1451    pub fn new(cache: Arc<MultiLevelCache>, invalidator: Arc<CacheInvalidator>) -> Self {
1452        Self { cache, invalidator }
1453    }
1454
1455    /// Generate comprehensive cache analysis report
1456    pub fn analyze(&self) -> CacheAnalysisReport {
1457        let stats = self.cache.get_stats();
1458        let memory_stats = self.cache.get_memory_stats();
1459        let invalidation_stats = self.invalidator.get_stats();
1460
1461        let memory_utilization =
1462            memory_stats.memory_bytes as f64 / memory_stats.max_memory_bytes as f64;
1463
1464        let total_requests = stats.total_requests;
1465        let total_hits = stats.memory_hits + stats.persistent_hits;
1466        let hit_ratio = if total_requests > 0 {
1467            total_hits as f64 / total_requests as f64
1468        } else {
1469            0.0
1470        };
1471
1472        let persistent_hit_ratio = if stats.persistent_hits + stats.persistent_misses > 0 {
1473            stats.persistent_hits as f64 / (stats.persistent_hits + stats.persistent_misses) as f64
1474        } else {
1475            0.0
1476        };
1477
1478        let mut recommendations = Vec::new();
1479
1480        // Generate recommendations
1481        if hit_ratio < 0.5 {
1482            recommendations
1483                .push("Consider increasing cache size or adjusting eviction policy".to_string());
1484        }
1485
1486        if memory_utilization > 0.9 {
1487            recommendations.push(
1488                "Memory cache is near capacity - consider increasing max_memory_bytes".to_string(),
1489            );
1490        }
1491
1492        if persistent_hit_ratio < 0.3 {
1493            recommendations
1494                .push("Persistent cache hit ratio is low - review TTL settings".to_string());
1495        }
1496
1497        if invalidation_stats.tracked_namespaces > 100 {
1498            recommendations
1499                .push("Consider consolidating namespaces to reduce tracking overhead".to_string());
1500        }
1501
1502        // Calculate performance score (weighted combination of metrics)
1503        let performance_score =
1504            (hit_ratio * 0.4 + (1.0 - memory_utilization) * 0.3 + persistent_hit_ratio * 0.3)
1505                .clamp(0.0, 1.0);
1506
1507        CacheAnalysisReport {
1508            memory_utilization,
1509            hit_ratio,
1510            persistent_hit_ratio,
1511            most_accessed_namespaces: vec![], // Would need access pattern tracking
1512            recommendations,
1513            performance_score,
1514        }
1515    }
1516
1517    /// Get recommendations for cache configuration optimization
1518    pub fn get_optimization_recommendations(&self) -> Vec<String> {
1519        self.analyze().recommendations
1520    }
1521}
1522
1523#[cfg(test)]
1524mod tests {
1525    use super::*;
1526    use tempfile::TempDir;
1527
1528    #[test]
1529    fn test_cache_key() {
1530        let key = CacheKey::new("embeddings", "test_doc").with_variant("v1");
1531
1532        assert_eq!(key.namespace, "embeddings");
1533        assert_eq!(key.key, "test_doc");
1534        assert_eq!(key.variant, Some("v1".to_string()));
1535        assert_eq!(key.to_string(), "embeddings:test_doc:v1");
1536    }
1537
1538    #[test]
1539    fn test_memory_cache() {
1540        let config = CacheConfig {
1541            max_memory_entries: 2,
1542            max_memory_bytes: 1024,
1543            ..Default::default()
1544        };
1545
1546        let mut cache = MemoryCache::new(config);
1547
1548        let key1 = CacheKey::new("test", "key1");
1549        let key2 = CacheKey::new("test", "key2");
1550        let key3 = CacheKey::new("test", "key3");
1551
1552        let vector1 = Vector::new(vec![1.0, 2.0, 3.0]);
1553        let vector2 = Vector::new(vec![4.0, 5.0, 6.0]);
1554        let vector3 = Vector::new(vec![7.0, 8.0, 9.0]);
1555
1556        // Insert vectors
1557        cache
1558            .insert(key1.clone(), CacheEntry::new(vector1.clone()))
1559            .unwrap();
1560        cache
1561            .insert(key2.clone(), CacheEntry::new(vector2.clone()))
1562            .unwrap();
1563
1564        // Check retrieval
1565        assert!(cache.get(&key1).is_some());
1566        assert!(cache.get(&key2).is_some());
1567
1568        // Insert third vector (should evict one)
1569        cache
1570            .insert(key3.clone(), CacheEntry::new(vector3.clone()))
1571            .unwrap();
1572
1573        // One of the first two should be evicted
1574        let remaining = cache.entries.len();
1575        assert_eq!(remaining, 2);
1576    }
1577
1578    #[test]
1579    fn test_persistent_cache() {
1580        let temp_dir = TempDir::new().unwrap();
1581
1582        let config = CacheConfig {
1583            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1584            enable_compression: true,
1585            ..Default::default()
1586        };
1587
1588        let cache = PersistentCache::new(config).unwrap();
1589
1590        let key = CacheKey::new("test", "persistent_key");
1591        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1592        let entry = CacheEntry::new(vector.clone());
1593
1594        // Store and retrieve
1595        cache.store(&key, &entry).unwrap();
1596        let retrieved = cache.load(&key).unwrap();
1597
1598        // Should succeed now with proper serialization
1599        assert!(retrieved.is_some());
1600        let retrieved_entry = retrieved.unwrap();
1601        assert_eq!(retrieved_entry.data.as_f32(), vector.as_f32());
1602    }
1603
1604    #[test]
1605    fn test_multi_level_cache() {
1606        let temp_dir = TempDir::new().unwrap();
1607
1608        let config = CacheConfig {
1609            max_memory_entries: 2,
1610            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1611            enable_persistent: true,
1612            ..Default::default()
1613        };
1614
1615        let cache = MultiLevelCache::new(config).unwrap();
1616
1617        let key = CacheKey::new("test", "multi_level");
1618        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1619
1620        // Insert and retrieve
1621        cache.insert(key.clone(), vector.clone()).unwrap();
1622        let retrieved = cache.get(&key).unwrap();
1623
1624        assert_eq!(retrieved.as_f32(), vector.as_f32());
1625
1626        // Check stats
1627        let stats = cache.get_stats();
1628        assert_eq!(stats.total_requests, 1);
1629        assert_eq!(stats.memory_hits, 1);
1630    }
1631
1632    #[test]
1633    fn test_cache_expiration() {
1634        let config = CacheConfig {
1635            max_memory_entries: 10,
1636            ttl: Some(Duration::from_millis(10)),
1637            ..Default::default()
1638        };
1639
1640        let mut cache = MemoryCache::new(config);
1641
1642        let key = CacheKey::new("test", "expiring");
1643        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1644        let entry = CacheEntry::new(vector).with_ttl(Duration::from_millis(10));
1645
1646        cache.insert(key.clone(), entry).unwrap();
1647
1648        // Should be available immediately
1649        assert!(cache.get(&key).is_some());
1650
1651        // Wait for expiration
1652        std::thread::sleep(Duration::from_millis(20));
1653
1654        // Should be expired and removed
1655        assert!(cache.get(&key).is_none());
1656    }
1657
1658    #[test]
1659    fn test_arc_eviction_policy() {
1660        let config = CacheConfig {
1661            max_memory_entries: 3,
1662            eviction_policy: EvictionPolicy::ARC,
1663            ..Default::default()
1664        };
1665
1666        let mut cache = MemoryCache::new(config);
1667
1668        let key1 = CacheKey::new("test", "arc1");
1669        let key2 = CacheKey::new("test", "arc2");
1670        let key3 = CacheKey::new("test", "arc3");
1671        let key4 = CacheKey::new("test", "arc4");
1672
1673        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1674
1675        // Insert three items
1676        cache
1677            .insert(key1.clone(), CacheEntry::new(vector.clone()))
1678            .unwrap();
1679        cache
1680            .insert(key2.clone(), CacheEntry::new(vector.clone()))
1681            .unwrap();
1682        cache
1683            .insert(key3.clone(), CacheEntry::new(vector.clone()))
1684            .unwrap();
1685
1686        // Access key1 multiple times to make it frequent
1687        cache.get(&key1);
1688        cache.get(&key1);
1689        cache.get(&key1);
1690
1691        // Insert key4 - should evict the least valuable item
1692        cache
1693            .insert(key4.clone(), CacheEntry::new(vector.clone()))
1694            .unwrap();
1695
1696        // key1 should still be there (frequent access)
1697        assert!(cache.get(&key1).is_some());
1698
1699        // Check that we have exactly 3 items
1700        assert_eq!(cache.entries.len(), 3);
1701    }
1702
1703    #[test]
1704    fn test_cache_warmer() {
1705        let temp_dir = TempDir::new().unwrap();
1706
1707        let config = CacheConfig {
1708            max_memory_entries: 10,
1709            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1710            enable_persistent: true,
1711            ..Default::default()
1712        };
1713
1714        let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1715        let warmer = CacheWarmer::new(Arc::clone(&cache));
1716
1717        // Prepare test data
1718        let test_data = vec![
1719            (CacheKey::new("test", "warm1"), Vector::new(vec![1.0, 2.0])),
1720            (CacheKey::new("test", "warm2"), Vector::new(vec![3.0, 4.0])),
1721            (CacheKey::new("test", "warm3"), Vector::new(vec![5.0, 6.0])),
1722        ];
1723
1724        // Warm cache with data
1725        let loaded_count = warmer.warm_with_data(test_data.clone()).unwrap();
1726        assert_eq!(loaded_count, 3);
1727
1728        // Verify data is in cache
1729        for (key, expected_vector) in test_data {
1730            let cached_vector = cache.get(&key).unwrap();
1731            assert_eq!(cached_vector.as_f32(), expected_vector.as_f32());
1732        }
1733    }
1734
1735    #[test]
1736    fn test_cache_warmer_with_generator() {
1737        let temp_dir = TempDir::new().unwrap();
1738
1739        let config = CacheConfig {
1740            max_memory_entries: 10,
1741            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1742            enable_persistent: true,
1743            ..Default::default()
1744        };
1745
1746        let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1747        let warmer = CacheWarmer::new(Arc::clone(&cache));
1748
1749        // Use generator to warm cache
1750        let loaded_count = warmer
1751            .warm_with_generator(5, |i| {
1752                Some((
1753                    CacheKey::new("generated", format!("item_{i}")),
1754                    Vector::new(vec![i as f32, (i * 2) as f32]),
1755                ))
1756            })
1757            .unwrap();
1758
1759        assert_eq!(loaded_count, 5);
1760
1761        // Verify generated data is in cache
1762        for i in 0..5 {
1763            let key = CacheKey::new("generated", format!("item_{i}"));
1764            let cached_vector = cache.get(&key).unwrap();
1765            assert_eq!(cached_vector.as_f32(), vec![i as f32, (i * 2) as f32]);
1766        }
1767    }
1768
1769    #[test]
1770    fn test_cache_analyzer() {
1771        let temp_dir = TempDir::new().unwrap();
1772
1773        let config = CacheConfig {
1774            max_memory_entries: 10,
1775            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1776            enable_persistent: true,
1777            ..Default::default()
1778        };
1779
1780        let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1781        let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1782        let analyzer = CacheAnalyzer::new(Arc::clone(&cache), Arc::clone(&invalidator));
1783
1784        // Add some test data and access patterns
1785        let key1 = CacheKey::new("test", "analyze1");
1786        let key2 = CacheKey::new("test", "analyze2");
1787        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1788
1789        cache.insert(key1.clone(), vector.clone()).unwrap();
1790        cache.insert(key2.clone(), vector.clone()).unwrap();
1791
1792        // Access the cache to generate some stats
1793        cache.get(&key1);
1794        cache.get(&key2);
1795        cache.get(&key1); // Hit
1796        cache.get(&CacheKey::new("test", "nonexistent")); // Miss
1797
1798        // Analyze cache performance
1799        let report = analyzer.analyze();
1800
1801        assert!(report.hit_ratio > 0.0);
1802        assert!(report.memory_utilization >= 0.0 && report.memory_utilization <= 1.0);
1803        assert!(report.performance_score >= 0.0 && report.performance_score <= 1.0);
1804
1805        // Should have some recommendations if performance isn't perfect
1806        let recommendations = analyzer.get_optimization_recommendations();
1807        // In this test case, we might get recommendations about hit ratio
1808        assert!(!recommendations.is_empty());
1809    }
1810
1811    #[test]
1812    fn test_background_cache_worker() {
1813        let temp_dir = TempDir::new().unwrap();
1814
1815        let config = CacheConfig {
1816            max_memory_entries: 10,
1817            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1818            enable_persistent: true,
1819            enable_background_updates: true,
1820            background_update_interval: Duration::from_millis(100),
1821            ..Default::default()
1822        };
1823
1824        let cache = Arc::new(MultiLevelCache::new(config.clone()).unwrap());
1825        let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1826        let mut worker =
1827            BackgroundCacheWorker::new(Arc::clone(&cache), Arc::clone(&invalidator), config);
1828
1829        // Start the worker
1830        worker.start().unwrap();
1831
1832        // Add some test data
1833        let key = CacheKey::new("test", "background");
1834        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1835        cache.insert(key.clone(), vector.clone()).unwrap();
1836
1837        // Let the worker run for a short time
1838        std::thread::sleep(Duration::from_millis(150));
1839
1840        // Stop the worker
1841        worker.stop().unwrap();
1842
1843        // Verify data is still accessible
1844        assert!(cache.get(&key).is_some());
1845    }
1846
1847    #[test]
1848    fn test_cache_invalidation_by_tag() {
1849        let temp_dir = TempDir::new().unwrap();
1850
1851        let config = CacheConfig {
1852            max_memory_entries: 10,
1853            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1854            enable_persistent: true,
1855            ..Default::default()
1856        };
1857
1858        let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1859        let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1860
1861        // Create entries with tags
1862        let key1 = CacheKey::new("test", "tagged1");
1863        let key2 = CacheKey::new("test", "tagged2");
1864        let key3 = CacheKey::new("test", "tagged3");
1865
1866        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1867
1868        cache.insert(key1.clone(), vector.clone()).unwrap();
1869        cache.insert(key2.clone(), vector.clone()).unwrap();
1870        cache.insert(key3.clone(), vector.clone()).unwrap();
1871
1872        // Register entries with tags
1873        let mut tags1 = HashMap::new();
1874        tags1.insert("category".to_string(), "embeddings".to_string());
1875        invalidator.register_entry(&key1, &tags1);
1876
1877        let mut tags2 = HashMap::new();
1878        tags2.insert("category".to_string(), "embeddings".to_string());
1879        invalidator.register_entry(&key2, &tags2);
1880
1881        let mut tags3 = HashMap::new();
1882        tags3.insert("category".to_string(), "vectors".to_string());
1883        invalidator.register_entry(&key3, &tags3);
1884
1885        // Invalidate by tag
1886        let invalidated_count = invalidator
1887            .invalidate_by_tag("category", "embeddings")
1888            .unwrap();
1889        assert_eq!(invalidated_count, 2);
1890
1891        // Check that tagged entries are removed
1892        assert!(cache.get(&key1).is_none());
1893        assert!(cache.get(&key2).is_none());
1894
1895        // Check that untagged entry remains
1896        assert!(cache.get(&key3).is_some());
1897    }
1898
1899    #[test]
1900    fn test_cache_invalidation_by_namespace() {
1901        let temp_dir = TempDir::new().unwrap();
1902
1903        let config = CacheConfig {
1904            max_memory_entries: 10,
1905            persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1906            enable_persistent: true,
1907            ..Default::default()
1908        };
1909
1910        let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1911        let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1912
1913        // Create entries in different namespaces
1914        let key1 = CacheKey::new("embeddings", "item1");
1915        let key2 = CacheKey::new("embeddings", "item2");
1916        let key3 = CacheKey::new("vectors", "item3");
1917
1918        let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1919
1920        cache.insert(key1.clone(), vector.clone()).unwrap();
1921        cache.insert(key2.clone(), vector.clone()).unwrap();
1922        cache.insert(key3.clone(), vector.clone()).unwrap();
1923
1924        // Register entries for tracking
1925        invalidator.register_entry(&key1, &HashMap::new());
1926        invalidator.register_entry(&key2, &HashMap::new());
1927        invalidator.register_entry(&key3, &HashMap::new());
1928
1929        // Invalidate by namespace
1930        let invalidated_count = invalidator.invalidate_namespace("embeddings").unwrap();
1931        assert_eq!(invalidated_count, 2);
1932
1933        // Check that namespace entries are removed
1934        assert!(cache.get(&key1).is_none());
1935        assert!(cache.get(&key2).is_none());
1936
1937        // Check that other namespace entry remains
1938        assert!(cache.get(&key3).is_some());
1939    }
1940}