1use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, VecDeque};
9use std::hash::{Hash, Hasher};
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12use tokio::sync::RwLock;
13use tracing::{debug, info, warn};
14
15use crate::ast::{Definition, Document, FragmentDefinition, OperationDefinition, Value};
16use crate::distributed_cache::{CacheConfig, GraphQLQueryCache};
17
18#[derive(Debug, Clone)]
20pub struct IntelligentCacheConfig {
21 pub max_cache_entries: usize,
22 pub max_pattern_history: usize,
23 pub prediction_confidence_threshold: f64,
24 pub cache_ttl_seconds: u64,
25 pub pattern_analysis_interval_seconds: u64,
26 pub enable_predictive_caching: bool,
27 pub enable_pattern_learning: bool,
28 pub enable_usage_analytics: bool,
29}
30
31impl Default for IntelligentCacheConfig {
32 fn default() -> Self {
33 Self {
34 max_cache_entries: 10000,
35 max_pattern_history: 1000,
36 prediction_confidence_threshold: 0.75,
37 cache_ttl_seconds: 3600, pattern_analysis_interval_seconds: 300, enable_predictive_caching: true,
40 enable_pattern_learning: true,
41 enable_usage_analytics: true,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct QueryPattern {
49 pub query_type: String,
50 pub field_count: usize,
51 pub depth: usize,
52 pub has_arguments: bool,
53 pub has_fragments: bool,
54 pub complexity_score: f64,
55 pub timestamp: u64,
56 pub access_frequency: f64,
57 pub execution_time_ms: u64,
58}
59
60impl QueryPattern {
61 pub fn from_document(doc: &Document) -> Self {
62 let operations = Self::extract_operations(doc);
63 let fragments = Self::extract_fragments(doc);
64
65 let query_type = operations
66 .first()
67 .map(|op| format!("{:?}", op.operation_type).to_lowercase())
68 .unwrap_or_else(|| "query".to_string());
69
70 let field_count = Self::count_fields_from_operations(&operations);
71 let depth = Self::calculate_depth_from_operations(&operations);
72 let has_arguments = Self::has_arguments_from_operations(&operations);
73 let has_fragments = !fragments.is_empty();
74 let complexity_score = Self::calculate_complexity_from_operations(&operations);
75
76 let timestamp = SystemTime::now()
77 .duration_since(UNIX_EPOCH)
78 .unwrap_or_default()
79 .as_secs();
80
81 Self {
82 query_type,
83 field_count,
84 depth,
85 has_arguments,
86 has_fragments,
87 complexity_score,
88 timestamp,
89 access_frequency: 1.0,
90 execution_time_ms: 0,
91 }
92 }
93
94 fn extract_operations(doc: &Document) -> Vec<&OperationDefinition> {
95 doc.definitions
96 .iter()
97 .filter_map(|def| {
98 if let Definition::Operation(op) = def {
99 Some(op)
100 } else {
101 None
102 }
103 })
104 .collect()
105 }
106
107 fn extract_fragments(doc: &Document) -> Vec<&FragmentDefinition> {
108 doc.definitions
109 .iter()
110 .filter_map(|def| {
111 if let Definition::Fragment(frag) = def {
112 Some(frag)
113 } else {
114 None
115 }
116 })
117 .collect()
118 }
119
120 fn count_fields_from_operations(operations: &[&OperationDefinition]) -> usize {
121 operations
123 .iter()
124 .map(|op| op.selection_set.selections.len())
125 .sum()
126 }
127
128 fn calculate_depth_from_operations(operations: &[&OperationDefinition]) -> usize {
129 operations
131 .iter()
132 .map(|op| Self::selection_depth(&op.selection_set.selections))
133 .max()
134 .unwrap_or(1)
135 }
136
137 fn selection_depth(selections: &[crate::ast::Selection]) -> usize {
138 selections
139 .iter()
140 .map(|sel| match sel {
141 crate::ast::Selection::Field(field) => {
142 if let Some(ref selection_set) = field.selection_set {
143 1 + Self::selection_depth(&selection_set.selections)
144 } else {
145 1
146 }
147 }
148 crate::ast::Selection::InlineFragment(frag) => {
149 1 + Self::selection_depth(&frag.selection_set.selections)
150 }
151 crate::ast::Selection::FragmentSpread(_) => 1,
152 })
153 .max()
154 .unwrap_or(1)
155 }
156
157 fn has_arguments_from_operations(operations: &[&OperationDefinition]) -> bool {
158 operations
159 .iter()
160 .any(|op| Self::selection_has_args(&op.selection_set.selections))
161 }
162
163 fn selection_has_args(selections: &[crate::ast::Selection]) -> bool {
164 selections.iter().any(|sel| match sel {
165 crate::ast::Selection::Field(field) => {
166 !field.arguments.is_empty()
167 || field
168 .selection_set
169 .as_ref()
170 .map(|ss| Self::selection_has_args(&ss.selections))
171 .unwrap_or(false)
172 }
173 crate::ast::Selection::InlineFragment(frag) => {
174 Self::selection_has_args(&frag.selection_set.selections)
175 }
176 crate::ast::Selection::FragmentSpread(_) => false,
177 })
178 }
179
180 fn calculate_complexity_from_operations(operations: &[&OperationDefinition]) -> f64 {
181 let field_weight = Self::count_fields_from_operations(operations) as f64 * 1.0;
182 let depth_weight = Self::calculate_depth_from_operations(operations) as f64 * 2.0;
183 let args_weight = if Self::has_arguments_from_operations(operations) {
184 3.0
185 } else {
186 0.0
187 };
188
189 field_weight + depth_weight + args_weight
190 }
191
192 pub fn similarity(&self, other: &QueryPattern) -> f64 {
194 let type_similarity = if self.query_type == other.query_type {
195 1.0
196 } else {
197 0.0
198 };
199 let field_similarity =
200 1.0 - ((self.field_count as f64 - other.field_count as f64).abs() / 10.0).min(1.0);
201 let depth_similarity =
202 1.0 - ((self.depth as f64 - other.depth as f64).abs() / 5.0).min(1.0);
203 let args_similarity = if self.has_arguments == other.has_arguments {
204 1.0
205 } else {
206 0.5
207 };
208 let fragments_similarity = if self.has_fragments == other.has_fragments {
209 1.0
210 } else {
211 0.5
212 };
213 let complexity_similarity =
214 1.0 - ((self.complexity_score - other.complexity_score).abs() / 20.0).min(1.0);
215
216 type_similarity * 0.3
217 + field_similarity * 0.2
218 + depth_similarity * 0.2
219 + args_similarity * 0.1
220 + fragments_similarity * 0.1
221 + complexity_similarity * 0.1
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct QueryUsageStats {
228 pub hit_count: u64,
229 pub last_access: u64,
230 pub average_execution_time_ms: f64,
231 pub cache_hits: u64,
232 pub cache_misses: u64,
233 pub pattern: QueryPattern,
234}
235
236impl QueryUsageStats {
237 pub fn new(pattern: QueryPattern) -> Self {
238 let timestamp = SystemTime::now()
239 .duration_since(UNIX_EPOCH)
240 .unwrap_or_default()
241 .as_secs();
242
243 Self {
244 hit_count: 1,
245 last_access: timestamp,
246 average_execution_time_ms: 0.0,
247 cache_hits: 0,
248 cache_misses: 0,
249 pattern,
250 }
251 }
252
253 pub fn update_access(&mut self, execution_time_ms: f64, was_cache_hit: bool) {
254 self.hit_count += 1;
255 self.last_access = SystemTime::now()
256 .duration_since(UNIX_EPOCH)
257 .unwrap_or_default()
258 .as_secs();
259
260 let alpha = 0.1; self.average_execution_time_ms =
263 alpha * execution_time_ms + (1.0 - alpha) * self.average_execution_time_ms;
264
265 if was_cache_hit {
266 self.cache_hits += 1;
267 } else {
268 self.cache_misses += 1;
269 }
270 }
271
272 pub fn cache_hit_ratio(&self) -> f64 {
273 if self.cache_hits + self.cache_misses == 0 {
274 0.0
275 } else {
276 self.cache_hits as f64 / (self.cache_hits + self.cache_misses) as f64
277 }
278 }
279
280 pub fn access_frequency(&self) -> f64 {
281 let now = SystemTime::now()
282 .duration_since(UNIX_EPOCH)
283 .unwrap_or_default()
284 .as_secs();
285
286 let age_hours = ((now - self.pattern.timestamp) as f64) / 3600.0;
287 if age_hours == 0.0 {
288 self.hit_count as f64
289 } else {
290 self.hit_count as f64 / age_hours
291 }
292 }
293}
294
295#[derive(Debug, Clone)]
297pub struct CacheEntry {
298 pub value: Value,
299 pub created_at: Instant,
300 pub last_accessed: Instant,
301 pub access_count: u64,
302 pub prediction_confidence: f64,
303}
304
305impl CacheEntry {
306 pub fn new(value: Value, prediction_confidence: f64) -> Self {
307 let now = Instant::now();
308 Self {
309 value,
310 created_at: now,
311 last_accessed: now,
312 access_count: 0,
313 prediction_confidence,
314 }
315 }
316
317 pub fn access(&mut self) -> &Value {
318 self.last_accessed = Instant::now();
319 self.access_count += 1;
320 &self.value
321 }
322
323 pub fn is_expired(&self, ttl: Duration) -> bool {
324 self.created_at.elapsed() > ttl
325 }
326
327 pub fn access_score(&self) -> f64 {
328 let recency_score = 1.0 / (1.0 + self.last_accessed.elapsed().as_secs() as f64 / 3600.0);
329 let frequency_score = (self.access_count as f64).ln().max(1.0);
330 let confidence_score = self.prediction_confidence;
331
332 recency_score * 0.4 + frequency_score * 0.3 + confidence_score * 0.3
333 }
334}
335
336pub struct IntelligentQueryCache {
338 config: IntelligentCacheConfig,
339 cache_entries: Arc<RwLock<HashMap<String, CacheEntry>>>,
340 usage_stats: Arc<RwLock<HashMap<String, QueryUsageStats>>>,
341 pattern_history: Arc<RwLock<VecDeque<QueryPattern>>>,
342 distributed_cache: Option<Arc<GraphQLQueryCache>>,
343}
344
345impl IntelligentQueryCache {
346 pub fn new(config: IntelligentCacheConfig) -> Self {
347 Self {
348 config,
349 cache_entries: Arc::new(RwLock::new(HashMap::new())),
350 usage_stats: Arc::new(RwLock::new(HashMap::new())),
351 pattern_history: Arc::new(RwLock::new(VecDeque::new())),
352 distributed_cache: None,
353 }
354 }
355
356 pub async fn with_distributed_cache(
357 mut self,
358 cache_config: CacheConfig,
359 ) -> anyhow::Result<Self> {
360 let distributed_cache = Arc::new(GraphQLQueryCache::new(cache_config).await?);
361 self.distributed_cache = Some(distributed_cache);
362 Ok(self)
363 }
364
365 pub fn generate_cache_key(&self, query: &str, variables: &HashMap<String, Value>) -> String {
367 use std::collections::hash_map::DefaultHasher;
368
369 let mut hasher = DefaultHasher::new();
370 query.hash(&mut hasher);
371
372 let mut var_pairs: Vec<_> = variables.iter().collect();
374 var_pairs.sort_by_key(|(k, _)| *k);
375
376 for (key, value) in var_pairs {
377 key.hash(&mut hasher);
378 format!("{value:?}").hash(&mut hasher);
379 }
380
381 format!("iqc:{:x}", hasher.finish())
382 }
383
384 pub async fn get(&self, query: &str, variables: &HashMap<String, Value>) -> Option<Value> {
386 let cache_key = self.generate_cache_key(query, variables);
387 let start_time = Instant::now();
388
389 {
391 let mut cache = self.cache_entries.write().await;
392 if let Some(entry) = cache.get_mut(&cache_key) {
393 if !entry.is_expired(Duration::from_secs(self.config.cache_ttl_seconds)) {
394 let result = entry.access().clone();
395
396 self.update_usage_stats(
398 &cache_key,
399 start_time.elapsed().as_millis() as f64,
400 true,
401 )
402 .await;
403
404 debug!("Cache hit for query key: {}", cache_key);
405 return Some(result);
406 }
407 }
408 }
409
410 if let Some(ref distributed_cache) = self.distributed_cache {
412 if let Ok(Some(data)) = distributed_cache.raw_get(&cache_key).await {
413 if let Ok(value) = serde_json::from_slice::<Value>(&data) {
415 self.store_local(&cache_key, value.clone(), 0.8).await;
417
418 self.update_usage_stats(
420 &cache_key,
421 start_time.elapsed().as_millis() as f64,
422 true,
423 )
424 .await;
425
426 debug!("Distributed cache hit for query key: {}", cache_key);
427 return Some(value);
428 }
429 }
430 }
431
432 self.update_usage_stats(&cache_key, start_time.elapsed().as_millis() as f64, false)
434 .await;
435 None
436 }
437
438 pub async fn set(
440 &self,
441 query: &str,
442 variables: &HashMap<String, Value>,
443 result: Value,
444 doc: &Document,
445 ) -> anyhow::Result<()> {
446 let cache_key = self.generate_cache_key(query, variables);
447 let pattern = QueryPattern::from_document(doc);
448
449 let confidence = self.calculate_prediction_confidence(&pattern).await;
451
452 self.store_local(&cache_key, result.clone(), confidence)
454 .await;
455
456 if let Some(ref distributed_cache) = self.distributed_cache {
458 if let Ok(data) = serde_json::to_vec(&result) {
459 if let Err(e) = distributed_cache
460 .raw_set(
461 &cache_key,
462 data,
463 Some(Duration::from_secs(self.config.cache_ttl_seconds)),
464 )
465 .await
466 {
467 warn!("Failed to store in distributed cache: {}", e);
468 }
469 }
470 }
471
472 if self.config.enable_pattern_learning {
474 self.record_pattern(pattern).await;
475 }
476
477 {
479 let mut stats = self.usage_stats.write().await;
480 stats
481 .entry(cache_key)
482 .or_insert_with(|| QueryUsageStats::new(QueryPattern::from_document(doc)));
483 }
484
485 Ok(())
486 }
487
488 async fn store_local(&self, cache_key: &str, value: Value, confidence: f64) {
489 let mut cache = self.cache_entries.write().await;
490
491 let ttl = Duration::from_secs(self.config.cache_ttl_seconds);
493 cache.retain(|_, entry| !entry.is_expired(ttl));
494
495 while cache.len() >= self.config.max_cache_entries {
497 if let Some(evict_key) = cache
498 .iter()
499 .min_by(|(_, a), (_, b)| {
500 a.access_score()
501 .partial_cmp(&b.access_score())
502 .unwrap_or(std::cmp::Ordering::Equal)
503 })
504 .map(|(k, _)| k.clone())
505 {
506 cache.remove(&evict_key);
507 } else {
508 break;
509 }
510 }
511
512 cache.insert(cache_key.to_string(), CacheEntry::new(value, confidence));
513 }
514
515 async fn calculate_prediction_confidence(&self, pattern: &QueryPattern) -> f64 {
516 if !self.config.enable_predictive_caching {
517 return 0.5; }
519
520 let pattern_history = self.pattern_history.read().await;
521 let similar_patterns: Vec<_> = pattern_history
522 .iter()
523 .map(|p| (p.similarity(pattern), p))
524 .filter(|(sim, _)| *sim > 0.5)
525 .collect();
526
527 if similar_patterns.is_empty() {
528 return 0.3; }
530
531 let total_similarity: f64 = similar_patterns.iter().map(|(sim, _)| sim).sum();
533 let avg_similarity = total_similarity / similar_patterns.len() as f64;
534
535 let frequency_boost = (similar_patterns.len() as f64 / 10.0).min(0.3);
537
538 (avg_similarity + frequency_boost).min(1.0)
539 }
540
541 async fn record_pattern(&self, pattern: QueryPattern) {
542 let mut history = self.pattern_history.write().await;
543
544 history.push_back(pattern);
545
546 while history.len() > self.config.max_pattern_history {
548 history.pop_front();
549 }
550 }
551
552 async fn update_usage_stats(
553 &self,
554 cache_key: &str,
555 execution_time_ms: f64,
556 was_cache_hit: bool,
557 ) {
558 if !self.config.enable_usage_analytics {
559 return;
560 }
561
562 let mut stats = self.usage_stats.write().await;
563 if let Some(stat) = stats.get_mut(cache_key) {
564 stat.update_access(execution_time_ms, was_cache_hit);
565 }
566 }
567
568 pub async fn get_statistics(&self) -> anyhow::Result<HashMap<String, serde_json::Value>> {
570 let mut stats = HashMap::new();
571
572 let cache_entries = self.cache_entries.read().await;
573 let usage_stats = self.usage_stats.read().await;
574 let pattern_history = self.pattern_history.read().await;
575
576 stats.insert(
577 "cache_size".to_string(),
578 serde_json::Value::Number(cache_entries.len().into()),
579 );
580
581 stats.insert(
582 "pattern_history_size".to_string(),
583 serde_json::Value::Number(pattern_history.len().into()),
584 );
585
586 stats.insert(
587 "usage_stats_size".to_string(),
588 serde_json::Value::Number(usage_stats.len().into()),
589 );
590
591 let total_hits: u64 = usage_stats.values().map(|s| s.cache_hits).sum();
593 let total_misses: u64 = usage_stats.values().map(|s| s.cache_misses).sum();
594 let hit_ratio = if total_hits + total_misses > 0 {
595 total_hits as f64 / (total_hits + total_misses) as f64
596 } else {
597 0.0
598 };
599
600 stats.insert(
601 "overall_hit_ratio".to_string(),
602 serde_json::Value::Number(
603 serde_json::Number::from_f64(hit_ratio)
604 .expect("hit_ratio should be a valid f64 for JSON"),
605 ),
606 );
607
608 let avg_exec_time: f64 = usage_stats
610 .values()
611 .map(|s| s.average_execution_time_ms)
612 .sum::<f64>()
613 / usage_stats.len().max(1) as f64;
614
615 stats.insert(
616 "average_execution_time_ms".to_string(),
617 serde_json::Value::Number(
618 serde_json::Number::from_f64(avg_exec_time)
619 .expect("avg_exec_time should be a valid f64 for JSON"),
620 ),
621 );
622
623 Ok(stats)
624 }
625
626 pub async fn predict_queries(&self) -> Vec<(String, f64)> {
628 if !self.config.enable_predictive_caching {
629 return Vec::new();
630 }
631
632 let _pattern_history = self.pattern_history.read().await;
633 let usage_stats = self.usage_stats.read().await;
634
635 let mut predictions = Vec::new();
637
638 for (cache_key, stats) in usage_stats.iter() {
639 let frequency = stats.access_frequency();
640 let recency_factor = {
641 let hours_since_access = (SystemTime::now()
642 .duration_since(UNIX_EPOCH)
643 .unwrap_or_default()
644 .as_secs()
645 - stats.last_access) as f64
646 / 3600.0;
647 1.0 / (1.0 + hours_since_access / 24.0) };
649
650 let prediction_score = frequency * recency_factor * stats.cache_hit_ratio();
651
652 if prediction_score > self.config.prediction_confidence_threshold {
653 predictions.push((cache_key.clone(), prediction_score));
654 }
655 }
656
657 predictions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
659
660 info!("Generated {} query predictions", predictions.len());
661 predictions
662 }
663
664 pub async fn cleanup_expired(&self) -> usize {
666 let mut cache = self.cache_entries.write().await;
667 let initial_size = cache.len();
668
669 let ttl = Duration::from_secs(self.config.cache_ttl_seconds);
670 cache.retain(|_, entry| !entry.is_expired(ttl));
671
672 let removed = initial_size - cache.len();
673 if removed > 0 {
674 info!("Cleaned up {} expired cache entries", removed);
675 }
676
677 removed
678 }
679
680 pub async fn get_advanced_analytics(&self) -> AdvancedCacheAnalytics {
682 let cache = self.cache_entries.read().await;
683 let stats = self.usage_stats.read().await;
684 let patterns = self.pattern_history.read().await;
685
686 let total_entries = cache.len();
687 let total_queries = stats.values().map(|s| s.hit_count as usize).sum::<usize>();
688 let total_hit_time: Duration = stats
689 .values()
690 .map(|s| Duration::from_millis(s.average_execution_time_ms as u64))
691 .sum();
692 let cache_efficiency = if total_queries > 0 {
693 stats.values().filter(|s| s.cache_hit_ratio() > 0.0).count() as f64
694 / total_queries as f64
695 } else {
696 0.0
697 };
698
699 let unique_patterns = patterns.len();
701 let pattern_diversity = if unique_patterns > 0 {
702 1.0 - (patterns.iter().map(|p| p.complexity_score).sum::<f64>()
703 / (unique_patterns as f64).powi(2))
704 } else {
705 0.0
706 };
707
708 let estimated_memory_bytes = total_entries * 1024; let predicted_hit_ratio = self.predict_future_hit_ratio(&stats).await;
713 let optimization_recommendations = self
714 .generate_optimization_recommendations(&cache, &stats)
715 .await;
716
717 AdvancedCacheAnalytics {
718 total_entries,
719 total_queries,
720 cache_efficiency,
721 pattern_diversity,
722 estimated_memory_bytes,
723 average_response_time: if total_queries > 0 {
724 total_hit_time / total_queries as u32
725 } else {
726 Duration::from_millis(0)
727 },
728 predicted_hit_ratio,
729 optimization_recommendations,
730 cache_heat_map: self.generate_cache_heat_map(&stats).await,
731 performance_trends: self.calculate_performance_trends(&patterns).await,
732 }
733 }
734
735 async fn predict_future_hit_ratio(&self, stats: &HashMap<String, QueryUsageStats>) -> f64 {
737 if stats.is_empty() {
738 return 0.0;
739 }
740
741 let recent_hits: Vec<f64> = stats.values().map(|s| s.cache_hit_ratio()).collect();
742
743 let mut ema = recent_hits[0];
745 let alpha = 0.3; for &hit_ratio in recent_hits.iter().skip(1) {
748 ema = alpha * hit_ratio + (1.0 - alpha) * ema;
749 }
750
751 ema.clamp(0.0, 1.0)
752 }
753
754 async fn generate_optimization_recommendations(
756 &self,
757 cache: &HashMap<String, CacheEntry>,
758 stats: &HashMap<String, QueryUsageStats>,
759 ) -> Vec<OptimizationRecommendation> {
760 let mut recommendations = Vec::new();
761
762 for (key, stat) in stats {
764 if stat.cache_hit_ratio() < 0.3 && stat.hit_count > 10 {
765 recommendations.push(OptimizationRecommendation {
766 recommendation_type: OptimizationType::IncreaseQueryTTL,
767 query_pattern: key.clone(),
768 impact_score: 0.8,
769 description: format!("Query '{}' has low hit ratio ({:.2}%) but high access count ({}). Consider increasing TTL.",
770 key, stat.cache_hit_ratio() * 100.0, stat.hit_count),
771 });
772 }
773 }
774
775 if cache.len() > self.config.max_cache_entries * 80 / 100 {
777 recommendations.push(OptimizationRecommendation {
778 recommendation_type: OptimizationType::MemoryOptimization,
779 query_pattern: "global".to_string(),
780 impact_score: 0.9,
781 description: "Cache is approaching capacity limit. Consider implementing more aggressive eviction policies.".to_string(),
782 });
783 }
784
785 if self.pattern_history.read().await.len() > 100 {
787 recommendations.push(OptimizationRecommendation {
788 recommendation_type: OptimizationType::PatternOptimization,
789 query_pattern: "patterns".to_string(),
790 impact_score: 0.7,
791 description: "Rich pattern history detected. Consider implementing predictive pre-caching for similar queries.".to_string(),
792 });
793 }
794
795 recommendations
796 }
797
798 async fn generate_cache_heat_map(
800 &self,
801 stats: &HashMap<String, QueryUsageStats>,
802 ) -> CacheHeatMap {
803 let mut hot_queries = Vec::new();
804 let mut warm_queries = Vec::new();
805 let mut cold_queries = Vec::new();
806
807 for (key, stat) in stats {
808 let heat_score = stat.hit_count as f64 * stat.cache_hit_ratio();
809
810 if heat_score > 50.0 {
811 hot_queries.push((key.clone(), heat_score));
812 } else if heat_score > 10.0 {
813 warm_queries.push((key.clone(), heat_score));
814 } else {
815 cold_queries.push((key.clone(), heat_score));
816 }
817 }
818
819 hot_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
821 warm_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
822 cold_queries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
823
824 CacheHeatMap {
825 hot_queries: hot_queries.into_iter().take(10).collect(),
826 warm_queries: warm_queries.into_iter().take(20).collect(),
827 cold_queries: cold_queries.into_iter().take(10).collect(),
828 }
829 }
830
831 async fn calculate_performance_trends(
833 &self,
834 patterns: &VecDeque<QueryPattern>,
835 ) -> PerformanceTrends {
836 if patterns.is_empty() {
837 return PerformanceTrends::default();
838 }
839
840 let recent_patterns: Vec<&QueryPattern> = patterns.iter().rev().take(50).collect();
841
842 let complexity_trend = self.calculate_complexity_trend(&recent_patterns);
843 let frequency_trend = self.calculate_frequency_trend(&recent_patterns);
844 let efficiency_trend = self.calculate_efficiency_trend(&recent_patterns);
845
846 PerformanceTrends {
847 complexity_trend,
848 frequency_trend,
849 efficiency_trend,
850 prediction_accuracy: self.calculate_prediction_accuracy(&recent_patterns),
851 }
852 }
853
854 fn calculate_complexity_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
855 if patterns.len() < 10 {
856 return TrendDirection::Stable;
857 }
858
859 let recent_avg = patterns
860 .iter()
861 .rev()
862 .take(10)
863 .map(|p| p.complexity_score)
864 .sum::<f64>()
865 / 10.0;
866 let older_avg = patterns
867 .iter()
868 .take(10)
869 .map(|p| p.complexity_score)
870 .sum::<f64>()
871 / 10.0;
872
873 if recent_avg > older_avg * 1.1 {
874 TrendDirection::Increasing
875 } else if recent_avg < older_avg * 0.9 {
876 TrendDirection::Decreasing
877 } else {
878 TrendDirection::Stable
879 }
880 }
881
882 fn calculate_frequency_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
883 if patterns.len() < 10 {
884 return TrendDirection::Stable;
885 }
886
887 let recent_avg = patterns
888 .iter()
889 .rev()
890 .take(10)
891 .map(|p| p.complexity_score)
892 .sum::<f64>()
893 / 10.0;
894 let older_avg = patterns
895 .iter()
896 .take(10)
897 .map(|p| p.complexity_score)
898 .sum::<f64>()
899 / 10.0;
900
901 if recent_avg > older_avg * 1.1 {
902 TrendDirection::Increasing
903 } else if recent_avg < older_avg * 0.9 {
904 TrendDirection::Decreasing
905 } else {
906 TrendDirection::Stable
907 }
908 }
909
910 fn calculate_efficiency_trend(&self, patterns: &[&QueryPattern]) -> TrendDirection {
911 if patterns.len() < 10 {
913 return TrendDirection::Stable;
914 }
915
916 let recent_efficiency: f64 = patterns
917 .iter()
918 .rev()
919 .take(10)
920 .map(|p| p.complexity_score / (p.field_count as f64 + 1.0))
921 .sum::<f64>()
922 / 10.0;
923
924 let older_efficiency: f64 = patterns
925 .iter()
926 .take(10)
927 .map(|p| p.complexity_score / (p.field_count as f64 + 1.0))
928 .sum::<f64>()
929 / 10.0;
930
931 if recent_efficiency > older_efficiency * 1.1 {
932 TrendDirection::Increasing
933 } else if recent_efficiency < older_efficiency * 0.9 {
934 TrendDirection::Decreasing
935 } else {
936 TrendDirection::Stable
937 }
938 }
939
940 fn calculate_prediction_accuracy(&self, patterns: &[&QueryPattern]) -> f64 {
941 if patterns.is_empty() {
944 return 0.0;
945 }
946
947 let consistency_score = patterns
948 .iter()
949 .map(|p| p.complexity_score)
950 .collect::<Vec<_>>()
951 .windows(2)
952 .map(|w| (w[0] - w[1]).abs())
953 .sum::<f64>()
954 / patterns.len() as f64;
955
956 (1.0f64 - consistency_score.min(1.0f64)).max(0.0f64)
957 }
958}
959
960#[derive(Debug, Clone, Serialize, Deserialize)]
962pub struct AdvancedCacheAnalytics {
963 pub total_entries: usize,
964 pub total_queries: usize,
965 pub cache_efficiency: f64,
966 pub pattern_diversity: f64,
967 pub estimated_memory_bytes: usize,
968 pub average_response_time: Duration,
969 pub predicted_hit_ratio: f64,
970 pub optimization_recommendations: Vec<OptimizationRecommendation>,
971 pub cache_heat_map: CacheHeatMap,
972 pub performance_trends: PerformanceTrends,
973}
974
975#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct OptimizationRecommendation {
978 pub recommendation_type: OptimizationType,
979 pub query_pattern: String,
980 pub impact_score: f64,
981 pub description: String,
982}
983
984#[derive(Debug, Clone, Serialize, Deserialize)]
986pub enum OptimizationType {
987 IncreaseQueryTTL,
988 DecreaseQueryTTL,
989 MemoryOptimization,
990 PatternOptimization,
991 PredictiveCaching,
992 EvictionPolicyAdjustment,
993}
994
995#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct CacheHeatMap {
998 pub hot_queries: Vec<(String, f64)>,
999 pub warm_queries: Vec<(String, f64)>,
1000 pub cold_queries: Vec<(String, f64)>,
1001}
1002
1003#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct PerformanceTrends {
1006 pub complexity_trend: TrendDirection,
1007 pub frequency_trend: TrendDirection,
1008 pub efficiency_trend: TrendDirection,
1009 pub prediction_accuracy: f64,
1010}
1011
1012impl Default for PerformanceTrends {
1013 fn default() -> Self {
1014 Self {
1015 complexity_trend: TrendDirection::Stable,
1016 frequency_trend: TrendDirection::Stable,
1017 efficiency_trend: TrendDirection::Stable,
1018 prediction_accuracy: 0.0,
1019 }
1020 }
1021}
1022
1023#[derive(Debug, Clone, Serialize, Deserialize)]
1025pub enum TrendDirection {
1026 Increasing,
1027 Decreasing,
1028 Stable,
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033 use super::*;
1034 use crate::ast::{Definition, Document, OperationDefinition, OperationType, SelectionSet};
1035
1036 #[tokio::test]
1037 async fn test_intelligent_cache_creation() {
1038 let config = IntelligentCacheConfig::default();
1039 let cache = IntelligentQueryCache::new(config);
1040
1041 assert!(cache.cache_entries.read().await.is_empty());
1042 assert!(cache.usage_stats.read().await.is_empty());
1043 assert!(cache.pattern_history.read().await.is_empty());
1044 }
1045
1046 #[tokio::test]
1047 async fn test_cache_key_generation() {
1048 let cache = IntelligentQueryCache::new(IntelligentCacheConfig::default());
1049
1050 let query = "query { user { name } }";
1051 let variables = HashMap::new();
1052
1053 let key1 = cache.generate_cache_key(query, &variables);
1054 let key2 = cache.generate_cache_key(query, &variables);
1055
1056 assert_eq!(key1, key2);
1057 assert!(key1.starts_with("iqc:"));
1058 }
1059
1060 #[tokio::test]
1061 async fn test_pattern_similarity() {
1062 let doc1 = Document {
1063 definitions: vec![Definition::Operation(OperationDefinition {
1064 operation_type: OperationType::Query,
1065 name: None,
1066 variable_definitions: Vec::new(),
1067 directives: Vec::new(),
1068 selection_set: SelectionSet {
1069 selections: Vec::new(),
1070 },
1071 })],
1072 };
1073
1074 let pattern1 = QueryPattern::from_document(&doc1);
1075 let pattern2 = QueryPattern::from_document(&doc1);
1076
1077 let similarity = pattern1.similarity(&pattern2);
1078 assert!((similarity - 1.0).abs() < 0.001); }
1080
1081 #[tokio::test]
1082 async fn test_cache_statistics() {
1083 let cache = IntelligentQueryCache::new(IntelligentCacheConfig::default());
1084 let stats = cache.get_statistics().await.expect("should succeed");
1085
1086 assert!(stats.contains_key("cache_size"));
1087 assert!(stats.contains_key("pattern_history_size"));
1088 assert!(stats.contains_key("overall_hit_ratio"));
1089 }
1090}