Skip to main content

oxirs_gql/
advanced_cache.rs

1//! Advanced GraphQL Caching System
2//!
3//! This module provides sophisticated caching strategies including adaptive TTL,
4//! cache warming, intelligent invalidation, and multi-tier caching.
5
6// anyhow removed - unused import
7use serde::{Deserialize, Serialize};
8use std::cmp::Reverse;
9use std::collections::{HashMap, HashSet};
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime};
12use tokio::sync::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
13use tokio::time::interval;
14
15/// Cache configuration with adaptive settings
16#[derive(Debug, Clone)]
17pub struct AdvancedCacheConfig {
18    pub l1_max_size: usize,             // In-memory cache size
19    pub l2_max_size: usize,             // Persistent cache size
20    pub default_ttl: Duration,          // Default cache TTL
21    pub adaptive_ttl: bool,             // Enable adaptive TTL
22    pub max_ttl: Duration,              // Maximum TTL
23    pub min_ttl: Duration,              // Minimum TTL
24    pub cache_warming: bool,            // Enable cache warming
25    pub intelligent_invalidation: bool, // Enable smart invalidation
26    pub compression: bool,              // Enable cache compression
27    pub persistence: bool,              // Enable persistent cache
28    pub metrics_collection: bool,       // Enable detailed metrics
29}
30
31impl Default for AdvancedCacheConfig {
32    fn default() -> Self {
33        Self {
34            l1_max_size: 1000,
35            l2_max_size: 10000,
36            default_ttl: Duration::from_secs(300),
37            adaptive_ttl: true,
38            max_ttl: Duration::from_secs(3600),
39            min_ttl: Duration::from_secs(30),
40            cache_warming: true,
41            intelligent_invalidation: true,
42            compression: true,
43            persistence: false,
44            metrics_collection: true,
45        }
46    }
47}
48
49/// Cache entry with metadata
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CacheEntry {
52    pub data: serde_json::Value,
53    pub created_at: SystemTime,
54    pub ttl: Duration,
55    pub access_count: u32,
56    pub last_accessed: SystemTime,
57    pub size_bytes: usize,
58    pub query_complexity: usize,
59    pub dependencies: HashSet<String>,
60    pub tags: HashSet<String>,
61}
62
63impl CacheEntry {
64    pub fn new(data: serde_json::Value, ttl: Duration) -> Self {
65        let size_bytes = data.to_string().len();
66        Self {
67            data,
68            created_at: SystemTime::now(),
69            ttl,
70            access_count: 0,
71            last_accessed: SystemTime::now(),
72            size_bytes,
73            query_complexity: 0,
74            dependencies: HashSet::new(),
75            tags: HashSet::new(),
76        }
77    }
78
79    pub fn is_expired(&self) -> bool {
80        self.created_at.elapsed().unwrap_or(Duration::from_secs(0)) > self.ttl
81    }
82
83    pub fn access(&mut self) {
84        self.access_count += 1;
85        self.last_accessed = SystemTime::now();
86    }
87
88    pub fn with_complexity(mut self, complexity: usize) -> Self {
89        self.query_complexity = complexity;
90        self
91    }
92
93    pub fn with_dependencies(mut self, deps: HashSet<String>) -> Self {
94        self.dependencies = deps;
95        self
96    }
97
98    pub fn with_tags(mut self, tags: HashSet<String>) -> Self {
99        self.tags = tags;
100        self
101    }
102}
103
104/// Cache statistics and metrics
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct CacheMetrics {
107    pub l1_hits: u64,
108    pub l1_misses: u64,
109    pub l2_hits: u64,
110    pub l2_misses: u64,
111    pub evictions: u64,
112    pub invalidations: u64,
113    pub warming_operations: u64,
114    pub total_size_bytes: usize,
115    pub average_ttl: Duration,
116    pub hit_ratio: f64,
117    pub popular_queries: Vec<PopularQuery>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct PopularQuery {
122    pub cache_key: String,
123    pub access_count: u32,
124    pub avg_execution_time: Duration,
125    pub last_accessed: SystemTime,
126}
127
128/// Cache warming strategy
129#[derive(Debug, Clone)]
130pub enum WarmingStrategy {
131    Popular,    // Warm most popular queries
132    Predictive, // Warm based on usage patterns
133    Schedule,   // Warm on schedule
134    Dependency, // Warm based on dependencies
135}
136
137/// Cache invalidation strategy
138#[derive(Debug, Clone)]
139pub enum InvalidationStrategy {
140    TTL,        // Time-based invalidation
141    Manual,     // Manual invalidation
142    Dependency, // Dependency-based invalidation
143    Smart,      // Intelligent invalidation
144}
145
146/// Advanced multi-tier cache system
147pub struct AdvancedCache {
148    config: AdvancedCacheConfig,
149    l1_cache: Arc<AsyncRwLock<HashMap<String, CacheEntry>>>,
150    l2_cache: Arc<AsyncRwLock<HashMap<String, CacheEntry>>>,
151    metrics: Arc<AsyncRwLock<CacheMetrics>>,
152    access_patterns: Arc<AsyncRwLock<HashMap<String, AccessPattern>>>,
153    dependency_graph: Arc<AsyncRwLock<DependencyGraph>>,
154    warming_queue: Arc<AsyncMutex<Vec<WarmingTask>>>,
155}
156
157/// Access pattern tracking
158#[derive(Debug, Clone)]
159struct AccessPattern {
160    access_times: VecDeque<Instant>,
161    frequency: f64,
162    #[allow(dead_code)]
163    last_prediction: Option<Instant>,
164}
165
166use std::collections::VecDeque;
167
168impl AccessPattern {
169    fn new() -> Self {
170        Self {
171            access_times: VecDeque::new(),
172            frequency: 0.0,
173            last_prediction: None,
174        }
175    }
176
177    fn record_access(&mut self, now: Instant) {
178        self.access_times.push_back(now);
179
180        // Keep only recent accesses (last hour)
181        let cutoff = now - Duration::from_secs(3600);
182        while let Some(&front) = self.access_times.front() {
183            if front < cutoff {
184                self.access_times.pop_front();
185            } else {
186                break;
187            }
188        }
189
190        // Calculate frequency (accesses per hour)
191        self.frequency = self.access_times.len() as f64;
192    }
193
194    #[allow(dead_code)]
195    fn predict_next_access(&self) -> Option<Instant> {
196        if self.access_times.len() < 2 {
197            return None;
198        }
199
200        // Simple prediction based on average interval
201        let intervals: Vec<Duration> = self
202            .access_times
203            .iter()
204            .zip(self.access_times.iter().skip(1))
205            .map(|(first, second)| *second - *first)
206            .collect();
207
208        if intervals.is_empty() {
209            return None;
210        }
211
212        let total_nanos =
213            intervals.iter().map(|d| d.as_nanos()).sum::<u128>() / intervals.len() as u128;
214        let avg_interval = Duration::from_nanos(total_nanos.min(u64::MAX as u128) as u64);
215
216        self.access_times.back().map(|&last| last + avg_interval)
217    }
218}
219
220/// Dependency tracking graph
221#[derive(Debug, Default)]
222struct DependencyGraph {
223    dependencies: HashMap<String, HashSet<String>>,
224    dependents: HashMap<String, HashSet<String>>,
225}
226
227impl DependencyGraph {
228    fn add_dependency(&mut self, cache_key: &str, dependency: &str) {
229        self.dependencies
230            .entry(cache_key.to_string())
231            .or_default()
232            .insert(dependency.to_string());
233
234        self.dependents
235            .entry(dependency.to_string())
236            .or_default()
237            .insert(cache_key.to_string());
238    }
239
240    fn get_affected_keys(&self, changed_key: &str) -> HashSet<String> {
241        let mut affected = HashSet::new();
242        let mut to_check = vec![changed_key.to_string()];
243
244        while let Some(key) = to_check.pop() {
245            if let Some(dependents) = self.dependents.get(&key) {
246                for dependent in dependents {
247                    if affected.insert(dependent.clone()) {
248                        to_check.push(dependent.clone());
249                    }
250                }
251            }
252        }
253
254        affected
255    }
256}
257
258/// Cache warming task
259#[derive(Debug, Clone)]
260struct WarmingTask {
261    #[allow(dead_code)]
262    cache_key: String,
263    #[allow(dead_code)]
264    query: String,
265    #[allow(dead_code)]
266    variables: HashMap<String, serde_json::Value>,
267    #[allow(dead_code)]
268    priority: u8,
269    scheduled_time: Instant,
270}
271
272impl AdvancedCache {
273    /// Create a new advanced cache system
274    pub fn new(config: AdvancedCacheConfig) -> Self {
275        let cache = Self {
276            config: config.clone(),
277            l1_cache: Arc::new(AsyncRwLock::new(HashMap::new())),
278            l2_cache: Arc::new(AsyncRwLock::new(HashMap::new())),
279            metrics: Arc::new(AsyncRwLock::new(CacheMetrics {
280                l1_hits: 0,
281                l1_misses: 0,
282                l2_hits: 0,
283                l2_misses: 0,
284                evictions: 0,
285                invalidations: 0,
286                warming_operations: 0,
287                total_size_bytes: 0,
288                average_ttl: config.default_ttl,
289                hit_ratio: 0.0,
290                popular_queries: Vec::new(),
291            })),
292            access_patterns: Arc::new(AsyncRwLock::new(HashMap::new())),
293            dependency_graph: Arc::new(AsyncRwLock::new(DependencyGraph::default())),
294            warming_queue: Arc::new(AsyncMutex::new(Vec::new())),
295        };
296
297        // Start background tasks
298        cache.start_cleanup_task();
299        cache.start_warming_task();
300        cache.start_metrics_task();
301
302        cache
303    }
304
305    /// Get a value from cache with intelligent tier selection
306    pub async fn get(&self, key: &str) -> Option<serde_json::Value> {
307        // Try L1 cache first
308        {
309            let mut l1 = self.l1_cache.write().await;
310            if let Some(entry) = l1.get_mut(key) {
311                if !entry.is_expired() {
312                    entry.access();
313                    self.update_access_pattern(key).await;
314                    self.record_l1_hit().await;
315                    return Some(entry.data.clone());
316                } else {
317                    l1.remove(key);
318                }
319            }
320        }
321
322        self.record_l1_miss().await;
323
324        // Try L2 cache
325        {
326            let mut l2 = self.l2_cache.write().await;
327            if let Some(entry) = l2.get_mut(key) {
328                if !entry.is_expired() {
329                    entry.access();
330
331                    // Promote to L1 if frequently accessed
332                    if entry.access_count > 5 {
333                        self.promote_to_l1(key.to_string(), entry.clone()).await;
334                    }
335
336                    self.update_access_pattern(key).await;
337                    self.record_l2_hit().await;
338                    return Some(entry.data.clone());
339                } else {
340                    l2.remove(key);
341                }
342            }
343        }
344
345        self.record_l2_miss().await;
346        None
347    }
348
349    /// Store a value in cache with intelligent tier placement
350    pub async fn set(
351        &self,
352        key: String,
353        value: serde_json::Value,
354        ttl: Option<Duration>,
355        complexity: Option<usize>,
356        dependencies: Option<HashSet<String>>,
357        tags: Option<HashSet<String>>,
358    ) {
359        let effective_ttl = if self.config.adaptive_ttl {
360            self.calculate_adaptive_ttl(&key, complexity.unwrap_or(0))
361                .await
362        } else {
363            ttl.unwrap_or(self.config.default_ttl)
364        };
365
366        let mut entry = CacheEntry::new(value, effective_ttl);
367
368        if let Some(complexity) = complexity {
369            entry = entry.with_complexity(complexity);
370        }
371
372        if let Some(deps) = dependencies {
373            entry = entry.with_dependencies(deps.clone());
374            self.add_dependencies(&key, &deps).await;
375        }
376
377        if let Some(tags) = tags {
378            entry = entry.with_tags(tags);
379        }
380
381        // Decide tier placement based on expected access pattern
382        let should_use_l1 = self.should_place_in_l1(&key, &entry).await;
383
384        if should_use_l1 {
385            self.set_l1(key, entry).await;
386        } else {
387            self.set_l2(key, entry).await;
388        }
389    }
390
391    /// Intelligent cache invalidation
392    pub async fn invalidate(&self, key: &str, strategy: InvalidationStrategy) {
393        match strategy {
394            InvalidationStrategy::TTL => {
395                // Natural TTL expiration - no action needed
396            }
397            InvalidationStrategy::Manual => {
398                self.remove_from_all_tiers(key).await;
399            }
400            InvalidationStrategy::Dependency => {
401                let affected_keys = self.get_affected_keys(key).await;
402                for affected_key in affected_keys {
403                    self.remove_from_all_tiers(&affected_key).await;
404                }
405            }
406            InvalidationStrategy::Smart => {
407                self.smart_invalidation(key).await;
408            }
409        }
410
411        self.record_invalidation().await;
412    }
413
414    /// Warm cache with predicted queries
415    pub async fn warm_cache(&self, strategy: WarmingStrategy) {
416        match strategy {
417            WarmingStrategy::Popular => {
418                self.warm_popular_queries().await;
419            }
420            WarmingStrategy::Predictive => {
421                self.warm_predicted_queries().await;
422            }
423            WarmingStrategy::Schedule => {
424                self.warm_scheduled_queries().await;
425            }
426            WarmingStrategy::Dependency => {
427                self.warm_dependency_queries().await;
428            }
429        }
430
431        self.record_warming_operation().await;
432    }
433
434    /// Get comprehensive cache metrics
435    pub async fn get_metrics(&self) -> CacheMetrics {
436        let mut metrics = self.metrics.read().await.clone();
437
438        // Calculate hit ratio
439        let total_requests =
440            metrics.l1_hits + metrics.l1_misses + metrics.l2_hits + metrics.l2_misses;
441        if total_requests > 0 {
442            metrics.hit_ratio = (metrics.l1_hits + metrics.l2_hits) as f64 / total_requests as f64;
443        }
444
445        // Update popular queries
446        metrics.popular_queries = self.get_popular_queries().await;
447
448        metrics
449    }
450
451    /// Clear all caches
452    pub async fn clear_all(&self) {
453        self.l1_cache.write().await.clear();
454        self.l2_cache.write().await.clear();
455        self.access_patterns.write().await.clear();
456        self.dependency_graph.write().await.dependencies.clear();
457        self.dependency_graph.write().await.dependents.clear();
458    }
459
460    // Private helper methods
461
462    async fn promote_to_l1(&self, key: String, entry: CacheEntry) {
463        self.set_l1(key, entry).await;
464    }
465
466    async fn set_l1(&self, key: String, entry: CacheEntry) {
467        let mut l1 = self.l1_cache.write().await;
468
469        // Evict if necessary
470        while l1.len() >= self.config.l1_max_size {
471            self.evict_l1_entry(&mut l1).await;
472        }
473
474        l1.insert(key, entry);
475    }
476
477    async fn set_l2(&self, key: String, entry: CacheEntry) {
478        let mut l2 = self.l2_cache.write().await;
479
480        // Evict if necessary
481        while l2.len() >= self.config.l2_max_size {
482            self.evict_l2_entry(&mut l2).await;
483        }
484
485        l2.insert(key, entry);
486    }
487
488    async fn evict_l1_entry(&self, l1: &mut HashMap<String, CacheEntry>) {
489        // LRU eviction
490        let mut oldest_key: Option<String> = None;
491        let mut oldest_time = SystemTime::now();
492
493        for (key, entry) in l1.iter() {
494            if entry.last_accessed < oldest_time {
495                oldest_time = entry.last_accessed;
496                oldest_key = Some(key.clone());
497            }
498        }
499
500        if let Some(key) = oldest_key {
501            if let Some(entry) = l1.remove(&key) {
502                // Demote to L2 if still valuable
503                if entry.access_count > 1 && !entry.is_expired() {
504                    self.set_l2(key, entry).await;
505                }
506            }
507        }
508
509        self.record_eviction().await;
510    }
511
512    async fn evict_l2_entry(&self, l2: &mut HashMap<String, CacheEntry>) {
513        // LRU eviction for L2
514        let mut oldest_key: Option<String> = None;
515        let mut oldest_time = SystemTime::now();
516
517        for (key, entry) in l2.iter() {
518            if entry.last_accessed < oldest_time {
519                oldest_time = entry.last_accessed;
520                oldest_key = Some(key.clone());
521            }
522        }
523
524        if let Some(key) = oldest_key {
525            l2.remove(&key);
526        }
527
528        self.record_eviction().await;
529    }
530
531    async fn should_place_in_l1(&self, key: &str, _entry: &CacheEntry) -> bool {
532        // Check access patterns to decide tier placement
533        let patterns = self.access_patterns.read().await;
534        if let Some(pattern) = patterns.get(key) {
535            pattern.frequency > 10.0 // More than 10 accesses per hour
536        } else {
537            false // New entries go to L2 first
538        }
539    }
540
541    async fn calculate_adaptive_ttl(&self, key: &str, complexity: usize) -> Duration {
542        let patterns = self.access_patterns.read().await;
543
544        let base_ttl = if let Some(pattern) = patterns.get(key) {
545            // Higher frequency = longer TTL
546            let frequency_factor = (pattern.frequency / 10.0).clamp(0.5, 3.0);
547            Duration::from_secs(
548                (self.config.default_ttl.as_secs() as f64 * frequency_factor) as u64,
549            )
550        } else {
551            self.config.default_ttl
552        };
553
554        // Adjust based on complexity (more complex = longer TTL)
555        let complexity_factor = 1.0 + (complexity as f64 / 100.0).min(2.0);
556        let adjusted_ttl =
557            Duration::from_secs((base_ttl.as_secs() as f64 * complexity_factor) as u64);
558
559        // Clamp to min/max TTL
560        adjusted_ttl
561            .max(self.config.min_ttl)
562            .min(self.config.max_ttl)
563    }
564
565    async fn update_access_pattern(&self, key: &str) {
566        let mut patterns = self.access_patterns.write().await;
567        let pattern = patterns
568            .entry(key.to_string())
569            .or_insert_with(AccessPattern::new);
570        pattern.record_access(Instant::now());
571    }
572
573    async fn add_dependencies(&self, key: &str, dependencies: &HashSet<String>) {
574        let mut graph = self.dependency_graph.write().await;
575        for dep in dependencies {
576            graph.add_dependency(key, dep);
577        }
578    }
579
580    async fn get_affected_keys(&self, key: &str) -> HashSet<String> {
581        let graph = self.dependency_graph.read().await;
582        graph.get_affected_keys(key)
583    }
584
585    async fn remove_from_all_tiers(&self, key: &str) {
586        self.l1_cache.write().await.remove(key);
587        self.l2_cache.write().await.remove(key);
588    }
589
590    async fn smart_invalidation(&self, key: &str) {
591        // Implement intelligent invalidation logic
592        // For now, just remove from all tiers
593        self.remove_from_all_tiers(key).await;
594    }
595
596    async fn warm_popular_queries(&self) {
597        // Implementation for warming popular queries
598    }
599
600    async fn warm_predicted_queries(&self) {
601        // Implementation for warming predicted queries based on patterns
602    }
603
604    async fn warm_scheduled_queries(&self) {
605        // Implementation for warming scheduled queries
606    }
607
608    async fn warm_dependency_queries(&self) {
609        // Implementation for warming based on dependencies
610    }
611
612    async fn get_popular_queries(&self) -> Vec<PopularQuery> {
613        let mut popular = Vec::new();
614
615        // Collect from both tiers
616        let l1 = self.l1_cache.read().await;
617        let l2 = self.l2_cache.read().await;
618
619        for (key, entry) in l1.iter().chain(l2.iter()) {
620            if entry.access_count > 5 {
621                // Estimate execution time based on query complexity and cache frequency
622                let estimated_exec_time = if entry.query_complexity > 0 {
623                    Duration::from_millis((entry.query_complexity as u64 * 10).min(5000))
624                } else {
625                    Duration::from_millis(50) // Default for simple queries
626                };
627
628                popular.push(PopularQuery {
629                    cache_key: key.clone(),
630                    access_count: entry.access_count,
631                    avg_execution_time: estimated_exec_time,
632                    last_accessed: entry.last_accessed,
633                });
634            }
635        }
636
637        popular.sort_by_key(|p| Reverse(p.access_count));
638        popular.truncate(10);
639        popular
640    }
641
642    // Metrics recording methods
643    async fn record_l1_hit(&self) {
644        let mut metrics = self.metrics.write().await;
645        metrics.l1_hits += 1;
646    }
647
648    async fn record_l1_miss(&self) {
649        let mut metrics = self.metrics.write().await;
650        metrics.l1_misses += 1;
651    }
652
653    async fn record_l2_hit(&self) {
654        let mut metrics = self.metrics.write().await;
655        metrics.l2_hits += 1;
656    }
657
658    async fn record_l2_miss(&self) {
659        let mut metrics = self.metrics.write().await;
660        metrics.l2_misses += 1;
661    }
662
663    async fn record_eviction(&self) {
664        let mut metrics = self.metrics.write().await;
665        metrics.evictions += 1;
666    }
667
668    async fn record_invalidation(&self) {
669        let mut metrics = self.metrics.write().await;
670        metrics.invalidations += 1;
671    }
672
673    async fn record_warming_operation(&self) {
674        let mut metrics = self.metrics.write().await;
675        metrics.warming_operations += 1;
676    }
677
678    // Background task methods
679    fn start_cleanup_task(&self) {
680        let l1_cache = Arc::clone(&self.l1_cache);
681        let l2_cache = Arc::clone(&self.l2_cache);
682
683        tokio::spawn(async move {
684            let mut interval = interval(Duration::from_secs(60));
685
686            loop {
687                interval.tick().await;
688
689                // Clean expired entries from L1
690                {
691                    let mut l1 = l1_cache.write().await;
692                    l1.retain(|_, entry| !entry.is_expired());
693                }
694
695                // Clean expired entries from L2
696                {
697                    let mut l2 = l2_cache.write().await;
698                    l2.retain(|_, entry| !entry.is_expired());
699                }
700            }
701        });
702    }
703
704    fn start_warming_task(&self) {
705        if !self.config.cache_warming {
706            return;
707        }
708
709        let warming_queue = Arc::clone(&self.warming_queue);
710
711        tokio::spawn(async move {
712            let mut interval = interval(Duration::from_secs(300)); // Every 5 minutes
713
714            loop {
715                interval.tick().await;
716
717                // Process warming queue
718                let mut queue = warming_queue.lock().await;
719                let now = Instant::now();
720
721                queue.retain(|task| {
722                    if task.scheduled_time <= now {
723                        // Process warming task
724                        // Implementation would execute the query and cache result
725                        false // Remove from queue
726                    } else {
727                        true // Keep in queue
728                    }
729                });
730            }
731        });
732    }
733
734    fn start_metrics_task(&self) {
735        if !self.config.metrics_collection {
736            return;
737        }
738
739        let metrics = Arc::clone(&self.metrics);
740        let l1_cache = Arc::clone(&self.l1_cache);
741        let l2_cache = Arc::clone(&self.l2_cache);
742
743        tokio::spawn(async move {
744            let mut interval = interval(Duration::from_secs(60));
745
746            loop {
747                interval.tick().await;
748
749                // Update size metrics
750                let mut metrics = metrics.write().await;
751                let l1 = l1_cache.read().await;
752                let l2 = l2_cache.read().await;
753
754                metrics.total_size_bytes = l1
755                    .values()
756                    .chain(l2.values())
757                    .map(|entry| entry.size_bytes)
758                    .sum();
759            }
760        });
761    }
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767
768    #[tokio::test]
769    async fn test_advanced_cache_basic_operations() {
770        let config = AdvancedCacheConfig::default();
771        let cache = AdvancedCache::new(config);
772
773        let key = "test_key".to_string();
774        let value = serde_json::json!({"test": "value"});
775
776        // Test set and get
777        cache
778            .set(key.clone(), value.clone(), None, None, None, None)
779            .await;
780        let result = cache.get(&key).await;
781
782        assert_eq!(result, Some(value));
783    }
784
785    #[tokio::test]
786    async fn test_cache_tiers() {
787        let config = AdvancedCacheConfig {
788            l1_max_size: 1, // Force L2 usage
789            ..Default::default()
790        };
791
792        let cache = AdvancedCache::new(config);
793
794        // Fill L1
795        cache
796            .set(
797                "key1".to_string(),
798                serde_json::json!(1),
799                None,
800                None,
801                None,
802                None,
803            )
804            .await;
805        cache
806            .set(
807                "key2".to_string(),
808                serde_json::json!(2),
809                None,
810                None,
811                None,
812                None,
813            )
814            .await;
815
816        // key1 should be evicted to L2
817        let result1 = cache.get("key1").await;
818        let result2 = cache.get("key2").await;
819
820        assert_eq!(result1, Some(serde_json::json!(1)));
821        assert_eq!(result2, Some(serde_json::json!(2)));
822    }
823
824    #[tokio::test]
825    async fn test_dependency_invalidation() {
826        let cache = AdvancedCache::new(AdvancedCacheConfig::default());
827
828        let mut deps = HashSet::new();
829        deps.insert("dependency1".to_string());
830
831        cache
832            .set(
833                "main_key".to_string(),
834                serde_json::json!({"data": "test"}),
835                None,
836                None,
837                Some(deps),
838                None,
839            )
840            .await;
841
842        // Invalidate dependency
843        cache
844            .invalidate("dependency1", InvalidationStrategy::Dependency)
845            .await;
846
847        // Main key should be invalidated
848        let result = cache.get("main_key").await;
849        assert_eq!(result, None);
850    }
851}