1use 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
20type TagIndex = Arc<RwLock<HashMap<String, HashMap<String, Vec<CacheKey>>>>>;
22
23#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25pub enum EvictionPolicy {
26 LRU,
28 LFU,
30 ARC,
32 FIFO,
34 TTL,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CacheConfig {
41 pub max_memory_entries: usize,
43 pub max_memory_bytes: usize,
45 pub ttl: Option<Duration>,
47 pub eviction_policy: EvictionPolicy,
49 pub enable_persistent: bool,
51 pub persistent_cache_dir: Option<std::path::PathBuf>,
53 pub max_persistent_bytes: usize,
55 pub enable_compression: bool,
57 pub enable_background_updates: bool,
59 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, ttl: Some(Duration::from_secs(3600)), eviction_policy: EvictionPolicy::LRU,
70 enable_persistent: true,
71 persistent_cache_dir: None,
72 max_persistent_bytes: 1024 * 1024 * 1024, enable_compression: true,
74 enable_background_updates: false,
75 background_update_interval: Duration::from_secs(300), }
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct CacheEntry {
83 pub data: Vector,
85 pub created_at: Instant,
87 pub last_accessed: Instant,
89 pub access_count: u64,
91 pub size_bytes: usize,
93 pub ttl: Option<Duration>,
95 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; 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 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 pub fn touch(&mut self) {
136 self.last_accessed = Instant::now();
137 self.access_count += 1;
138 }
139}
140
141#[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
174pub struct MemoryCache {
176 config: CacheConfig,
177 entries: HashMap<CacheKey, CacheEntry>,
178 access_order: VecDeque<CacheKey>, frequency_map: HashMap<CacheKey, u64>, current_memory_bytes: usize,
181 arc_t1: VecDeque<CacheKey>, arc_t2: VecDeque<CacheKey>, arc_b1: VecDeque<CacheKey>, arc_b2: VecDeque<CacheKey>, arc_p: usize, }
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 pub fn insert(&mut self, key: CacheKey, entry: CacheEntry) -> Result<()> {
207 self.clean_expired();
209
210 while self.should_evict(&entry) {
212 self.evict_one()?;
213 }
214
215 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 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 pub fn get(&mut self, key: &CacheKey) -> Option<Vector> {
231 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 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 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 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 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 let key = self
294 .entries
295 .keys()
296 .next()
297 .expect("entries should not be empty when at capacity")
298 .clone();
299 self.remove(&key);
300 Ok(())
301 } else {
302 Err(anyhow!("No entries to evict"))
303 }
304 }
305
306 fn find_lru_key(&self) -> Option<CacheKey> {
308 self.access_order.front().cloned()
309 }
310
311 fn find_lfu_key(&self) -> Option<CacheKey> {
313 self.frequency_map
314 .iter()
315 .min_by_key(|&(_, &freq)| freq)
316 .map(|(key, _)| key.clone())
317 }
318
319 fn find_arc_key(&mut self) -> Option<CacheKey> {
321 let c = self.config.max_memory_entries;
322
323 if !self.arc_t1.is_empty()
325 && (self.arc_t1.len() > self.arc_p
326 || (self.arc_t2.is_empty() && self.arc_t1.len() == self.arc_p))
327 {
328 if let Some(key) = self.arc_t1.pop_front() {
329 self.arc_b1.push_back(key.clone());
331 if self.arc_b1.len() > c {
332 self.arc_b1.pop_front();
333 }
334 return Some(key);
335 }
336 }
337
338 if let Some(key) = self.arc_t2.pop_front() {
340 self.arc_b2.push_back(key.clone());
342 if self.arc_b2.len() > c {
343 self.arc_b2.pop_front();
344 }
345 return Some(key);
346 }
347
348 self.find_lru_key()
350 }
351
352 fn find_fifo_key(&self) -> Option<CacheKey> {
354 self.entries
355 .iter()
356 .min_by_key(|(_, entry)| entry.created_at)
357 .map(|(key, _)| key.clone())
358 }
359
360 fn find_expired_key(&self) -> Option<CacheKey> {
362 self.entries
363 .iter()
364 .find(|(_, entry)| entry.is_expired())
365 .map(|(key, _)| key.clone())
366 }
367
368 fn track_access(&mut self, key: &CacheKey) {
370 if let Some(pos) = self.access_order.iter().position(|k| k == key) {
372 self.access_order.remove(pos);
373 }
374 self.access_order.push_back(key.clone());
375
376 *self.frequency_map.entry(key.clone()).or_insert(0) += 1;
378
379 if self.config.eviction_policy == EvictionPolicy::ARC {
381 self.track_arc_access(key);
382 }
383 }
384
385 fn track_arc_access(&mut self, key: &CacheKey) {
387 let c = self.config.max_memory_entries;
388
389 if let Some(pos) = self.arc_t1.iter().position(|k| k == key) {
391 self.arc_t1.remove(pos);
393 self.arc_t2.push_back(key.clone());
394 } else if let Some(pos) = self.arc_t2.iter().position(|k| k == key) {
395 self.arc_t2.remove(pos);
397 self.arc_t2.push_back(key.clone());
398 } else if let Some(pos) = self.arc_b1.iter().position(|k| k == key) {
399 self.arc_b1.remove(pos);
401 self.arc_p = (self.arc_p + 1.max(self.arc_b2.len() / self.arc_b1.len())).min(c);
402 self.arc_t2.push_back(key.clone());
403 } else if let Some(pos) = self.arc_b2.iter().position(|k| k == key) {
404 self.arc_b2.remove(pos);
406 self.arc_p = self
407 .arc_p
408 .saturating_sub(1.max(self.arc_b1.len() / self.arc_b2.len()));
409 self.arc_t2.push_back(key.clone());
410 } else {
411 self.arc_t1.push_back(key.clone());
413 }
414 }
415
416 fn remove_from_tracking(&mut self, key: &CacheKey) {
418 if let Some(pos) = self.access_order.iter().position(|k| k == key) {
419 self.access_order.remove(pos);
420 }
421 self.frequency_map.remove(key);
422
423 if self.config.eviction_policy == EvictionPolicy::ARC {
425 if let Some(pos) = self.arc_t1.iter().position(|k| k == key) {
426 self.arc_t1.remove(pos);
427 }
428 if let Some(pos) = self.arc_t2.iter().position(|k| k == key) {
429 self.arc_t2.remove(pos);
430 }
431 if let Some(pos) = self.arc_b1.iter().position(|k| k == key) {
432 self.arc_b1.remove(pos);
433 }
434 if let Some(pos) = self.arc_b2.iter().position(|k| k == key) {
435 self.arc_b2.remove(pos);
436 }
437 }
438 }
439
440 fn clean_expired(&mut self) {
442 let expired_keys: Vec<CacheKey> = self
443 .entries
444 .iter()
445 .filter(|(_, entry)| entry.is_expired())
446 .map(|(key, _)| key.clone())
447 .collect();
448
449 for key in expired_keys {
450 self.remove(&key);
451 }
452 }
453
454 pub fn stats(&self) -> CacheStats {
456 CacheStats {
457 entries: self.entries.len(),
458 memory_bytes: self.current_memory_bytes,
459 max_entries: self.config.max_memory_entries,
460 max_memory_bytes: self.config.max_memory_bytes,
461 hit_ratio: 0.0, }
463 }
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct CacheStats {
469 pub entries: usize,
470 pub memory_bytes: usize,
471 pub max_entries: usize,
472 pub max_memory_bytes: usize,
473 pub hit_ratio: f32,
474}
475
476pub struct PersistentCache {
478 config: CacheConfig,
479 cache_dir: std::path::PathBuf,
480}
481
482impl PersistentCache {
483 pub fn new(config: CacheConfig) -> Result<Self> {
484 let cache_dir = config
485 .persistent_cache_dir
486 .clone()
487 .unwrap_or_else(|| std::env::temp_dir().join("oxirs_vec_cache"));
488
489 std::fs::create_dir_all(&cache_dir)?;
490
491 Ok(Self { config, cache_dir })
492 }
493
494 pub fn store(&self, key: &CacheKey, entry: &CacheEntry) -> Result<()> {
496 let file_path = self.get_file_path(key);
497
498 if let Some(parent) = file_path.parent() {
499 std::fs::create_dir_all(parent)?;
500 }
501
502 let serialized = self.serialize_entry(entry)?;
503 let final_data = if self.config.enable_compression {
504 self.compress_data(&serialized)?
505 } else {
506 serialized
507 };
508
509 std::fs::write(file_path, final_data)?;
510 Ok(())
511 }
512
513 pub fn load(&self, key: &CacheKey) -> Result<Option<CacheEntry>> {
515 let file_path = self.get_file_path(key);
516
517 if !file_path.exists() {
518 return Ok(None);
519 }
520
521 let data = std::fs::read(&file_path)?;
522
523 let decompressed = if self.config.enable_compression {
524 self.decompress_data(&data)?
525 } else {
526 data
527 };
528
529 let entry = self.deserialize_entry(&decompressed)?;
530
531 if entry.is_expired() {
533 let _ = std::fs::remove_file(file_path);
535 Ok(None)
536 } else {
537 Ok(Some(entry))
538 }
539 }
540
541 pub fn remove(&self, key: &CacheKey) -> Result<()> {
543 let file_path = self.get_file_path(key);
544 if file_path.exists() {
545 std::fs::remove_file(file_path)?;
546 }
547 Ok(())
548 }
549
550 pub fn clear(&self) -> Result<()> {
552 if self.cache_dir.exists() {
553 std::fs::remove_dir_all(&self.cache_dir)?;
554 std::fs::create_dir_all(&self.cache_dir)?;
555 }
556 Ok(())
557 }
558
559 fn get_file_path(&self, key: &CacheKey) -> std::path::PathBuf {
561 let key_str = key.to_string();
562 let hash = self.hash_key(&key_str);
563
564 let sub_dir = format!("{:02x}", (hash % 256) as u8);
566
567 let encoded_key = self.encode_cache_key_for_filename(key);
569 let filename = format!("{hash:016x}_{encoded_key}.cache");
570
571 self.cache_dir.join(sub_dir).join(filename)
572 }
573
574 fn encode_cache_key_for_filename(&self, key: &CacheKey) -> String {
576 let key_data = serde_json::json!({
577 "namespace": key.namespace,
578 "key": key.key,
579 "variant": key.variant
580 });
581
582 use base64::{engine::general_purpose, Engine as _};
584 general_purpose::URL_SAFE_NO_PAD.encode(key_data.to_string().as_bytes())
585 }
586
587 fn decode_cache_key_from_filename(&self, filename: &str) -> Option<CacheKey> {
589 if let Some(encoded_part) = filename
590 .strip_suffix(".cache")
591 .and_then(|s| s.split('_').nth(1))
592 {
593 use base64::{engine::general_purpose, Engine as _};
594 if let Ok(decoded_bytes) = general_purpose::URL_SAFE_NO_PAD.decode(encoded_part) {
595 if let Ok(decoded_str) = String::from_utf8(decoded_bytes) {
596 if let Ok(key_data) = serde_json::from_str::<serde_json::Value>(&decoded_str) {
597 return Some(CacheKey {
598 namespace: key_data["namespace"].as_str()?.to_string(),
599 key: key_data["key"].as_str()?.to_string(),
600 variant: key_data["variant"].as_str().map(|s| s.to_string()),
601 });
602 }
603 }
604 }
605 }
606 None
607 }
608
609 fn hash_key(&self, key: &str) -> u64 {
611 let mut hasher = std::collections::hash_map::DefaultHasher::new();
612 key.hash(&mut hasher);
613 hasher.finish()
614 }
615
616 fn serialize_entry(&self, entry: &CacheEntry) -> Result<Vec<u8>> {
618 let mut data = Vec::new();
620
621 let vector_data = &entry.data.as_f32();
623 data.extend_from_slice(&(vector_data.len() as u32).to_le_bytes());
624 for &value in vector_data {
625 data.extend_from_slice(&value.to_le_bytes());
626 }
627
628 let created_nanos = entry.created_at.elapsed().as_nanos() as u64;
630 let accessed_nanos = entry.last_accessed.elapsed().as_nanos() as u64;
631 data.extend_from_slice(&created_nanos.to_le_bytes());
632 data.extend_from_slice(&accessed_nanos.to_le_bytes());
633
634 data.extend_from_slice(&entry.access_count.to_le_bytes());
636 data.extend_from_slice(&(entry.size_bytes as u64).to_le_bytes());
637
638 if let Some(ttl) = entry.ttl {
640 data.push(1); data.extend_from_slice(&ttl.as_nanos().to_le_bytes());
642 } else {
643 data.push(0); }
645
646 data.extend_from_slice(&(entry.tags.len() as u32).to_le_bytes());
648 for (key, value) in &entry.tags {
649 data.extend_from_slice(&(key.len() as u32).to_le_bytes());
650 data.extend_from_slice(key.as_bytes());
651 data.extend_from_slice(&(value.len() as u32).to_le_bytes());
652 data.extend_from_slice(value.as_bytes());
653 }
654
655 Ok(data)
656 }
657
658 fn deserialize_entry(&self, data: &[u8]) -> Result<CacheEntry> {
660 if data.len() < 4 {
662 return Err(anyhow::anyhow!(
663 "Invalid cache entry data: too small (expected at least 4 bytes, got {})",
664 data.len()
665 ));
666 }
667
668 let mut offset = 0;
669
670 let vector_len = u32::from_le_bytes([
672 data[offset],
673 data[offset + 1],
674 data[offset + 2],
675 data[offset + 3],
676 ]) as usize;
677 offset += 4;
678
679 let mut vector_data = Vec::with_capacity(vector_len);
680 for _ in 0..vector_len {
681 let value = f32::from_le_bytes([
682 data[offset],
683 data[offset + 1],
684 data[offset + 2],
685 data[offset + 3],
686 ]);
687 vector_data.push(value);
688 offset += 4;
689 }
690 let vector = Vector::new(vector_data);
691
692 let created_nanos = u64::from_le_bytes([
694 data[offset],
695 data[offset + 1],
696 data[offset + 2],
697 data[offset + 3],
698 data[offset + 4],
699 data[offset + 5],
700 data[offset + 6],
701 data[offset + 7],
702 ]);
703 offset += 8;
704
705 let accessed_nanos = u64::from_le_bytes([
706 data[offset],
707 data[offset + 1],
708 data[offset + 2],
709 data[offset + 3],
710 data[offset + 4],
711 data[offset + 5],
712 data[offset + 6],
713 data[offset + 7],
714 ]);
715 offset += 8;
716
717 let now = Instant::now();
719 let created_at = now - Duration::from_nanos(created_nanos);
720 let last_accessed = now - Duration::from_nanos(accessed_nanos);
721
722 let access_count = u64::from_le_bytes([
724 data[offset],
725 data[offset + 1],
726 data[offset + 2],
727 data[offset + 3],
728 data[offset + 4],
729 data[offset + 5],
730 data[offset + 6],
731 data[offset + 7],
732 ]);
733 offset += 8;
734
735 let size_bytes = u64::from_le_bytes([
736 data[offset],
737 data[offset + 1],
738 data[offset + 2],
739 data[offset + 3],
740 data[offset + 4],
741 data[offset + 5],
742 data[offset + 6],
743 data[offset + 7],
744 ]) as usize;
745 offset += 8;
746
747 let ttl = if data[offset] == 1 {
749 offset += 1;
750 let ttl_nanos = u128::from_le_bytes([
751 data[offset],
752 data[offset + 1],
753 data[offset + 2],
754 data[offset + 3],
755 data[offset + 4],
756 data[offset + 5],
757 data[offset + 6],
758 data[offset + 7],
759 data[offset + 8],
760 data[offset + 9],
761 data[offset + 10],
762 data[offset + 11],
763 data[offset + 12],
764 data[offset + 13],
765 data[offset + 14],
766 data[offset + 15],
767 ]);
768 offset += 16;
769 Some(Duration::from_nanos(ttl_nanos as u64))
770 } else {
771 offset += 1;
772 None
773 };
774
775 let tags_len = u32::from_le_bytes([
777 data[offset],
778 data[offset + 1],
779 data[offset + 2],
780 data[offset + 3],
781 ]) as usize;
782 offset += 4;
783
784 let mut tags = HashMap::new();
785 for _ in 0..tags_len {
786 let key_len = u32::from_le_bytes([
787 data[offset],
788 data[offset + 1],
789 data[offset + 2],
790 data[offset + 3],
791 ]) as usize;
792 offset += 4;
793 let key = String::from_utf8(data[offset..offset + key_len].to_vec())?;
794 offset += key_len;
795
796 let value_len = u32::from_le_bytes([
797 data[offset],
798 data[offset + 1],
799 data[offset + 2],
800 data[offset + 3],
801 ]) as usize;
802 offset += 4;
803 let value = String::from_utf8(data[offset..offset + value_len].to_vec())?;
804 offset += value_len;
805
806 tags.insert(key, value);
807 }
808
809 Ok(CacheEntry {
810 data: vector,
811 created_at,
812 last_accessed,
813 access_count,
814 size_bytes,
815 ttl,
816 tags,
817 })
818 }
819
820 fn compress_data(&self, data: &[u8]) -> Result<Vec<u8>> {
822 let mut compressed = Vec::new();
824
825 if data.is_empty() {
826 return Ok(compressed);
827 }
828
829 let mut current_byte = data[0];
830 let mut count = 1u8;
831
832 for &byte in &data[1..] {
833 if byte == current_byte && count < 255 {
834 count += 1;
835 } else {
836 compressed.push(count);
837 compressed.push(current_byte);
838 current_byte = byte;
839 count = 1;
840 }
841 }
842
843 compressed.push(count);
845 compressed.push(current_byte);
846
847 Ok(compressed)
848 }
849
850 fn decompress_data(&self, data: &[u8]) -> Result<Vec<u8>> {
852 let mut decompressed = Vec::new();
853
854 if data.len() % 2 != 0 {
855 return Err(anyhow!("Invalid compressed data length"));
856 }
857
858 for chunk in data.chunks(2) {
859 let count = chunk[0];
860 let byte = chunk[1];
861
862 for _ in 0..count {
863 decompressed.push(byte);
864 }
865 }
866
867 Ok(decompressed)
868 }
869}
870
871pub struct MultiLevelCache {
873 memory_cache: Arc<RwLock<MemoryCache>>,
874 persistent_cache: Option<Arc<PersistentCache>>,
875 #[allow(dead_code)]
876 config: CacheConfig,
877 stats: Arc<RwLock<MultiLevelCacheStats>>,
878}
879
880#[derive(Debug, Default, Clone)]
881pub struct MultiLevelCacheStats {
882 pub memory_hits: u64,
883 pub memory_misses: u64,
884 pub persistent_hits: u64,
885 pub persistent_misses: u64,
886 pub total_requests: u64,
887}
888
889impl MultiLevelCache {
890 pub fn new(config: CacheConfig) -> Result<Self> {
891 let memory_cache = Arc::new(RwLock::new(MemoryCache::new(config.clone())));
892
893 let persistent_cache = if config.enable_persistent {
894 Some(Arc::new(PersistentCache::new(config.clone())?))
895 } else {
896 None
897 };
898
899 Ok(Self {
900 memory_cache,
901 persistent_cache,
902 config,
903 stats: Arc::new(RwLock::new(MultiLevelCacheStats::default())),
904 })
905 }
906
907 pub fn insert(&self, key: CacheKey, data: Vector) -> Result<()> {
909 let entry = CacheEntry::new(data);
910
911 {
913 let mut memory = self.memory_cache.write().expect("lock poisoned");
914 memory.insert(key.clone(), entry.clone())?;
915 }
916
917 if let Some(ref persistent) = self.persistent_cache {
919 persistent.store(&key, &entry)?;
920 }
921
922 Ok(())
923 }
924
925 pub fn get(&self, key: &CacheKey) -> Option<Vector> {
927 self.update_stats_total();
928
929 {
931 let mut memory = self.memory_cache.write().expect("lock poisoned");
932 if let Some(data) = memory.get(key) {
933 self.update_stats_memory_hit();
934 return Some(data.clone());
935 }
936 }
937
938 self.update_stats_memory_miss();
939
940 if let Some(ref persistent) = self.persistent_cache {
942 if let Ok(Some(mut entry)) = persistent.load(key) {
943 self.update_stats_persistent_hit();
944
945 let data = entry.data.clone();
947 entry.touch();
948 if let Ok(mut memory) = self.memory_cache.write() {
949 let _ = memory.insert(key.clone(), entry);
950 }
951
952 return Some(data);
953 }
954 }
955
956 self.update_stats_persistent_miss();
957 None
958 }
959
960 pub fn remove(&self, key: &CacheKey) -> Result<()> {
962 {
964 let mut memory = self.memory_cache.write().expect("lock poisoned");
965 memory.remove(key);
966 }
967
968 if let Some(ref persistent) = self.persistent_cache {
970 persistent.remove(key)?;
971 }
972
973 Ok(())
974 }
975
976 pub fn clear(&self) -> Result<()> {
978 {
980 let mut memory = self.memory_cache.write().expect("lock poisoned");
981 memory.clear();
982 }
983
984 if let Some(ref persistent) = self.persistent_cache {
986 persistent.clear()?;
987 }
988
989 {
991 let mut stats = self.stats.write().expect("lock poisoned");
992 *stats = MultiLevelCacheStats::default();
993 }
994
995 Ok(())
996 }
997
998 pub fn get_stats(&self) -> MultiLevelCacheStats {
1000 self.stats.read().expect("lock poisoned").clone()
1001 }
1002
1003 pub fn get_memory_stats(&self) -> CacheStats {
1005 let memory = self.memory_cache.read().expect("lock poisoned");
1006 memory.stats()
1007 }
1008
1009 fn update_stats_total(&self) {
1011 let mut stats = self.stats.write().expect("lock poisoned");
1012 stats.total_requests += 1;
1013 }
1014
1015 fn update_stats_memory_hit(&self) {
1016 let mut stats = self.stats.write().expect("lock poisoned");
1017 stats.memory_hits += 1;
1018 }
1019
1020 fn update_stats_memory_miss(&self) {
1021 let mut stats = self.stats.write().expect("lock poisoned");
1022 stats.memory_misses += 1;
1023 }
1024
1025 fn update_stats_persistent_hit(&self) {
1026 let mut stats = self.stats.write().expect("lock poisoned");
1027 stats.persistent_hits += 1;
1028 }
1029
1030 fn update_stats_persistent_miss(&self) {
1031 let mut stats = self.stats.write().expect("lock poisoned");
1032 stats.persistent_misses += 1;
1033 }
1034}
1035
1036pub struct CacheInvalidator {
1038 cache: Arc<MultiLevelCache>,
1039 tag_index: TagIndex, namespace_index: Arc<RwLock<HashMap<String, Vec<CacheKey>>>>, }
1042
1043impl CacheInvalidator {
1044 pub fn new(cache: Arc<MultiLevelCache>) -> Self {
1045 Self {
1046 cache,
1047 tag_index: Arc::new(RwLock::new(HashMap::new())),
1048 namespace_index: Arc::new(RwLock::new(HashMap::new())),
1049 }
1050 }
1051
1052 pub fn register_entry(&self, key: &CacheKey, tags: &HashMap<String, String>) {
1054 {
1056 let mut ns_index = self.namespace_index.write().expect("lock poisoned");
1057 ns_index
1058 .entry(key.namespace.clone())
1059 .or_default()
1060 .push(key.clone());
1061 }
1062
1063 {
1065 let mut tag_idx = self.tag_index.write().expect("lock poisoned");
1066 for (tag_key, tag_value) in tags {
1067 tag_idx
1068 .entry(tag_key.clone())
1069 .or_default()
1070 .entry(tag_value.clone())
1071 .or_default()
1072 .push(key.clone());
1073 }
1074 }
1075 }
1076
1077 pub fn unregister_entry(&self, key: &CacheKey) {
1079 {
1081 let mut ns_index = self.namespace_index.write().expect("lock poisoned");
1082 if let Some(keys) = ns_index.get_mut(&key.namespace) {
1083 keys.retain(|k| k != key);
1084 if keys.is_empty() {
1085 ns_index.remove(&key.namespace);
1086 }
1087 }
1088 }
1089
1090 {
1092 let mut tag_idx = self.tag_index.write().expect("lock poisoned");
1093 let mut tags_to_remove = Vec::new();
1094
1095 for (tag_key, tag_values) in tag_idx.iter_mut() {
1096 let mut values_to_remove = Vec::new();
1097
1098 for (tag_value, keys) in tag_values.iter_mut() {
1099 keys.retain(|k| k != key);
1100 if keys.is_empty() {
1101 values_to_remove.push(tag_value.clone());
1102 }
1103 }
1104
1105 for value in values_to_remove {
1106 tag_values.remove(&value);
1107 }
1108
1109 if tag_values.is_empty() {
1110 tags_to_remove.push(tag_key.clone());
1111 }
1112 }
1113
1114 for tag in tags_to_remove {
1115 tag_idx.remove(&tag);
1116 }
1117 }
1118 }
1119
1120 pub fn invalidate_by_tag(&self, tag_key: &str, tag_value: &str) -> Result<usize> {
1122 let keys_to_invalidate = {
1123 let tag_idx = self.tag_index.read().expect("lock poisoned");
1124 tag_idx
1125 .get(tag_key)
1126 .and_then(|values| values.get(tag_value))
1127 .cloned()
1128 .unwrap_or_default()
1129 };
1130
1131 let mut invalidated_count = 0;
1132 for key in &keys_to_invalidate {
1133 if self.cache.remove(key).is_ok() {
1134 invalidated_count += 1;
1135 }
1136 self.unregister_entry(key);
1137 }
1138
1139 Ok(invalidated_count)
1140 }
1141
1142 pub fn invalidate_namespace(&self, namespace: &str) -> Result<usize> {
1144 let keys_to_invalidate = {
1145 let ns_index = self.namespace_index.read().expect("lock poisoned");
1146 ns_index.get(namespace).cloned().unwrap_or_default()
1147 };
1148
1149 let mut invalidated_count = 0;
1150 for key in &keys_to_invalidate {
1151 if self.cache.remove(key).is_ok() {
1152 invalidated_count += 1;
1153 }
1154 self.unregister_entry(key);
1155 }
1156
1157 Ok(invalidated_count)
1158 }
1159
1160 pub fn invalidate_expired(&self) -> Result<usize> {
1162 if let Some(ref persistent) = self.cache.persistent_cache {
1165 return self.scan_and_remove_expired_files(persistent);
1166 }
1167 Ok(0)
1168 }
1169
1170 fn scan_and_remove_expired_files(&self, persistent_cache: &PersistentCache) -> Result<usize> {
1172 let cache_dir = &persistent_cache.cache_dir;
1173 let mut removed_count = 0;
1174
1175 if !cache_dir.exists() {
1176 return Ok(0);
1177 }
1178
1179 for entry in std::fs::read_dir(cache_dir)? {
1181 let entry = entry?;
1182 if entry.file_type()?.is_dir() {
1183 for sub_entry in std::fs::read_dir(entry.path())? {
1185 let sub_entry = sub_entry?;
1186 if sub_entry.file_type()?.is_file() {
1187 if let Some(file_name) = sub_entry.file_name().to_str() {
1188 if file_name.ends_with(".cache") {
1189 if let Some(cache_key) =
1191 persistent_cache.decode_cache_key_from_filename(file_name)
1192 {
1193 if let Ok(Some(entry)) = persistent_cache.load(&cache_key) {
1195 if entry.is_expired() {
1196 let _ = std::fs::remove_file(sub_entry.path());
1197 removed_count += 1;
1198 }
1199 } else {
1200 let _ = std::fs::remove_file(sub_entry.path());
1202 removed_count += 1;
1203 }
1204 } else {
1205 if let Ok(metadata) = std::fs::metadata(sub_entry.path()) {
1207 if let Ok(modified) = metadata.modified() {
1208 let age = modified
1209 .elapsed()
1210 .unwrap_or(Duration::from_secs(0));
1211 if age > Duration::from_secs(24 * 3600) {
1213 let _ = std::fs::remove_file(sub_entry.path());
1214 removed_count += 1;
1215 }
1216 }
1217 }
1218 }
1219 }
1220 }
1221 }
1222 }
1223 }
1224 }
1225
1226 Ok(removed_count)
1227 }
1228
1229 pub fn get_stats(&self) -> InvalidationStats {
1231 let tag_idx = self.tag_index.read().expect("lock poisoned");
1232 let ns_index = self.namespace_index.read().expect("lock poisoned");
1233
1234 let total_tag_entries = tag_idx
1235 .values()
1236 .flat_map(|values| values.values())
1237 .map(|keys| keys.len())
1238 .sum();
1239
1240 let total_namespace_entries = ns_index.values().map(|keys| keys.len()).sum();
1241
1242 InvalidationStats {
1243 tracked_tags: tag_idx.len(),
1244 tracked_namespaces: ns_index.len(),
1245 total_tag_entries,
1246 total_namespace_entries,
1247 }
1248 }
1249}
1250
1251#[derive(Debug, Clone)]
1253pub struct InvalidationStats {
1254 pub tracked_tags: usize,
1255 pub tracked_namespaces: usize,
1256 pub total_tag_entries: usize,
1257 pub total_namespace_entries: usize,
1258}
1259
1260pub struct BackgroundCacheWorker {
1262 cache: Arc<MultiLevelCache>,
1263 invalidator: Arc<CacheInvalidator>,
1264 config: CacheConfig,
1265 worker_handle: Option<JoinHandle<()>>,
1266 shutdown_signal: Arc<RwLock<bool>>,
1267}
1268
1269impl BackgroundCacheWorker {
1270 pub fn new(
1271 cache: Arc<MultiLevelCache>,
1272 invalidator: Arc<CacheInvalidator>,
1273 config: CacheConfig,
1274 ) -> Self {
1275 Self {
1276 cache,
1277 invalidator,
1278 config,
1279 worker_handle: None,
1280 shutdown_signal: Arc::new(RwLock::new(false)),
1281 }
1282 }
1283
1284 pub fn start(&mut self) -> Result<()> {
1286 if !self.config.enable_background_updates {
1287 return Ok(());
1288 }
1289
1290 let cache = Arc::clone(&self.cache);
1291 let invalidator = Arc::clone(&self.invalidator);
1292 let interval = self.config.background_update_interval;
1293 let shutdown_signal = Arc::clone(&self.shutdown_signal);
1294
1295 let handle = thread::spawn(move || {
1296 while let Ok(shutdown) = shutdown_signal.read() {
1297 if *shutdown {
1298 break;
1299 }
1300 drop(shutdown); if let Err(e) = Self::perform_maintenance(&cache, &invalidator) {
1304 tracing::warn!("Background cache maintenance error: {}", e);
1306 }
1307
1308 thread::sleep(interval);
1310 }
1311 });
1312
1313 self.worker_handle = Some(handle);
1314 Ok(())
1315 }
1316
1317 pub fn stop(&mut self) -> Result<()> {
1319 {
1321 let mut signal = self.shutdown_signal.write().expect("lock poisoned");
1322 *signal = true;
1323 }
1324
1325 if let Some(handle) = self.worker_handle.take() {
1327 handle
1328 .join()
1329 .map_err(|e| anyhow!("Failed to join worker thread: {:?}", e))?;
1330 }
1331
1332 Ok(())
1333 }
1334
1335 fn perform_maintenance(
1337 cache: &Arc<MultiLevelCache>,
1338 invalidator: &Arc<CacheInvalidator>,
1339 ) -> Result<()> {
1340 let expired_count = invalidator.invalidate_expired()?;
1342 if expired_count > 0 {
1343 println!("Background worker cleaned {expired_count} expired entries");
1344 }
1345
1346 let memory_stats = cache.get_memory_stats();
1348 let utilization = memory_stats.memory_bytes as f64 / memory_stats.max_memory_bytes as f64;
1349
1350 if utilization > 0.9 {
1351 Self::aggressive_cleanup(cache)?;
1353 }
1354
1355 Self::sync_hot_entries(cache)?;
1357
1358 Ok(())
1359 }
1360
1361 fn aggressive_cleanup(_cache: &Arc<MultiLevelCache>) -> Result<()> {
1363 println!("Performing aggressive cache cleanup due to high memory usage");
1366 Ok(())
1367 }
1368
1369 fn sync_hot_entries(_cache: &Arc<MultiLevelCache>) -> Result<()> {
1371 Ok(())
1374 }
1375}
1376
1377impl Drop for BackgroundCacheWorker {
1378 fn drop(&mut self) {
1379 let _ = self.stop();
1380 }
1381}
1382
1383pub struct CacheWarmer {
1385 cache: Arc<MultiLevelCache>,
1386}
1387
1388impl CacheWarmer {
1389 pub fn new(cache: Arc<MultiLevelCache>) -> Self {
1390 Self { cache }
1391 }
1392
1393 pub fn warm_with_data(&self, data: Vec<(CacheKey, Vector)>) -> Result<usize> {
1395 let mut loaded_count = 0;
1396
1397 for (key, vector) in data {
1398 if self.cache.insert(key, vector).is_ok() {
1399 loaded_count += 1;
1400 }
1401 }
1402
1403 Ok(loaded_count)
1404 }
1405
1406 pub fn warm_from_persistent(&self, keys: Vec<CacheKey>) -> Result<usize> {
1408 let mut loaded_count = 0;
1409
1410 for key in keys {
1411 if self.cache.get(&key).is_some() {
1413 loaded_count += 1;
1414 }
1415 }
1416
1417 Ok(loaded_count)
1418 }
1419
1420 pub fn warm_with_generator<F>(&self, count: usize, generator: F) -> Result<usize>
1422 where
1423 F: Fn(usize) -> Option<(CacheKey, Vector)>,
1424 {
1425 let mut loaded_count = 0;
1426
1427 for i in 0..count {
1428 if let Some((key, vector)) = generator(i) {
1429 if self.cache.insert(key, vector).is_ok() {
1430 loaded_count += 1;
1431 }
1432 }
1433 }
1434
1435 Ok(loaded_count)
1436 }
1437}
1438
1439pub struct CacheAnalyzer {
1441 cache: Arc<MultiLevelCache>,
1442 invalidator: Arc<CacheInvalidator>,
1443}
1444
1445#[derive(Debug, Clone)]
1446pub struct CacheAnalysisReport {
1447 pub memory_utilization: f64,
1448 pub hit_ratio: f64,
1449 pub persistent_hit_ratio: f64,
1450 pub most_accessed_namespaces: Vec<(String, usize)>,
1451 pub recommendations: Vec<String>,
1452 pub performance_score: f64, }
1454
1455impl CacheAnalyzer {
1456 pub fn new(cache: Arc<MultiLevelCache>, invalidator: Arc<CacheInvalidator>) -> Self {
1457 Self { cache, invalidator }
1458 }
1459
1460 pub fn analyze(&self) -> CacheAnalysisReport {
1462 let stats = self.cache.get_stats();
1463 let memory_stats = self.cache.get_memory_stats();
1464 let invalidation_stats = self.invalidator.get_stats();
1465
1466 let memory_utilization =
1467 memory_stats.memory_bytes as f64 / memory_stats.max_memory_bytes as f64;
1468
1469 let total_requests = stats.total_requests;
1470 let total_hits = stats.memory_hits + stats.persistent_hits;
1471 let hit_ratio = if total_requests > 0 {
1472 total_hits as f64 / total_requests as f64
1473 } else {
1474 0.0
1475 };
1476
1477 let persistent_hit_ratio = if stats.persistent_hits + stats.persistent_misses > 0 {
1478 stats.persistent_hits as f64 / (stats.persistent_hits + stats.persistent_misses) as f64
1479 } else {
1480 0.0
1481 };
1482
1483 let mut recommendations = Vec::new();
1484
1485 if hit_ratio < 0.5 {
1487 recommendations
1488 .push("Consider increasing cache size or adjusting eviction policy".to_string());
1489 }
1490
1491 if memory_utilization > 0.9 {
1492 recommendations.push(
1493 "Memory cache is near capacity - consider increasing max_memory_bytes".to_string(),
1494 );
1495 }
1496
1497 if persistent_hit_ratio < 0.3 {
1498 recommendations
1499 .push("Persistent cache hit ratio is low - review TTL settings".to_string());
1500 }
1501
1502 if invalidation_stats.tracked_namespaces > 100 {
1503 recommendations
1504 .push("Consider consolidating namespaces to reduce tracking overhead".to_string());
1505 }
1506
1507 let performance_score =
1509 (hit_ratio * 0.4 + (1.0 - memory_utilization) * 0.3 + persistent_hit_ratio * 0.3)
1510 .clamp(0.0, 1.0);
1511
1512 CacheAnalysisReport {
1513 memory_utilization,
1514 hit_ratio,
1515 persistent_hit_ratio,
1516 most_accessed_namespaces: vec![], recommendations,
1518 performance_score,
1519 }
1520 }
1521
1522 pub fn get_optimization_recommendations(&self) -> Vec<String> {
1524 self.analyze().recommendations
1525 }
1526}
1527
1528#[cfg(test)]
1529mod tests {
1530 use super::*;
1531 use tempfile::TempDir;
1532
1533 #[test]
1534 fn test_cache_key() {
1535 let key = CacheKey::new("embeddings", "test_doc").with_variant("v1");
1536
1537 assert_eq!(key.namespace, "embeddings");
1538 assert_eq!(key.key, "test_doc");
1539 assert_eq!(key.variant, Some("v1".to_string()));
1540 assert_eq!(key.to_string(), "embeddings:test_doc:v1");
1541 }
1542
1543 #[test]
1544 fn test_memory_cache() {
1545 let config = CacheConfig {
1546 max_memory_entries: 2,
1547 max_memory_bytes: 1024,
1548 ..Default::default()
1549 };
1550
1551 let mut cache = MemoryCache::new(config);
1552
1553 let key1 = CacheKey::new("test", "key1");
1554 let key2 = CacheKey::new("test", "key2");
1555 let key3 = CacheKey::new("test", "key3");
1556
1557 let vector1 = Vector::new(vec![1.0, 2.0, 3.0]);
1558 let vector2 = Vector::new(vec![4.0, 5.0, 6.0]);
1559 let vector3 = Vector::new(vec![7.0, 8.0, 9.0]);
1560
1561 cache
1563 .insert(key1.clone(), CacheEntry::new(vector1.clone()))
1564 .unwrap();
1565 cache
1566 .insert(key2.clone(), CacheEntry::new(vector2.clone()))
1567 .unwrap();
1568
1569 assert!(cache.get(&key1).is_some());
1571 assert!(cache.get(&key2).is_some());
1572
1573 cache
1575 .insert(key3.clone(), CacheEntry::new(vector3.clone()))
1576 .unwrap();
1577
1578 let remaining = cache.entries.len();
1580 assert_eq!(remaining, 2);
1581 }
1582
1583 #[test]
1584 fn test_persistent_cache() {
1585 let temp_dir = TempDir::new().unwrap();
1586
1587 let config = CacheConfig {
1588 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1589 enable_compression: true,
1590 ..Default::default()
1591 };
1592
1593 let cache = PersistentCache::new(config).unwrap();
1594
1595 let key = CacheKey::new("test", "persistent_key");
1596 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1597 let entry = CacheEntry::new(vector.clone());
1598
1599 cache.store(&key, &entry).unwrap();
1601 let retrieved = cache.load(&key).unwrap();
1602
1603 assert!(retrieved.is_some());
1605 let retrieved_entry = retrieved.unwrap();
1606 assert_eq!(retrieved_entry.data.as_f32(), vector.as_f32());
1607 }
1608
1609 #[test]
1610 fn test_multi_level_cache() {
1611 let temp_dir = TempDir::new().unwrap();
1612
1613 let config = CacheConfig {
1614 max_memory_entries: 2,
1615 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1616 enable_persistent: true,
1617 ..Default::default()
1618 };
1619
1620 let cache = MultiLevelCache::new(config).unwrap();
1621
1622 let key = CacheKey::new("test", "multi_level");
1623 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1624
1625 cache.insert(key.clone(), vector.clone()).unwrap();
1627 let retrieved = cache.get(&key).unwrap();
1628
1629 assert_eq!(retrieved.as_f32(), vector.as_f32());
1630
1631 let stats = cache.get_stats();
1633 assert_eq!(stats.total_requests, 1);
1634 assert_eq!(stats.memory_hits, 1);
1635 }
1636
1637 #[test]
1638 fn test_cache_expiration() {
1639 let config = CacheConfig {
1640 max_memory_entries: 10,
1641 ttl: Some(Duration::from_millis(10)),
1642 ..Default::default()
1643 };
1644
1645 let mut cache = MemoryCache::new(config);
1646
1647 let key = CacheKey::new("test", "expiring");
1648 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1649 let entry = CacheEntry::new(vector).with_ttl(Duration::from_millis(10));
1650
1651 cache.insert(key.clone(), entry).unwrap();
1652
1653 assert!(cache.get(&key).is_some());
1655
1656 std::thread::sleep(Duration::from_millis(20));
1658
1659 assert!(cache.get(&key).is_none());
1661 }
1662
1663 #[test]
1664 fn test_arc_eviction_policy() {
1665 let config = CacheConfig {
1666 max_memory_entries: 3,
1667 eviction_policy: EvictionPolicy::ARC,
1668 ..Default::default()
1669 };
1670
1671 let mut cache = MemoryCache::new(config);
1672
1673 let key1 = CacheKey::new("test", "arc1");
1674 let key2 = CacheKey::new("test", "arc2");
1675 let key3 = CacheKey::new("test", "arc3");
1676 let key4 = CacheKey::new("test", "arc4");
1677
1678 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1679
1680 cache
1682 .insert(key1.clone(), CacheEntry::new(vector.clone()))
1683 .unwrap();
1684 cache
1685 .insert(key2.clone(), CacheEntry::new(vector.clone()))
1686 .unwrap();
1687 cache
1688 .insert(key3.clone(), CacheEntry::new(vector.clone()))
1689 .unwrap();
1690
1691 cache.get(&key1);
1693 cache.get(&key1);
1694 cache.get(&key1);
1695
1696 cache
1698 .insert(key4.clone(), CacheEntry::new(vector.clone()))
1699 .unwrap();
1700
1701 assert!(cache.get(&key1).is_some());
1703
1704 assert_eq!(cache.entries.len(), 3);
1706 }
1707
1708 #[test]
1709 fn test_cache_warmer() {
1710 let temp_dir = TempDir::new().unwrap();
1711
1712 let config = CacheConfig {
1713 max_memory_entries: 10,
1714 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1715 enable_persistent: true,
1716 ..Default::default()
1717 };
1718
1719 let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1720 let warmer = CacheWarmer::new(Arc::clone(&cache));
1721
1722 let test_data = vec![
1724 (CacheKey::new("test", "warm1"), Vector::new(vec![1.0, 2.0])),
1725 (CacheKey::new("test", "warm2"), Vector::new(vec![3.0, 4.0])),
1726 (CacheKey::new("test", "warm3"), Vector::new(vec![5.0, 6.0])),
1727 ];
1728
1729 let loaded_count = warmer.warm_with_data(test_data.clone()).unwrap();
1731 assert_eq!(loaded_count, 3);
1732
1733 for (key, expected_vector) in test_data {
1735 let cached_vector = cache.get(&key).unwrap();
1736 assert_eq!(cached_vector.as_f32(), expected_vector.as_f32());
1737 }
1738 }
1739
1740 #[test]
1741 fn test_cache_warmer_with_generator() {
1742 let temp_dir = TempDir::new().unwrap();
1743
1744 let config = CacheConfig {
1745 max_memory_entries: 10,
1746 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1747 enable_persistent: true,
1748 ..Default::default()
1749 };
1750
1751 let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1752 let warmer = CacheWarmer::new(Arc::clone(&cache));
1753
1754 let loaded_count = warmer
1756 .warm_with_generator(5, |i| {
1757 Some((
1758 CacheKey::new("generated", format!("item_{i}")),
1759 Vector::new(vec![i as f32, (i * 2) as f32]),
1760 ))
1761 })
1762 .unwrap();
1763
1764 assert_eq!(loaded_count, 5);
1765
1766 for i in 0..5 {
1768 let key = CacheKey::new("generated", format!("item_{i}"));
1769 let cached_vector = cache.get(&key).unwrap();
1770 assert_eq!(cached_vector.as_f32(), vec![i as f32, (i * 2) as f32]);
1771 }
1772 }
1773
1774 #[test]
1775 fn test_cache_analyzer() {
1776 let temp_dir = TempDir::new().unwrap();
1777
1778 let config = CacheConfig {
1779 max_memory_entries: 10,
1780 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1781 enable_persistent: true,
1782 ..Default::default()
1783 };
1784
1785 let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1786 let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1787 let analyzer = CacheAnalyzer::new(Arc::clone(&cache), Arc::clone(&invalidator));
1788
1789 let key1 = CacheKey::new("test", "analyze1");
1791 let key2 = CacheKey::new("test", "analyze2");
1792 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1793
1794 cache.insert(key1.clone(), vector.clone()).unwrap();
1795 cache.insert(key2.clone(), vector.clone()).unwrap();
1796
1797 cache.get(&key1);
1799 cache.get(&key2);
1800 cache.get(&key1); cache.get(&CacheKey::new("test", "nonexistent")); let report = analyzer.analyze();
1805
1806 assert!(report.hit_ratio > 0.0);
1807 assert!(report.memory_utilization >= 0.0 && report.memory_utilization <= 1.0);
1808 assert!(report.performance_score >= 0.0 && report.performance_score <= 1.0);
1809
1810 let recommendations = analyzer.get_optimization_recommendations();
1812 assert!(!recommendations.is_empty());
1814 }
1815
1816 #[test]
1817 fn test_background_cache_worker() {
1818 let temp_dir = TempDir::new().unwrap();
1819
1820 let config = CacheConfig {
1821 max_memory_entries: 10,
1822 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1823 enable_persistent: true,
1824 enable_background_updates: true,
1825 background_update_interval: Duration::from_millis(100),
1826 ..Default::default()
1827 };
1828
1829 let cache = Arc::new(MultiLevelCache::new(config.clone()).unwrap());
1830 let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1831 let mut worker =
1832 BackgroundCacheWorker::new(Arc::clone(&cache), Arc::clone(&invalidator), config);
1833
1834 worker.start().unwrap();
1836
1837 let key = CacheKey::new("test", "background");
1839 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1840 cache.insert(key.clone(), vector.clone()).unwrap();
1841
1842 std::thread::sleep(Duration::from_millis(150));
1844
1845 worker.stop().unwrap();
1847
1848 assert!(cache.get(&key).is_some());
1850 }
1851
1852 #[test]
1853 fn test_cache_invalidation_by_tag() {
1854 let temp_dir = TempDir::new().unwrap();
1855
1856 let config = CacheConfig {
1857 max_memory_entries: 10,
1858 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1859 enable_persistent: true,
1860 ..Default::default()
1861 };
1862
1863 let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1864 let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1865
1866 let key1 = CacheKey::new("test", "tagged1");
1868 let key2 = CacheKey::new("test", "tagged2");
1869 let key3 = CacheKey::new("test", "tagged3");
1870
1871 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1872
1873 cache.insert(key1.clone(), vector.clone()).unwrap();
1874 cache.insert(key2.clone(), vector.clone()).unwrap();
1875 cache.insert(key3.clone(), vector.clone()).unwrap();
1876
1877 let mut tags1 = HashMap::new();
1879 tags1.insert("category".to_string(), "embeddings".to_string());
1880 invalidator.register_entry(&key1, &tags1);
1881
1882 let mut tags2 = HashMap::new();
1883 tags2.insert("category".to_string(), "embeddings".to_string());
1884 invalidator.register_entry(&key2, &tags2);
1885
1886 let mut tags3 = HashMap::new();
1887 tags3.insert("category".to_string(), "vectors".to_string());
1888 invalidator.register_entry(&key3, &tags3);
1889
1890 let invalidated_count = invalidator
1892 .invalidate_by_tag("category", "embeddings")
1893 .unwrap();
1894 assert_eq!(invalidated_count, 2);
1895
1896 assert!(cache.get(&key1).is_none());
1898 assert!(cache.get(&key2).is_none());
1899
1900 assert!(cache.get(&key3).is_some());
1902 }
1903
1904 #[test]
1905 fn test_cache_invalidation_by_namespace() {
1906 let temp_dir = TempDir::new().unwrap();
1907
1908 let config = CacheConfig {
1909 max_memory_entries: 10,
1910 persistent_cache_dir: Some(temp_dir.path().to_path_buf()),
1911 enable_persistent: true,
1912 ..Default::default()
1913 };
1914
1915 let cache = Arc::new(MultiLevelCache::new(config).unwrap());
1916 let invalidator = Arc::new(CacheInvalidator::new(Arc::clone(&cache)));
1917
1918 let key1 = CacheKey::new("embeddings", "item1");
1920 let key2 = CacheKey::new("embeddings", "item2");
1921 let key3 = CacheKey::new("vectors", "item3");
1922
1923 let vector = Vector::new(vec![1.0, 2.0, 3.0]);
1924
1925 cache.insert(key1.clone(), vector.clone()).unwrap();
1926 cache.insert(key2.clone(), vector.clone()).unwrap();
1927 cache.insert(key3.clone(), vector.clone()).unwrap();
1928
1929 invalidator.register_entry(&key1, &HashMap::new());
1931 invalidator.register_entry(&key2, &HashMap::new());
1932 invalidator.register_entry(&key3, &HashMap::new());
1933
1934 let invalidated_count = invalidator.invalidate_namespace("embeddings").unwrap();
1936 assert_eq!(invalidated_count, 2);
1937
1938 assert!(cache.get(&key1).is_none());
1940 assert!(cache.get(&key2).is_none());
1941
1942 assert!(cache.get(&key3).is_some());
1944 }
1945}