1use serde::{Deserialize, Serialize};
4use std::time::{Duration, SystemTime};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum CompactionStrategy {
9 Periodic,
11 ThresholdBased,
13 SizeBased,
15 Adaptive,
17 Manual,
19}
20
21impl Default for CompactionStrategy {
22 fn default() -> Self {
23 Self::Adaptive
24 }
25}
26
27pub struct StrategyEvaluator {
29 strategy: CompactionStrategy,
30 last_compaction: Option<SystemTime>,
31}
32
33impl StrategyEvaluator {
34 pub fn new(strategy: CompactionStrategy) -> Self {
36 Self {
37 strategy,
38 last_compaction: None,
39 }
40 }
41
42 pub fn should_compact(
44 &self,
45 fragmentation: f64,
46 wasted_bytes: u64,
47 time_since_last: Option<Duration>,
48 interval: Duration,
49 fragmentation_threshold: f64,
50 size_threshold_bytes: u64,
51 ) -> bool {
52 match self.strategy {
53 CompactionStrategy::Periodic => {
54 time_since_last.map(|d| d >= interval).unwrap_or(true)
56 }
57 CompactionStrategy::ThresholdBased => {
58 fragmentation >= fragmentation_threshold
60 }
61 CompactionStrategy::SizeBased => {
62 wasted_bytes >= size_threshold_bytes
64 }
65 CompactionStrategy::Adaptive => {
66 self.evaluate_adaptive(
68 fragmentation,
69 wasted_bytes,
70 time_since_last,
71 interval,
72 fragmentation_threshold,
73 size_threshold_bytes,
74 )
75 }
76 CompactionStrategy::Manual => {
77 false
79 }
80 }
81 }
82
83 fn evaluate_adaptive(
85 &self,
86 fragmentation: f64,
87 wasted_bytes: u64,
88 time_since_last: Option<Duration>,
89 interval: Duration,
90 fragmentation_threshold: f64,
91 size_threshold_bytes: u64,
92 ) -> bool {
93 let mut score = 0.0;
94
95 if fragmentation > 0.0 {
97 let frag_ratio = fragmentation / fragmentation_threshold;
98 score += frag_ratio.min(1.0) * 0.4;
99 }
100
101 if wasted_bytes > 0 {
103 let size_ratio = wasted_bytes as f64 / size_threshold_bytes as f64;
104 score += size_ratio.min(1.0) * 0.3;
105 }
106
107 if let Some(time_elapsed) = time_since_last {
109 let time_ratio = time_elapsed.as_secs_f64() / interval.as_secs_f64();
110 score += time_ratio.min(1.0) * 0.3;
111 } else {
112 score += 0.3;
114 }
115
116 score >= 0.7
118 }
119
120 pub fn record_compaction(&mut self) {
122 self.last_compaction = Some(SystemTime::now());
123 }
124
125 pub fn time_since_last_compaction(&self) -> Option<Duration> {
127 self.last_compaction
128 .and_then(|t| SystemTime::now().duration_since(t).ok())
129 }
130
131 pub fn calculate_priority(
133 &self,
134 fragmentation: f64,
135 wasted_bytes: u64,
136 time_since_last: Option<Duration>,
137 ) -> f64 {
138 match self.strategy {
139 CompactionStrategy::Periodic => {
140 time_since_last
142 .map(|d| (d.as_secs() as f64 / 3600.0).min(1.0))
143 .unwrap_or(1.0)
144 }
145 CompactionStrategy::ThresholdBased => {
146 fragmentation
148 }
149 CompactionStrategy::SizeBased => {
150 let size_gb = wasted_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
152 (size_gb / 10.0).min(1.0) }
154 CompactionStrategy::Adaptive => {
155 let frag_priority = fragmentation * 0.4;
157 let size_priority =
158 (wasted_bytes as f64 / (1024.0 * 1024.0 * 1024.0) / 10.0).min(1.0) * 0.3;
159 let time_priority = time_since_last
160 .map(|d| (d.as_secs() as f64 / 3600.0).min(1.0) * 0.3)
161 .unwrap_or(0.3);
162 (frag_priority + size_priority + time_priority).min(1.0)
163 }
164 CompactionStrategy::Manual => 0.0,
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_periodic_strategy() {
175 let evaluator = StrategyEvaluator::new(CompactionStrategy::Periodic);
176
177 assert!(evaluator.should_compact(
179 0.1,
180 0,
181 Some(Duration::from_secs(7200)),
182 Duration::from_secs(3600),
183 0.3,
184 100_000_000,
185 ));
186
187 assert!(!evaluator.should_compact(
189 0.5,
190 1_000_000_000,
191 Some(Duration::from_secs(1800)),
192 Duration::from_secs(3600),
193 0.3,
194 100_000_000,
195 ));
196 }
197
198 #[test]
199 fn test_threshold_based_strategy() {
200 let evaluator = StrategyEvaluator::new(CompactionStrategy::ThresholdBased);
201
202 assert!(evaluator.should_compact(
204 0.5,
205 0,
206 None,
207 Duration::from_secs(3600),
208 0.3,
209 100_000_000,
210 ));
211
212 assert!(!evaluator.should_compact(
214 0.2,
215 1_000_000_000,
216 None,
217 Duration::from_secs(3600),
218 0.3,
219 100_000_000,
220 ));
221 }
222
223 #[test]
224 fn test_size_based_strategy() {
225 let evaluator = StrategyEvaluator::new(CompactionStrategy::SizeBased);
226
227 assert!(evaluator.should_compact(
229 0.1,
230 200_000_000,
231 None,
232 Duration::from_secs(3600),
233 0.3,
234 100_000_000,
235 ));
236
237 assert!(!evaluator.should_compact(
239 0.5,
240 50_000_000,
241 None,
242 Duration::from_secs(3600),
243 0.3,
244 100_000_000,
245 ));
246 }
247
248 #[test]
249 fn test_adaptive_strategy() {
250 let evaluator = StrategyEvaluator::new(CompactionStrategy::Adaptive);
251
252 assert!(evaluator.should_compact(
254 0.4,
255 150_000_000,
256 Some(Duration::from_secs(7200)),
257 Duration::from_secs(3600),
258 0.3,
259 100_000_000,
260 ));
261
262 assert!(!evaluator.should_compact(
264 0.05,
265 10_000_000,
266 Some(Duration::from_secs(300)),
267 Duration::from_secs(3600),
268 0.3,
269 100_000_000,
270 ));
271 }
272
273 #[test]
274 fn test_manual_strategy() {
275 let evaluator = StrategyEvaluator::new(CompactionStrategy::Manual);
276
277 assert!(!evaluator.should_compact(
279 0.9,
280 10_000_000_000,
281 Some(Duration::from_secs(100000)),
282 Duration::from_secs(3600),
283 0.3,
284 100_000_000,
285 ));
286 }
287
288 #[test]
289 fn test_priority_calculation() {
290 let evaluator = StrategyEvaluator::new(CompactionStrategy::Adaptive);
291
292 let priority =
293 evaluator.calculate_priority(0.5, 500_000_000, Some(Duration::from_secs(7200)));
294
295 assert!(priority > 0.0 && priority <= 1.0);
296 }
297}