oxirs_vec/compaction/
strategies.rs

1//! Compaction strategies
2
3use serde::{Deserialize, Serialize};
4use std::time::{Duration, SystemTime};
5
6/// Compaction strategy
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum CompactionStrategy {
9    /// Periodic compaction at fixed intervals
10    Periodic,
11    /// Threshold-based (compact when fragmentation exceeds threshold)
12    ThresholdBased,
13    /// Size-based (compact when wasted space exceeds threshold)
14    SizeBased,
15    /// Adaptive (automatically adjust based on workload)
16    Adaptive,
17    /// Manual (only compact when explicitly triggered)
18    Manual,
19}
20
21impl Default for CompactionStrategy {
22    fn default() -> Self {
23        Self::Adaptive
24    }
25}
26
27/// Strategy evaluator
28pub struct StrategyEvaluator {
29    strategy: CompactionStrategy,
30    last_compaction: Option<SystemTime>,
31}
32
33impl StrategyEvaluator {
34    /// Create a new strategy evaluator
35    pub fn new(strategy: CompactionStrategy) -> Self {
36        Self {
37            strategy,
38            last_compaction: None,
39        }
40    }
41
42    /// Check if compaction should be triggered
43    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                // Compact if interval has elapsed
55                time_since_last.map(|d| d >= interval).unwrap_or(true)
56            }
57            CompactionStrategy::ThresholdBased => {
58                // Compact if fragmentation exceeds threshold
59                fragmentation >= fragmentation_threshold
60            }
61            CompactionStrategy::SizeBased => {
62                // Compact if wasted space exceeds threshold
63                wasted_bytes >= size_threshold_bytes
64            }
65            CompactionStrategy::Adaptive => {
66                // Combine multiple factors
67                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                // Never automatically compact
78                false
79            }
80        }
81    }
82
83    /// Adaptive evaluation combining multiple factors
84    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        // Factor 1: Fragmentation (40% weight)
96        if fragmentation > 0.0 {
97            let frag_ratio = fragmentation / fragmentation_threshold;
98            score += frag_ratio.min(1.0) * 0.4;
99        }
100
101        // Factor 2: Wasted space (30% weight)
102        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        // Factor 3: Time since last compaction (30% weight)
108        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            // Never compacted, give full weight
113            score += 0.3;
114        }
115
116        // Trigger if score exceeds threshold
117        score >= 0.7
118    }
119
120    /// Update last compaction time
121    pub fn record_compaction(&mut self) {
122        self.last_compaction = Some(SystemTime::now());
123    }
124
125    /// Get time since last compaction
126    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    /// Calculate priority for compaction (0.0 - 1.0, higher = more urgent)
132    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                // Priority based on time
141                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                // Priority based on fragmentation
147                fragmentation
148            }
149            CompactionStrategy::SizeBased => {
150                // Priority based on wasted space
151                let size_gb = wasted_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
152                (size_gb / 10.0).min(1.0) // Normalize to 10GB max
153            }
154            CompactionStrategy::Adaptive => {
155                // Weighted combination
156                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        // Should compact if enough time has passed
178        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        // Should not compact if not enough time
188        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        // Should compact if fragmentation exceeds threshold
203        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        // Should not compact if below threshold
213        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        // Should compact if wasted space exceeds threshold
228        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        // Should not compact if below threshold
238        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        // Should compact with high fragmentation and time
253        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        // Should not compact with low values
263        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        // Should never auto-compact
278        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}