Skip to main content

do_memory_storage_turso/cache/
ttl_config.rs

1//! TTL Configuration for Adaptive Cache
2//!
3//! This module provides configuration types for the adaptive TTL cache system,
4//! including TTL bounds, adaptation policies, and memory pressure thresholds.
5
6use std::time::Duration;
7
8/// Default base TTL (5 minutes)
9pub const DEFAULT_BASE_TTL: Duration = Duration::from_secs(300);
10
11/// Default minimum TTL (1 minute)
12pub const DEFAULT_MIN_TTL: Duration = Duration::from_secs(60);
13
14/// Default maximum TTL (1 hour)
15pub const DEFAULT_MAX_TTL: Duration = Duration::from_secs(3600);
16
17/// Default hot threshold (accesses to be considered "hot")
18pub const DEFAULT_HOT_THRESHOLD: u64 = 10;
19
20/// Default cold threshold (accesses to be considered "cold")
21pub const DEFAULT_COLD_THRESHOLD: u64 = 2;
22
23/// Default adaptation rate (0.0 - 1.0)
24pub const DEFAULT_ADAPTATION_RATE: f64 = 0.25;
25
26/// Default cleanup interval (60 seconds)
27pub const DEFAULT_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
28
29/// Configuration for adaptive TTL cache
30#[derive(Debug, Clone)]
31pub struct TTLConfig {
32    /// Base TTL for new entries
33    pub base_ttl: Duration,
34
35    /// Minimum TTL (entries won't be reduced below this)
36    pub min_ttl: Duration,
37
38    /// Maximum TTL (entries won't be extended beyond this)
39    pub max_ttl: Duration,
40
41    /// Hot threshold - access count above which TTL is extended
42    pub hot_threshold: u64,
43
44    /// Cold threshold - access count below which TTL is reduced
45    pub cold_threshold: u64,
46
47    /// Adaptation rate - how fast TTL changes (0.0 - 1.0)
48    pub adaptation_rate: f64,
49
50    /// Enable background cleanup task
51    pub enable_background_cleanup: bool,
52
53    /// Cleanup interval for expired entries
54    pub cleanup_interval: Duration,
55
56    /// Maximum number of entries in cache
57    pub max_entries: usize,
58
59    /// Memory pressure threshold (0.0 - 1.0)
60    pub memory_pressure_threshold: f64,
61
62    /// Enable adaptive TTL based on access patterns
63    pub enable_adaptive_ttl: bool,
64
65    /// Time window for access pattern analysis (seconds)
66    pub access_window_secs: u64,
67}
68
69impl Default for TTLConfig {
70    fn default() -> Self {
71        Self {
72            base_ttl: DEFAULT_BASE_TTL,
73            min_ttl: DEFAULT_MIN_TTL,
74            max_ttl: DEFAULT_MAX_TTL,
75            hot_threshold: DEFAULT_HOT_THRESHOLD,
76            cold_threshold: DEFAULT_COLD_THRESHOLD,
77            adaptation_rate: DEFAULT_ADAPTATION_RATE,
78            enable_background_cleanup: true,
79            cleanup_interval: DEFAULT_CLEANUP_INTERVAL,
80            max_entries: 10_000,
81            memory_pressure_threshold: 0.8,
82            enable_adaptive_ttl: true,
83            access_window_secs: 300, // 5 minutes
84        }
85    }
86}
87
88impl TTLConfig {
89    /// Create a new TTLConfig with default values
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    /// Create a configuration optimized for high-hit-rate scenarios
95    pub fn high_hit_rate() -> Self {
96        Self {
97            base_ttl: Duration::from_secs(600), // 10 minutes
98            min_ttl: Duration::from_secs(120),  // 2 minutes
99            max_ttl: Duration::from_secs(7200), // 2 hours
100            hot_threshold: 5,                   // Lower threshold
101            cold_threshold: 1,
102            adaptation_rate: 0.35, // Faster adaptation
103            enable_background_cleanup: true,
104            cleanup_interval: Duration::from_secs(30),
105            max_entries: 20_000,
106            memory_pressure_threshold: 0.85,
107            enable_adaptive_ttl: true,
108            access_window_secs: 180, // 3 minutes
109        }
110    }
111
112    /// Create a configuration optimized for memory-constrained environments
113    pub fn memory_constrained() -> Self {
114        Self {
115            base_ttl: Duration::from_secs(180), // 3 minutes
116            min_ttl: Duration::from_secs(30),   // 30 seconds
117            max_ttl: Duration::from_secs(1800), // 30 minutes
118            hot_threshold: 15,
119            cold_threshold: 3,
120            adaptation_rate: 0.2,
121            enable_background_cleanup: true,
122            cleanup_interval: Duration::from_secs(15),
123            max_entries: 1_000,
124            memory_pressure_threshold: 0.6, // Lower threshold
125            enable_adaptive_ttl: true,
126            access_window_secs: 120, // 2 minutes
127        }
128    }
129
130    /// Create a configuration optimized for write-heavy workloads
131    pub fn write_heavy() -> Self {
132        Self {
133            base_ttl: Duration::from_secs(120), // 2 minutes
134            min_ttl: Duration::from_secs(30),   // 30 seconds
135            max_ttl: Duration::from_secs(600),  // 10 minutes
136            hot_threshold: 20,
137            cold_threshold: 5,
138            adaptation_rate: 0.15, // Slower adaptation
139            enable_background_cleanup: true,
140            cleanup_interval: Duration::from_secs(20),
141            max_entries: 5_000,
142            memory_pressure_threshold: 0.75,
143            enable_adaptive_ttl: true,
144            access_window_secs: 60, // 1 minute
145        }
146    }
147
148    /// Validate the configuration
149    pub fn validate(&self) -> Result<(), TTLConfigError> {
150        if self.min_ttl > self.base_ttl {
151            return Err(TTLConfigError::InvalidBounds(
152                "min_ttl cannot be greater than base_ttl".to_string(),
153            ));
154        }
155        if self.base_ttl > self.max_ttl {
156            return Err(TTLConfigError::InvalidBounds(
157                "base_ttl cannot be greater than max_ttl".to_string(),
158            ));
159        }
160        if !(0.0..=1.0).contains(&self.adaptation_rate) {
161            return Err(TTLConfigError::InvalidAdaptationRate(self.adaptation_rate));
162        }
163        if !(0.0..=1.0).contains(&self.memory_pressure_threshold) {
164            return Err(TTLConfigError::InvalidThreshold(
165                self.memory_pressure_threshold,
166            ));
167        }
168        if self.hot_threshold <= self.cold_threshold {
169            return Err(TTLConfigError::InvalidThresholds {
170                hot: self.hot_threshold,
171                cold: self.cold_threshold,
172            });
173        }
174        if self.max_entries == 0 {
175            return Err(TTLConfigError::InvalidMaxEntries);
176        }
177        Ok(())
178    }
179
180    /// Calculate adapted TTL based on access count
181    pub fn calculate_ttl(&self, current_ttl: Duration, access_count: u64) -> Duration {
182        if !self.enable_adaptive_ttl {
183            return self.base_ttl;
184        }
185
186        let new_ttl = if access_count >= self.hot_threshold {
187            // Extend TTL for hot items
188            let extension = current_ttl.mul_f64(self.adaptation_rate);
189            current_ttl + extension
190        } else if access_count <= self.cold_threshold {
191            // Reduce TTL for cold items
192            let reduction = current_ttl.mul_f64(self.adaptation_rate);
193            current_ttl.saturating_sub(reduction)
194        } else {
195            current_ttl
196        };
197
198        // Clamp to bounds
199        new_ttl.clamp(self.min_ttl, self.max_ttl)
200    }
201
202    /// Builder method to set base TTL
203    pub fn with_base_ttl(mut self, ttl: Duration) -> Self {
204        self.base_ttl = ttl;
205        self
206    }
207
208    /// Builder method to set min TTL
209    pub fn with_min_ttl(mut self, ttl: Duration) -> Self {
210        self.min_ttl = ttl;
211        self
212    }
213
214    /// Builder method to set max TTL
215    pub fn with_max_ttl(mut self, ttl: Duration) -> Self {
216        self.max_ttl = ttl;
217        self
218    }
219
220    /// Builder method to set hot threshold
221    pub fn with_hot_threshold(mut self, threshold: u64) -> Self {
222        self.hot_threshold = threshold;
223        self
224    }
225
226    /// Builder method to set cold threshold
227    pub fn with_cold_threshold(mut self, threshold: u64) -> Self {
228        self.cold_threshold = threshold;
229        self
230    }
231
232    /// Builder method to set adaptation rate
233    pub fn with_adaptation_rate(mut self, rate: f64) -> Self {
234        self.adaptation_rate = rate.clamp(0.0, 1.0);
235        self
236    }
237
238    /// Builder method to set max entries
239    pub fn with_max_entries(mut self, max: usize) -> Self {
240        self.max_entries = max;
241        self
242    }
243}
244
245/// Errors that can occur during TTL configuration validation
246#[derive(Debug, Clone, PartialEq)]
247pub enum TTLConfigError {
248    /// Invalid TTL bounds
249    InvalidBounds(String),
250    /// Invalid adaptation rate (must be 0.0 - 1.0)
251    InvalidAdaptationRate(f64),
252    /// Invalid threshold (must be 0.0 - 1.0)
253    InvalidThreshold(f64),
254    /// Invalid hot/cold thresholds (hot must be > cold)
255    InvalidThresholds { hot: u64, cold: u64 },
256    /// Invalid max entries (must be > 0)
257    InvalidMaxEntries,
258}
259
260impl std::fmt::Display for TTLConfigError {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        match self {
263            Self::InvalidBounds(msg) => write!(f, "Invalid TTL bounds: {msg}"),
264            Self::InvalidAdaptationRate(rate) => {
265                write!(f, "Invalid adaptation rate: {rate} (must be 0.0 - 1.0)")
266            }
267            Self::InvalidThreshold(threshold) => {
268                write!(f, "Invalid threshold: {threshold} (must be 0.0 - 1.0)")
269            }
270            Self::InvalidThresholds { hot, cold } => {
271                write!(f, "Invalid thresholds: hot ({hot}) must be > cold ({cold})")
272            }
273            Self::InvalidMaxEntries => write!(f, "Invalid max entries: must be > 0"),
274        }
275    }
276}
277
278impl std::error::Error for TTLConfigError {}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn test_default_config() {
286        let config = TTLConfig::default();
287        assert_eq!(config.base_ttl, DEFAULT_BASE_TTL);
288        assert_eq!(config.min_ttl, DEFAULT_MIN_TTL);
289        assert_eq!(config.max_ttl, DEFAULT_MAX_TTL);
290        assert!(config.validate().is_ok());
291    }
292
293    #[test]
294    fn test_high_hit_rate_config() {
295        let config = TTLConfig::high_hit_rate();
296        assert!(config.validate().is_ok());
297        assert_eq!(config.base_ttl, Duration::from_secs(600));
298        assert!(config.max_entries > 10_000);
299    }
300
301    #[test]
302    fn test_memory_constrained_config() {
303        let config = TTLConfig::memory_constrained();
304        assert!(config.validate().is_ok());
305        assert!(config.max_entries < 2_000);
306        assert!(config.memory_pressure_threshold < 0.7);
307    }
308
309    #[test]
310    fn test_write_heavy_config() {
311        let config = TTLConfig::write_heavy();
312        assert!(config.validate().is_ok());
313        assert!(config.base_ttl < Duration::from_secs(300));
314    }
315
316    #[test]
317    fn test_invalid_bounds() {
318        let config = TTLConfig {
319            min_ttl: Duration::from_secs(600),
320            base_ttl: Duration::from_secs(300),
321            ..Default::default()
322        };
323        assert!(matches!(
324            config.validate(),
325            Err(TTLConfigError::InvalidBounds(_))
326        ));
327    }
328
329    #[test]
330    fn test_invalid_adaptation_rate() {
331        let config = TTLConfig {
332            adaptation_rate: 1.5,
333            ..Default::default()
334        };
335        assert!(matches!(
336            config.validate(),
337            Err(TTLConfigError::InvalidAdaptationRate(1.5))
338        ));
339    }
340
341    #[test]
342    fn test_invalid_thresholds() {
343        let config = TTLConfig {
344            hot_threshold: 2,
345            cold_threshold: 5,
346            ..Default::default()
347        };
348        assert!(matches!(
349            config.validate(),
350            Err(TTLConfigError::InvalidThresholds { hot: 2, cold: 5 })
351        ));
352    }
353
354    #[test]
355    fn test_calculate_ttl_hot() {
356        let config = TTLConfig::default();
357        let current = Duration::from_secs(300);
358        let new_ttl = config.calculate_ttl(current, 15); // Above hot threshold
359        assert!(new_ttl > current);
360        assert!(new_ttl <= config.max_ttl);
361    }
362
363    #[test]
364    fn test_calculate_ttl_cold() {
365        let config = TTLConfig::default();
366        let current = Duration::from_secs(300);
367        let new_ttl = config.calculate_ttl(current, 1); // Below cold threshold
368        assert!(new_ttl < current);
369        assert!(new_ttl >= config.min_ttl);
370    }
371
372    #[test]
373    fn test_calculate_ttl_neutral() {
374        let config = TTLConfig::default();
375        let current = Duration::from_secs(300);
376        let new_ttl = config.calculate_ttl(current, 5); // Between thresholds
377        assert_eq!(new_ttl, current);
378    }
379
380    #[test]
381    fn test_builder_methods() {
382        let config = TTLConfig::new()
383            .with_base_ttl(Duration::from_secs(600))
384            .with_min_ttl(Duration::from_secs(120))
385            .with_max_ttl(Duration::from_secs(7200))
386            .with_hot_threshold(20)
387            .with_cold_threshold(3)
388            .with_adaptation_rate(0.5)
389            .with_max_entries(5000);
390
391        assert_eq!(config.base_ttl, Duration::from_secs(600));
392        assert_eq!(config.min_ttl, Duration::from_secs(120));
393        assert_eq!(config.max_ttl, Duration::from_secs(7200));
394        assert_eq!(config.hot_threshold, 20);
395        assert_eq!(config.cold_threshold, 3);
396        assert_eq!(config.adaptation_rate, 0.5);
397        assert_eq!(config.max_entries, 5000);
398        assert!(config.validate().is_ok());
399    }
400
401    #[test]
402    fn test_adaptation_rate_clamping() {
403        let config = TTLConfig::new().with_adaptation_rate(1.5);
404        assert_eq!(config.adaptation_rate, 1.0);
405
406        let config = TTLConfig::new().with_adaptation_rate(-0.5);
407        assert_eq!(config.adaptation_rate, 0.0);
408    }
409}