linkrs_query/cache/cte_cache/
manager.rs1use moka::sync::Cache;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::cache::config::CteCacheConfig;
6use crate::cache::stats::{CteCacheStats, CteCacheStatsSnapshot};
7use linkrs_metrics::StatsManager;
8
9use super::entry::CteCacheEntry;
10
11#[derive(Debug)]
15pub struct CteCacheManager {
16 cache: Cache<String, Arc<CteCacheEntry>>,
18 config: Arc<std::sync::RwLock<CteCacheConfig>>,
20 stats: Arc<CteCacheStats>,
22 stats_manager: std::sync::RwLock<Option<Arc<StatsManager>>>,
24}
25
26impl CteCacheManager {
27 pub fn new() -> Self {
29 Self::with_config(CteCacheConfig::default())
30 }
31
32 pub fn with_config(config: CteCacheConfig) -> Self {
34 let max_weight = config.max_size as u64;
35
36 let cache = Cache::builder()
37 .weigher(|_key, value: &Arc<CteCacheEntry>| -> u32 {
38 let arc_overhead = std::mem::size_of::<Arc<CteCacheEntry>>();
39 (value.estimate_memory() + arc_overhead) as u32
40 })
41 .max_capacity(max_weight)
42 .time_to_live(Duration::from_secs(config.entry_ttl_seconds))
43 .build();
44
45 let stats = Arc::new(CteCacheStats::new(config.max_size));
46
47 Self {
48 cache,
49 config: Arc::new(std::sync::RwLock::new(config.clone())),
50 stats,
51 stats_manager: std::sync::RwLock::new(None),
52 }
53 }
54
55 pub fn with_stats_manager(mut self, stats_manager: Arc<StatsManager>) -> Self {
56 self.stats_manager = std::sync::RwLock::new(Some(stats_manager));
57 self
58 }
59
60 pub fn set_stats_manager(&self, stats_manager: Arc<StatsManager>) {
62 if let Ok(mut guard) = self.stats_manager.write() {
63 *guard = Some(stats_manager);
64 }
65 }
66
67 pub fn config(&self) -> CteCacheConfig {
69 self.config.read().expect("Config lock poisoned").clone()
70 }
71
72 pub fn set_config(&self, config: CteCacheConfig) {
74 let mut cfg = self.config.write().expect("Config lock poisoned");
75 self.stats.memory.set_max_bytes(config.max_size);
76 *cfg = config;
77 }
78
79 pub fn is_enabled(&self) -> bool {
81 self.config.read().expect("Config lock poisoned").enabled
82 }
83
84 pub fn should_cache(
91 &self,
92 cte_definition: &str,
93 estimated_rows: u64,
94 is_deterministic: bool,
95 ) -> bool {
96 let config = self.config.read().expect("Config lock poisoned");
97
98 if !config.enabled {
99 return false;
100 }
101
102 if !is_deterministic {
103 return false;
104 }
105
106 if estimated_rows < config.min_row_count || estimated_rows > config.max_row_count {
107 return false;
108 }
109
110 let reuse_prob = self.predict_reuse_probability(cte_definition);
111 if reuse_prob < 0.3 {
112 return false;
113 }
114
115 true
116 }
117
118 pub(super) fn predict_reuse_probability(&self, cte_definition: &str) -> f64 {
120 let cte_hash = Self::compute_hash(cte_definition);
121
122 if let Some(entry) = self.cache.get(&cte_hash) {
123 return entry.reuse_probability;
124 }
125
126 let complexity = cte_definition.len() as f64 / 100.0;
127 let base_prob = 0.5;
128 let complexity_bonus = (complexity / 10.0).min(0.3);
129
130 base_prob + complexity_bonus
131 }
132
133 pub(super) fn compute_hash(cte_definition: &str) -> String {
135 use std::collections::hash_map::DefaultHasher;
136 use std::hash::{Hash, Hasher};
137
138 let mut hasher = DefaultHasher::new();
139 cte_definition.hash(&mut hasher);
140 format!("{:016x}", hasher.finish())
141 }
142
143 pub fn put(&self, cte_definition: &str, data: Vec<u8>, row_count: u64) -> Option<String> {
145 self.put_with_cost(cte_definition, data, row_count, 100)
146 }
147
148 pub fn put_with_cost(
150 &self,
151 cte_definition: &str,
152 data: Vec<u8>,
153 row_count: u64,
154 compute_cost_ms: u64,
155 ) -> Option<String> {
156 let config = self.config.read().expect("Config lock poisoned");
157
158 if !config.enabled {
159 return None;
160 }
161
162 if data.len() > config.max_entry_size {
163 self.stats.counters.record_rejection();
164 return None;
165 }
166
167 drop(config);
168
169 let cte_hash = Self::compute_hash(cte_definition);
170 let entry = Arc::new(CteCacheEntry::new(
171 cte_hash.clone(),
172 cte_definition.to_string(),
173 data,
174 row_count,
175 compute_cost_ms,
176 ));
177
178 self.cache.insert(cte_hash.clone(), entry);
179
180 self.stats.counters.record_insertion();
181 self.update_stats();
182
183 Some(cte_hash)
184 }
185
186 pub fn put_with_tables(
188 &self,
189 cte_definition: &str,
190 data: Vec<u8>,
191 row_count: u64,
192 compute_cost_ms: u64,
193 dependent_tables: Vec<String>,
194 ) -> Option<String> {
195 let config = self.config.read().expect("Config lock poisoned");
196
197 if !config.enabled {
198 return None;
199 }
200
201 if data.len() > config.max_entry_size {
202 self.stats.counters.record_rejection();
203 return None;
204 }
205
206 drop(config);
207
208 let cte_hash = Self::compute_hash(cte_definition);
209 let mut entry = CteCacheEntry::new(
210 cte_hash.clone(),
211 cte_definition.to_string(),
212 data,
213 row_count,
214 compute_cost_ms,
215 );
216 entry.dependent_tables = dependent_tables;
217
218 self.cache.insert(cte_hash.clone(), Arc::new(entry));
219
220 self.stats.counters.record_insertion();
221 self.update_stats();
222
223 Some(cte_hash)
224 }
225
226 pub fn evict_low_priority(&self, target_bytes: usize) -> usize {
228 let mut freed = 0;
229 let mut to_remove = Vec::new();
230
231 let entries: Vec<_> = self
232 .cache
233 .iter()
234 .map(|entry| {
235 let value_score = entry.1.value_score();
236 (
237 entry.0.as_ref().clone(),
238 value_score,
239 entry.1.data_size,
240 entry.1.priority,
241 )
242 })
243 .collect();
244
245 let mut entries_sorted = entries;
246 entries_sorted.sort_by(|a, b| {
247 a.1.partial_cmp(&b.1)
248 .unwrap_or(std::cmp::Ordering::Equal)
249 .then_with(|| a.3.cmp(&b.3))
250 });
251
252 for (key, _, size, _) in entries_sorted {
253 if freed >= target_bytes {
254 break;
255 }
256 to_remove.push(key);
257 freed += size;
258 }
259
260 for key in &to_remove {
261 self.cache.invalidate(key);
262 }
263
264 if freed > 0 {
265 self.stats.counters.record_eviction();
266 self.update_stats();
267 }
268
269 freed
270 }
271
272 pub fn get(&self, cte_definition: &str) -> Option<Arc<Vec<u8>>> {
274 let config = self.config.read().expect("Config lock poisoned");
275
276 if !config.enabled {
277 return None;
278 }
279
280 drop(config);
281
282 let cte_hash = Self::compute_hash(cte_definition);
283
284 if let Some(entry) = self.cache.get(&cte_hash) {
285 self.stats.counters.record_hit();
286 if let Ok(ref sm_guard) = self.stats_manager.read() {
287 if let Some(ref sm) = **sm_guard {
288 sm.record_cache_hit_global(true);
289 }
290 }
291 Some(entry.data.clone())
292 } else {
293 self.stats.counters.record_miss();
294 if let Ok(ref sm_guard) = self.stats_manager.read() {
295 if let Some(ref sm) = **sm_guard {
296 sm.record_cache_hit_global(false);
297 }
298 }
299 None
300 }
301 }
302
303 pub fn contains(&self, cte_definition: &str) -> bool {
305 let cte_hash = Self::compute_hash(cte_definition);
306 self.cache.contains_key(&cte_hash)
307 }
308
309 pub fn invalidate(&self, cte_definition: &str) -> bool {
311 let cte_hash = Self::compute_hash(cte_definition);
312 self.invalidate_by_hash(&cte_hash)
313 }
314
315 pub fn invalidate_by_hash(&self, cte_hash: &str) -> bool {
317 let removed = self.cache.remove(cte_hash).is_some();
318
319 if removed {
320 self.stats.counters.record_eviction();
321 self.update_stats();
322 }
323
324 removed
325 }
326
327 pub fn invalidate_by_table(&self, table_name: &str) -> usize {
329 let mut count = 0;
330 let keys_to_remove: Vec<String> = self
331 .cache
332 .iter()
333 .filter(|entry| entry.1.dependent_tables.iter().any(|t| t == table_name))
334 .map(|entry| entry.0.as_ref().clone())
335 .collect();
336
337 for key in keys_to_remove {
338 if self.cache.remove(&key).is_some() {
339 count += 1;
340 }
341 }
342
343 if count > 0 {
344 self.stats.counters.record_eviction();
345 self.update_stats();
346 }
347
348 count
349 }
350
351 pub fn get_cache_entries(&self) -> Vec<(String, f64, usize)> {
353 self.cache
354 .iter()
355 .map(|entry| {
356 let value_score = entry.1.value_score();
357 (entry.0.as_ref().clone(), value_score, entry.1.data_size)
358 })
359 .collect()
360 }
361
362 pub fn increment_evicted_count(&self, count: u64) {
364 for _ in 0..count {
365 self.stats.counters.record_eviction();
366 }
367 }
368
369 pub fn clear(&self) {
371 self.cache.invalidate_all();
372 self.stats.reset();
373 }
374
375 pub fn get_stats(&self) -> CteCacheStatsSnapshot {
377 self.update_stats();
378 self.stats.snapshot()
379 }
380
381 fn update_stats(&self) {
383 let current_entries = self.cache.entry_count() as usize;
384 let current_memory = self.estimate_current_memory();
385 self.stats.memory.update(current_memory, current_entries);
386 }
387
388 fn estimate_current_memory(&self) -> usize {
390 self.cache
391 .iter()
392 .map(|entry| entry.1.estimate_memory())
393 .sum()
394 }
395
396 pub fn reset_stats(&self) {
398 self.stats.reset();
399 }
400
401 pub fn current_memory(&self) -> usize {
403 self.estimate_current_memory()
404 }
405
406 pub fn entry_count(&self) -> usize {
408 self.cache.entry_count() as usize
409 }
410
411 pub fn cleanup_expired(&self) -> usize {
414 0
415 }
416
417 pub fn stats(&self) -> Arc<CteCacheStats> {
419 self.stats.clone()
420 }
421}
422
423impl Default for CteCacheManager {
424 fn default() -> Self {
425 Self::new()
426 }
427}