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