Skip to main content

rez_next_cache/
adaptive_tuner.rs

1//! Adaptive Cache Tuner
2//!
3//! This module provides real-time cache parameter optimization based on
4//! performance metrics and workload characteristics.
5
6use crate::{TuningConfig, UnifiedCacheStats};
7use serde::{Deserialize, Serialize};
8use std::{
9    collections::VecDeque,
10    sync::{Arc, RwLock},
11    time::SystemTime,
12};
13use tokio::time::Interval;
14
15/// Cache performance metrics for tuning decisions
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct PerformanceSnapshot {
18    /// Timestamp of the snapshot
19    pub timestamp: SystemTime,
20    /// Overall hit rate
21    pub hit_rate: f64,
22    /// L1 hit rate
23    pub l1_hit_rate: f64,
24    /// L2 hit rate
25    pub l2_hit_rate: f64,
26    /// Memory usage percentage
27    pub memory_usage_percent: f64,
28    /// Disk usage percentage
29    pub disk_usage_percent: f64,
30    /// Average get latency (microseconds)
31    pub avg_get_latency_us: f64,
32    /// Average put latency (microseconds)
33    pub avg_put_latency_us: f64,
34    /// Operations per second
35    pub ops_per_second: f64,
36    /// Eviction rate (evictions per minute)
37    pub eviction_rate: f64,
38    /// Promotion rate (L2 to L1 promotions per minute)
39    pub promotion_rate: f64,
40}
41
42impl Default for PerformanceSnapshot {
43    fn default() -> Self {
44        Self {
45            timestamp: SystemTime::now(),
46            hit_rate: 0.0,
47            l1_hit_rate: 0.0,
48            l2_hit_rate: 0.0,
49            memory_usage_percent: 0.0,
50            disk_usage_percent: 0.0,
51            avg_get_latency_us: 0.0,
52            avg_put_latency_us: 0.0,
53            ops_per_second: 0.0,
54            eviction_rate: 0.0,
55            promotion_rate: 0.0,
56        }
57    }
58}
59
60/// Tuning recommendation
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct TuningRecommendation {
63    /// Parameter to tune
64    pub parameter: String,
65    /// Current value
66    pub current_value: f64,
67    /// Recommended value
68    pub recommended_value: f64,
69    /// Confidence in recommendation (0.0 to 1.0)
70    pub confidence: f64,
71    /// Expected improvement
72    pub expected_improvement: f64,
73    /// Reason for recommendation
74    pub reason: String,
75}
76
77/// Adaptive tuning statistics
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AdaptiveTuningStats {
80    /// Total tuning operations performed
81    pub tuning_operations: u64,
82    /// Successful tuning operations (improved performance)
83    pub successful_tunings: u64,
84    /// Failed tuning operations (degraded performance)
85    pub failed_tunings: u64,
86    /// Success rate
87    pub success_rate: f64,
88    /// Average performance improvement
89    pub avg_improvement: f64,
90    /// Current tuning confidence
91    pub current_confidence: f64,
92    /// Last tuning timestamp
93    pub last_tuning: SystemTime,
94}
95
96impl Default for AdaptiveTuningStats {
97    fn default() -> Self {
98        Self {
99            tuning_operations: 0,
100            successful_tunings: 0,
101            failed_tunings: 0,
102            success_rate: 0.0,
103            avg_improvement: 0.0,
104            current_confidence: 0.5,
105            last_tuning: SystemTime::UNIX_EPOCH,
106        }
107    }
108}
109
110/// Adaptive Cache Tuner
111///
112/// Continuously monitors cache performance and automatically adjusts parameters
113/// to optimize hit rates, latency, and resource utilization.
114#[derive(Debug)]
115pub struct AdaptiveTuner {
116    /// Configuration
117    config: TuningConfig,
118    /// Performance history
119    performance_history: Arc<RwLock<VecDeque<PerformanceSnapshot>>>,
120    /// Current tuning parameters
121    current_params: Arc<RwLock<TuningParameters>>,
122    /// Tuning statistics
123    stats: Arc<RwLock<AdaptiveTuningStats>>,
124    /// Background tuning interval
125    _tuning_interval: Option<Interval>,
126}
127
128/// Current tuning parameters
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct TuningParameters {
131    /// L1 cache size multiplier
132    pub l1_size_multiplier: f64,
133    /// L2 cache size multiplier
134    pub l2_size_multiplier: f64,
135    /// Promotion threshold
136    pub promotion_threshold: u64,
137    /// Eviction aggressiveness (0.0 to 1.0)
138    pub eviction_aggressiveness: f64,
139    /// TTL multiplier
140    pub ttl_multiplier: f64,
141    /// Preheating aggressiveness (0.0 to 1.0)
142    pub preheating_aggressiveness: f64,
143}
144
145impl Default for TuningParameters {
146    fn default() -> Self {
147        Self {
148            l1_size_multiplier: 1.0,
149            l2_size_multiplier: 1.0,
150            promotion_threshold: 3,
151            eviction_aggressiveness: 0.5,
152            ttl_multiplier: 1.0,
153            preheating_aggressiveness: 0.3,
154        }
155    }
156}
157
158impl AdaptiveTuner {
159    /// Create a new adaptive tuner
160    pub fn new(config: TuningConfig) -> Self {
161        Self {
162            config,
163            performance_history: Arc::new(RwLock::new(VecDeque::new())),
164            current_params: Arc::new(RwLock::new(TuningParameters::default())),
165            stats: Arc::new(RwLock::new(AdaptiveTuningStats::default())),
166            _tuning_interval: None,
167        }
168    }
169
170    /// Record performance snapshot
171    pub async fn record_performance(&self, stats: &UnifiedCacheStats) {
172        let snapshot = PerformanceSnapshot {
173            timestamp: SystemTime::now(),
174            hit_rate: stats.overall_stats.overall_hit_rate,
175            l1_hit_rate: if stats.l1_stats.hits + stats.l1_stats.misses > 0 {
176                stats.l1_stats.hits as f64 / (stats.l1_stats.hits + stats.l1_stats.misses) as f64
177            } else {
178                0.0
179            },
180            l2_hit_rate: if stats.l2_stats.hits + stats.l2_stats.misses > 0 {
181                stats.l2_stats.hits as f64 / (stats.l2_stats.hits + stats.l2_stats.misses) as f64
182            } else {
183                0.0
184            },
185            memory_usage_percent: if stats.l1_stats.max_usage_bytes > 0 {
186                stats.l1_stats.usage_bytes as f64 / stats.l1_stats.max_usage_bytes as f64 * 100.0
187            } else {
188                0.0
189            },
190            disk_usage_percent: if stats.l2_stats.max_usage_bytes > 0 {
191                stats.l2_stats.usage_bytes as f64 / stats.l2_stats.max_usage_bytes as f64 * 100.0
192            } else {
193                0.0
194            },
195            avg_get_latency_us: stats.performance_metrics.avg_get_latency_us,
196            avg_put_latency_us: stats.performance_metrics.avg_put_latency_us,
197            ops_per_second: stats.performance_metrics.ops_per_second,
198            eviction_rate: stats.l1_stats.evictions as f64 + stats.l2_stats.evictions as f64,
199            promotion_rate: stats.overall_stats.promotions as f64,
200        };
201
202        let mut history = self.performance_history.write().unwrap();
203        history.push_back(snapshot);
204
205        // Keep only recent history
206        let max_history = self.config.performance_window_size;
207        while history.len() > max_history {
208            history.pop_front();
209        }
210    }
211
212    /// Analyze performance and generate tuning recommendations
213    pub async fn analyze_and_tune(&self) -> Vec<TuningRecommendation> {
214        if !self.config.enable_adaptive_tuning {
215            return Vec::new();
216        }
217
218        // Clone the history to avoid holding the lock across await
219        let history = {
220            let history_guard = self.performance_history.read().unwrap();
221            if history_guard.len() < self.config.min_samples_for_tuning {
222                return Vec::new();
223            }
224            history_guard.clone()
225        };
226
227        let mut recommendations = Vec::new();
228
229        // Analyze hit rate trends
230        if let Some(hit_rate_rec) = self.analyze_hit_rate(&history) {
231            recommendations.push(hit_rate_rec);
232        }
233
234        // Analyze memory usage
235        if let Some(memory_rec) = self.analyze_memory_usage(&history) {
236            recommendations.push(memory_rec);
237        }
238
239        // Analyze latency trends
240        if let Some(latency_rec) = self.analyze_latency(&history) {
241            recommendations.push(latency_rec);
242        }
243
244        // Analyze eviction patterns
245        if let Some(eviction_rec) = self.analyze_eviction_patterns(&history) {
246            recommendations.push(eviction_rec);
247        }
248
249        // Apply high-confidence recommendations
250        self.apply_recommendations(&recommendations).await;
251
252        recommendations
253    }
254
255    /// Analyze hit rate trends
256    fn analyze_hit_rate(
257        &self,
258        history: &VecDeque<PerformanceSnapshot>,
259    ) -> Option<TuningRecommendation> {
260        if history.len() < 3 {
261            return None;
262        }
263
264        let recent_hit_rate = history
265            .iter()
266            .rev()
267            .take(3)
268            .map(|s| s.hit_rate)
269            .sum::<f64>()
270            / 3.0;
271        let older_hit_rate = history.iter().take(3).map(|s| s.hit_rate).sum::<f64>() / 3.0;
272
273        // If hit rate is declining, recommend increasing cache size
274        if recent_hit_rate < older_hit_rate - 0.05 && recent_hit_rate < self.config.target_hit_rate
275        {
276            let current_params = self.current_params.read().unwrap();
277            let new_multiplier = (current_params.l1_size_multiplier * 1.2).min(2.0);
278
279            return Some(TuningRecommendation {
280                parameter: "l1_size_multiplier".to_string(),
281                current_value: current_params.l1_size_multiplier,
282                recommended_value: new_multiplier,
283                confidence: 0.8,
284                expected_improvement: 0.1,
285                reason: "Hit rate declining, increasing L1 cache size".to_string(),
286            });
287        }
288
289        None
290    }
291
292    /// Analyze memory usage patterns
293    fn analyze_memory_usage(
294        &self,
295        history: &VecDeque<PerformanceSnapshot>,
296    ) -> Option<TuningRecommendation> {
297        let avg_memory_usage =
298            history.iter().map(|s| s.memory_usage_percent).sum::<f64>() / history.len() as f64;
299
300        // If memory usage is consistently high, recommend more aggressive eviction
301        if avg_memory_usage > 90.0 {
302            let current_params = self.current_params.read().unwrap();
303            let new_aggressiveness = (current_params.eviction_aggressiveness + 0.1).min(1.0);
304
305            return Some(TuningRecommendation {
306                parameter: "eviction_aggressiveness".to_string(),
307                current_value: current_params.eviction_aggressiveness,
308                recommended_value: new_aggressiveness,
309                confidence: 0.9,
310                expected_improvement: 0.05,
311                reason: "High memory usage, increasing eviction aggressiveness".to_string(),
312            });
313        }
314
315        // If memory usage is low, we can be less aggressive
316        if avg_memory_usage < 50.0 {
317            let current_params = self.current_params.read().unwrap();
318            let new_aggressiveness = (current_params.eviction_aggressiveness - 0.1).max(0.1);
319
320            return Some(TuningRecommendation {
321                parameter: "eviction_aggressiveness".to_string(),
322                current_value: current_params.eviction_aggressiveness,
323                recommended_value: new_aggressiveness,
324                confidence: 0.7,
325                expected_improvement: 0.03,
326                reason: "Low memory usage, reducing eviction aggressiveness".to_string(),
327            });
328        }
329
330        None
331    }
332
333    /// Analyze latency trends
334    fn analyze_latency(
335        &self,
336        history: &VecDeque<PerformanceSnapshot>,
337    ) -> Option<TuningRecommendation> {
338        if history.len() < 5 {
339            return None;
340        }
341
342        let recent_latency = history
343            .iter()
344            .rev()
345            .take(3)
346            .map(|s| s.avg_get_latency_us)
347            .sum::<f64>()
348            / 3.0;
349        let baseline_latency = history
350            .iter()
351            .take(3)
352            .map(|s| s.avg_get_latency_us)
353            .sum::<f64>()
354            / 3.0;
355
356        // If latency is increasing significantly, recommend reducing cache size or TTL
357        if recent_latency > baseline_latency * 1.5 && recent_latency > 1000.0 {
358            let current_params = self.current_params.read().unwrap();
359            let new_ttl_multiplier = (current_params.ttl_multiplier * 0.8).max(0.5);
360
361            return Some(TuningRecommendation {
362                parameter: "ttl_multiplier".to_string(),
363                current_value: current_params.ttl_multiplier,
364                recommended_value: new_ttl_multiplier,
365                confidence: 0.7,
366                expected_improvement: 0.2,
367                reason: "High latency detected, reducing TTL to improve cache freshness"
368                    .to_string(),
369            });
370        }
371
372        None
373    }
374
375    /// Analyze eviction patterns
376    fn analyze_eviction_patterns(
377        &self,
378        history: &VecDeque<PerformanceSnapshot>,
379    ) -> Option<TuningRecommendation> {
380        let avg_eviction_rate =
381            history.iter().map(|s| s.eviction_rate).sum::<f64>() / history.len() as f64;
382
383        // If eviction rate is very high, recommend increasing cache size
384        if avg_eviction_rate > 100.0 {
385            let current_params = self.current_params.read().unwrap();
386            let new_multiplier = (current_params.l2_size_multiplier * 1.3).min(3.0);
387
388            return Some(TuningRecommendation {
389                parameter: "l2_size_multiplier".to_string(),
390                current_value: current_params.l2_size_multiplier,
391                recommended_value: new_multiplier,
392                confidence: 0.8,
393                expected_improvement: 0.15,
394                reason: "High eviction rate, increasing L2 cache size".to_string(),
395            });
396        }
397
398        None
399    }
400
401    /// Apply high-confidence recommendations
402    async fn apply_recommendations(&self, recommendations: &[TuningRecommendation]) {
403        let mut params = self.current_params.write().unwrap();
404        let mut stats = self.stats.write().unwrap();
405
406        for rec in recommendations {
407            if rec.confidence >= self.config.min_confidence_for_auto_tuning {
408                match rec.parameter.as_str() {
409                    "l1_size_multiplier" => params.l1_size_multiplier = rec.recommended_value,
410                    "l2_size_multiplier" => params.l2_size_multiplier = rec.recommended_value,
411                    "promotion_threshold" => {
412                        params.promotion_threshold = rec.recommended_value as u64
413                    }
414                    "eviction_aggressiveness" => {
415                        params.eviction_aggressiveness = rec.recommended_value
416                    }
417                    "ttl_multiplier" => params.ttl_multiplier = rec.recommended_value,
418                    "preheating_aggressiveness" => {
419                        params.preheating_aggressiveness = rec.recommended_value
420                    }
421                    _ => continue,
422                }
423
424                stats.tuning_operations += 1;
425                stats.last_tuning = SystemTime::now();
426            }
427        }
428    }
429
430    /// Get current tuning parameters
431    pub fn get_current_parameters(&self) -> TuningParameters {
432        self.current_params.read().unwrap().clone()
433    }
434
435    /// Get tuning statistics
436    pub fn get_stats(&self) -> AdaptiveTuningStats {
437        let mut stats = self.stats.read().unwrap().clone();
438
439        // Calculate success rate
440        if stats.tuning_operations > 0 {
441            stats.success_rate = stats.successful_tunings as f64 / stats.tuning_operations as f64;
442        }
443
444        stats
445    }
446
447    /// Record tuning outcome
448    pub async fn record_tuning_outcome(&self, improved: bool, improvement: f64) {
449        let mut stats = self.stats.write().unwrap();
450
451        if improved {
452            stats.successful_tunings += 1;
453            stats.avg_improvement = (stats.avg_improvement * (stats.successful_tunings - 1) as f64
454                + improvement)
455                / stats.successful_tunings as f64;
456        } else {
457            stats.failed_tunings += 1;
458        }
459
460        // Update confidence based on recent success rate
461        if stats.tuning_operations > 0 {
462            stats.current_confidence =
463                stats.successful_tunings as f64 / stats.tuning_operations as f64;
464        }
465    }
466}