Skip to main content

heliosdb_proxy/cache/
config.rs

1//! Cache Configuration
2//!
3//! Configuration structures for the multi-tier query cache.
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::time::Duration;
8
9/// Main cache configuration
10#[derive(Debug, Clone)]
11pub struct CacheConfig {
12    /// Enable/disable cache globally
13    pub enabled: bool,
14
15    /// L1 hot cache configuration
16    pub l1: L1Config,
17
18    /// L2 warm cache configuration
19    pub l2: L2Config,
20
21    /// L3 semantic cache configuration
22    pub l3: L3Config,
23
24    /// Cache invalidation configuration
25    pub invalidation: InvalidationConfig,
26
27    /// Default TTL for cached results
28    pub default_ttl: Duration,
29
30    /// Maximum result size to cache (bytes)
31    pub max_result_size: usize,
32
33    /// Table-specific configurations
34    pub table_configs: HashMap<String, TableCacheConfig>,
35
36    /// Excluded tables (never cache)
37    pub excluded_tables: Vec<String>,
38}
39
40impl Default for CacheConfig {
41    fn default() -> Self {
42        Self {
43            enabled: true,
44            l1: L1Config::default(),
45            l2: L2Config::default(),
46            l3: L3Config::default(),
47            invalidation: InvalidationConfig::default(),
48            default_ttl: Duration::from_secs(300), // 5 minutes
49            max_result_size: 10 * 1024 * 1024,     // 10 MB
50            table_configs: HashMap::new(),
51            excluded_tables: Vec::new(),
52        }
53    }
54}
55
56impl CacheConfig {
57    /// Create a minimal configuration (L1 only)
58    pub fn minimal() -> Self {
59        Self {
60            enabled: true,
61            l1: L1Config::default(),
62            l2: L2Config {
63                enabled: false,
64                ..Default::default()
65            },
66            l3: L3Config {
67                enabled: false,
68                ..Default::default()
69            },
70            ..Default::default()
71        }
72    }
73
74    /// Create a configuration optimized for high-throughput reads
75    pub fn high_throughput() -> Self {
76        Self {
77            enabled: true,
78            l1: L1Config {
79                size: 2000,
80                ttl: Duration::from_secs(60),
81                ..Default::default()
82            },
83            l2: L2Config {
84                size_mb: 1024,
85                ttl: Duration::from_secs(600),
86                ..Default::default()
87            },
88            l3: L3Config {
89                enabled: false,
90                ..Default::default()
91            },
92            default_ttl: Duration::from_secs(300),
93            ..Default::default()
94        }
95    }
96
97    /// Create a configuration optimized for AI/RAG workloads
98    pub fn ai_workload() -> Self {
99        Self {
100            enabled: true,
101            l1: L1Config::default(),
102            l2: L2Config::default(),
103            l3: L3Config {
104                enabled: true,
105                similarity_threshold: 0.90,
106                max_entries: 10000,
107                ..Default::default()
108            },
109            ..Default::default()
110        }
111    }
112
113    /// Validate configuration
114    pub fn validate(&self) -> Result<(), String> {
115        if self.l1.size == 0 && self.l1.enabled {
116            return Err("L1 cache size cannot be 0 when enabled".to_string());
117        }
118
119        if self.l2.size_mb == 0 && self.l2.enabled {
120            return Err("L2 cache size cannot be 0 when enabled".to_string());
121        }
122
123        if self.l3.similarity_threshold < 0.0 || self.l3.similarity_threshold > 1.0 {
124            return Err("L3 similarity threshold must be between 0.0 and 1.0".to_string());
125        }
126
127        Ok(())
128    }
129}
130
131/// L1 hot cache configuration (per-connection)
132#[derive(Debug, Clone)]
133pub struct L1Config {
134    /// Enable L1 cache
135    pub enabled: bool,
136
137    /// Maximum entries per connection
138    pub size: usize,
139
140    /// Time-to-live for cached entries
141    pub ttl: Duration,
142}
143
144impl Default for L1Config {
145    fn default() -> Self {
146        Self {
147            enabled: true,
148            size: 500,
149            ttl: Duration::from_secs(30),
150        }
151    }
152}
153
154/// L2 warm cache configuration (shared)
155#[derive(Debug, Clone)]
156pub struct L2Config {
157    /// Enable L2 cache
158    pub enabled: bool,
159
160    /// Maximum cache size in MB
161    pub size_mb: usize,
162
163    /// Time-to-live for cached entries
164    pub ttl: Duration,
165
166    /// Enable query normalization
167    pub normalize_queries: bool,
168
169    /// Storage backend
170    pub storage: StorageBackend,
171
172    /// Memory-mapped file path (for mmap backend)
173    pub mmap_path: Option<PathBuf>,
174}
175
176impl Default for L2Config {
177    fn default() -> Self {
178        Self {
179            enabled: true,
180            size_mb: 256,
181            ttl: Duration::from_secs(300),
182            normalize_queries: true,
183            storage: StorageBackend::Memory,
184            mmap_path: None,
185        }
186    }
187}
188
189/// Storage backend for L2 cache
190#[derive(Debug, Clone, PartialEq, Eq, Default)]
191pub enum StorageBackend {
192    /// In-process memory (lost on restart)
193    #[default]
194    Memory,
195    /// Memory-mapped file (survives restarts)
196    Mmap,
197}
198
199/// L3 semantic cache configuration
200#[derive(Debug, Clone)]
201pub struct L3Config {
202    /// Enable L3 semantic cache
203    pub enabled: bool,
204
205    /// Cosine similarity threshold for cache hits
206    pub similarity_threshold: f32,
207
208    /// Maximum entries in semantic cache
209    pub max_entries: usize,
210
211    /// Time-to-live for semantic cache entries
212    pub ttl: Duration,
213
214    /// Ollama endpoint for embeddings
215    pub embedding_endpoint: String,
216
217    /// Embedding model name
218    pub embedding_model: String,
219
220    /// Embedding dimension
221    pub embedding_dim: usize,
222}
223
224impl Default for L3Config {
225    fn default() -> Self {
226        Self {
227            enabled: false, // Disabled by default (requires Ollama)
228            similarity_threshold: 0.92,
229            max_entries: 5000,
230            ttl: Duration::from_secs(3600),
231            embedding_endpoint: "http://localhost:11434".to_string(),
232            embedding_model: "all-minilm".to_string(),
233            embedding_dim: 384, // all-MiniLM-L6-v2 dimension
234        }
235    }
236}
237
238/// Cache invalidation configuration
239#[derive(Debug, Clone)]
240pub struct InvalidationConfig {
241    /// Invalidation mode
242    pub mode: super::InvalidationMode,
243
244    /// Subscribe to WAL for automatic invalidation
245    pub wal_subscribe: bool,
246
247    /// Fallback TTL when WAL is unavailable
248    pub ttl_fallback: Duration,
249}
250
251impl Default for InvalidationConfig {
252    fn default() -> Self {
253        Self {
254            mode: super::InvalidationMode::Wal,
255            wal_subscribe: true,
256            ttl_fallback: Duration::from_secs(60),
257        }
258    }
259}
260
261/// Table-specific cache configuration
262#[derive(Debug, Clone)]
263pub struct TableCacheConfig {
264    /// Time-to-live for this table
265    pub ttl: Duration,
266
267    /// Exclude this table from caching
268    pub exclude: bool,
269
270    /// Columns to exclude from cached results
271    pub exclude_columns: Vec<String>,
272}
273
274impl Default for TableCacheConfig {
275    fn default() -> Self {
276        Self {
277            ttl: Duration::from_secs(300),
278            exclude: false,
279            exclude_columns: Vec::new(),
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn test_default_config() {
290        let config = CacheConfig::default();
291        assert!(config.enabled);
292        assert!(config.l1.enabled);
293        assert!(config.l2.enabled);
294        assert!(!config.l3.enabled); // Disabled by default
295        assert_eq!(config.l1.size, 500);
296        assert_eq!(config.l2.size_mb, 256);
297    }
298
299    #[test]
300    fn test_minimal_config() {
301        let config = CacheConfig::minimal();
302        assert!(config.l1.enabled);
303        assert!(!config.l2.enabled);
304        assert!(!config.l3.enabled);
305    }
306
307    #[test]
308    fn test_high_throughput_config() {
309        let config = CacheConfig::high_throughput();
310        assert_eq!(config.l1.size, 2000);
311        assert_eq!(config.l2.size_mb, 1024);
312        assert!(!config.l3.enabled);
313    }
314
315    #[test]
316    fn test_ai_workload_config() {
317        let config = CacheConfig::ai_workload();
318        assert!(config.l3.enabled);
319        assert_eq!(config.l3.similarity_threshold, 0.90);
320    }
321
322    #[test]
323    fn test_validation() {
324        let config = CacheConfig::default();
325        assert!(config.validate().is_ok());
326
327        let mut invalid = CacheConfig::default();
328        invalid.l1.size = 0;
329        assert!(invalid.validate().is_err());
330
331        let mut invalid2 = CacheConfig::default();
332        invalid2.l3.similarity_threshold = 1.5;
333        assert!(invalid2.validate().is_err());
334    }
335
336    #[test]
337    fn test_storage_backend() {
338        assert_eq!(StorageBackend::default(), StorageBackend::Memory);
339    }
340}