oxirs_arq/advanced_optimizer/
optimization_cache.rs1use std::collections::HashMap;
7use std::time::{Duration, Instant};
8
9use crate::algebra::Algebra;
10
11#[derive(Clone)]
13pub struct OptimizationCache {
14 plan_cache: HashMap<u64, CachedPlan>,
15 decision_cache: HashMap<u64, CachedDecision>,
16 config: CacheConfig,
17 statistics: CacheStatistics,
18}
19
20#[derive(Debug, Clone)]
22pub struct CacheConfig {
23 pub max_plan_entries: usize,
24 pub max_decision_entries: usize,
25 pub ttl_seconds: u64,
26 pub enable_statistics: bool,
27}
28
29#[derive(Debug, Clone)]
31pub struct CachedPlan {
32 pub optimized_plan: Algebra,
33 pub estimated_cost: f64,
34 pub creation_time: Instant,
35 pub access_count: usize,
36 pub last_access: Instant,
37}
38
39#[derive(Debug, Clone)]
41pub struct CachedDecision {
42 pub decision_type: DecisionType,
43 pub confidence: f64,
44 pub estimated_benefit: f64,
45 pub context_hash: u64,
46 pub creation_time: Instant,
47}
48
49#[derive(Debug, Clone)]
51pub enum DecisionType {
52 IndexSelection(String),
53 JoinAlgorithm(String),
54 StreamingStrategy(String),
55 ParallelismDegree(usize),
56 MaterializeView(String),
57}
58
59#[derive(Debug, Default, Clone)]
61pub struct CacheStatistics {
62 pub plan_hits: usize,
63 pub plan_misses: usize,
64 pub decision_hits: usize,
65 pub decision_misses: usize,
66 pub evictions: usize,
67 pub total_lookups: usize,
68}
69
70impl OptimizationCache {
71 pub fn new(config: CacheConfig) -> Self {
73 Self {
74 plan_cache: HashMap::new(),
75 decision_cache: HashMap::new(),
76 config,
77 statistics: CacheStatistics::default(),
78 }
79 }
80
81 pub fn cache_plan(&mut self, query_hash: u64, plan: Algebra, cost: f64) {
83 if self.plan_cache.len() >= self.config.max_plan_entries {
84 self.evict_least_recently_used_plan();
85 }
86
87 let cached_plan = CachedPlan {
88 optimized_plan: plan,
89 estimated_cost: cost,
90 creation_time: Instant::now(),
91 access_count: 0,
92 last_access: Instant::now(),
93 };
94
95 self.plan_cache.insert(query_hash, cached_plan);
96 }
97
98 pub fn get_cached_plan(&mut self, query_hash: u64) -> Option<Algebra> {
100 self.statistics.total_lookups += 1;
101
102 let should_remove = if let Some(cached) = self.plan_cache.get(&query_hash) {
104 self.is_expired(cached.creation_time)
105 } else {
106 false
107 };
108
109 if should_remove {
110 self.plan_cache.remove(&query_hash);
111 self.statistics.plan_misses += 1;
112 return None;
113 }
114
115 if let Some(cached) = self.plan_cache.get_mut(&query_hash) {
116 cached.access_count += 1;
117 cached.last_access = Instant::now();
118 self.statistics.plan_hits += 1;
119 Some(cached.optimized_plan.clone())
120 } else {
121 self.statistics.plan_misses += 1;
122 None
123 }
124 }
125
126 pub fn cache_decision(&mut self, context_hash: u64, decision: CachedDecision) {
128 if self.decision_cache.len() >= self.config.max_decision_entries {
129 self.evict_oldest_decision();
130 }
131
132 self.decision_cache.insert(context_hash, decision);
133 }
134
135 pub fn get_cached_decision(&mut self, context_hash: u64) -> Option<CachedDecision> {
137 self.statistics.total_lookups += 1;
138
139 let should_remove = if let Some(decision) = self.decision_cache.get(&context_hash) {
141 self.is_expired(decision.creation_time)
142 } else {
143 false
144 };
145
146 if should_remove {
147 self.decision_cache.remove(&context_hash);
148 self.statistics.decision_misses += 1;
149 return None;
150 }
151
152 if let Some(decision) = self.decision_cache.get(&context_hash) {
153 self.statistics.decision_hits += 1;
154 Some(decision.clone())
155 } else {
156 self.statistics.decision_misses += 1;
157 None
158 }
159 }
160
161 pub fn statistics(&self) -> &CacheStatistics {
163 &self.statistics
164 }
165
166 pub fn clear(&mut self) {
168 self.plan_cache.clear();
169 self.decision_cache.clear();
170 self.statistics = CacheStatistics::default();
171 }
172
173 pub fn plan_hit_ratio(&self) -> f64 {
175 let total = self.statistics.plan_hits + self.statistics.plan_misses;
176 if total == 0 {
177 0.0
178 } else {
179 self.statistics.plan_hits as f64 / total as f64
180 }
181 }
182
183 pub fn decision_hit_ratio(&self) -> f64 {
185 let total = self.statistics.decision_hits + self.statistics.decision_misses;
186 if total == 0 {
187 0.0
188 } else {
189 self.statistics.decision_hits as f64 / total as f64
190 }
191 }
192
193 pub fn hit_ratio(&self) -> f64 {
195 let total_hits = self.statistics.plan_hits + self.statistics.decision_hits;
196 let total_misses = self.statistics.plan_misses + self.statistics.decision_misses;
197 let total = total_hits + total_misses;
198 if total == 0 {
199 0.0
200 } else {
201 total_hits as f64 / total as f64
202 }
203 }
204
205 pub fn total_requests(&self) -> usize {
207 self.statistics.total_lookups
208 }
209
210 fn is_expired(&self, creation_time: Instant) -> bool {
211 creation_time.elapsed() > Duration::from_secs(self.config.ttl_seconds)
212 }
213
214 fn evict_least_recently_used_plan(&mut self) {
215 if let Some((&key, _)) = self
216 .plan_cache
217 .iter()
218 .min_by_key(|(_, cached)| cached.last_access)
219 {
220 self.plan_cache.remove(&key);
221 self.statistics.evictions += 1;
222 }
223 }
224
225 fn evict_oldest_decision(&mut self) {
226 if let Some((&key, _)) = self
227 .decision_cache
228 .iter()
229 .min_by_key(|(_, decision)| decision.creation_time)
230 {
231 self.decision_cache.remove(&key);
232 self.statistics.evictions += 1;
233 }
234 }
235}
236
237impl Default for CacheConfig {
238 fn default() -> Self {
239 Self {
240 max_plan_entries: 1000,
241 max_decision_entries: 5000,
242 ttl_seconds: 3600, enable_statistics: true,
244 }
245 }
246}