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, Default)]
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    #[default]
17    Adaptive,
18    /// Manual (only compact when explicitly triggered)
19    Manual,
20}
21
22/// Strategy evaluator
23pub struct StrategyEvaluator {
24    strategy: CompactionStrategy,
25    last_compaction: Option<SystemTime>,
26}
27
28impl StrategyEvaluator {
29    /// Create a new strategy evaluator
30    pub fn new(strategy: CompactionStrategy) -> Self {
31        Self {
32            strategy,
33            last_compaction: None,
34        }
35    }
36
37    /// Check if compaction should be triggered
38    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                // Compact if interval has elapsed
50                time_since_last.map(|d| d >= interval).unwrap_or(true)
51            }
52            CompactionStrategy::ThresholdBased => {
53                // Compact if fragmentation exceeds threshold
54                fragmentation >= fragmentation_threshold
55            }
56            CompactionStrategy::SizeBased => {
57                // Compact if wasted space exceeds threshold
58                wasted_bytes >= size_threshold_bytes
59            }
60            CompactionStrategy::Adaptive => {
61                // Combine multiple factors
62                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                // Never automatically compact
73                false
74            }
75        }
76    }
77
78    /// Adaptive evaluation combining multiple factors
79    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        // Factor 1: Fragmentation (40% weight)
91        if fragmentation > 0.0 {
92            let frag_ratio = fragmentation / fragmentation_threshold;
93            score += frag_ratio.min(1.0) * 0.4;
94        }
95
96        // Factor 2: Wasted space (30% weight)
97        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        // Factor 3: Time since last compaction (30% weight)
103        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            // Never compacted, give full weight
108            score += 0.3;
109        }
110
111        // Trigger if score exceeds threshold
112        score >= 0.7
113    }
114
115    /// Update last compaction time
116    pub fn record_compaction(&mut self) {
117        self.last_compaction = Some(SystemTime::now());
118    }
119
120    /// Get time since last compaction
121    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    /// Calculate priority for compaction (0.0 - 1.0, higher = more urgent)
127    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                // Priority based on time
136                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                // Priority based on fragmentation
142                fragmentation
143            }
144            CompactionStrategy::SizeBased => {
145                // Priority based on wasted space
146                let size_gb = wasted_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
147                (size_gb / 10.0).min(1.0) // Normalize to 10GB max
148            }
149            CompactionStrategy::Adaptive => {
150                // Weighted combination
151                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        // Should compact if enough time has passed
173        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        // Should not compact if not enough time
183        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        // Should compact if fragmentation exceeds threshold
198        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        // Should not compact if below threshold
208        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        // Should compact if wasted space exceeds threshold
223        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        // Should not compact if below threshold
233        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        // Should compact with high fragmentation and time
248        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        // Should not compact with low values
258        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        // Should never auto-compact
273        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}