allsource_core/infrastructure/persistence/
performance.rs

1use std::time::{Duration, Instant};
2
3/// Performance optimization utilities
4///
5/// Inspired by SierraDB's focus on high-throughput operations.
6///
7/// # Design Principles
8/// - Batch operations for reduced overhead
9/// - Minimal allocations
10/// - Zero-copy where possible
11/// - Lock-free when feasible
12
13/// Batch writer for high-throughput ingestion
14///
15/// Accumulates items in a buffer and flushes when:
16/// - Buffer reaches capacity
17/// - Time threshold exceeded
18/// - Explicit flush called
19///
20/// # Example
21/// ```ignore
22/// let mut writer = BatchWriter::new(100, Duration::from_millis(100));
23/// writer.add(item)?;
24/// if writer.should_flush() {
25///     writer.flush()?;
26/// }
27/// ```
28pub struct BatchWriter<T> {
29    buffer: Vec<T>,
30    capacity: usize,
31    last_flush: Instant,
32    flush_interval: Duration,
33}
34
35impl<T> BatchWriter<T> {
36    /// Create new batch writer
37    ///
38    /// # Arguments
39    /// - `capacity`: Maximum items before auto-flush
40    /// - `flush_interval`: Maximum time between flushes
41    pub fn new(capacity: usize, flush_interval: Duration) -> Self {
42        Self {
43            buffer: Vec::with_capacity(capacity),
44            capacity,
45            last_flush: Instant::now(),
46            flush_interval,
47        }
48    }
49
50    /// Add item to buffer
51    pub fn add(&mut self, item: T) {
52        self.buffer.push(item);
53    }
54
55    /// Check if buffer should be flushed
56    pub fn should_flush(&self) -> bool {
57        self.buffer.len() >= self.capacity || self.last_flush.elapsed() >= self.flush_interval
58    }
59
60    /// Get current buffer size
61    pub fn len(&self) -> usize {
62        self.buffer.len()
63    }
64
65    /// Check if buffer is empty
66    pub fn is_empty(&self) -> bool {
67        self.buffer.is_empty()
68    }
69
70    /// Flush buffer and return items
71    pub fn flush(&mut self) -> Vec<T> {
72        self.last_flush = Instant::now();
73        std::mem::take(&mut self.buffer)
74    }
75
76    /// Get time since last flush
77    pub fn time_since_flush(&self) -> Duration {
78        self.last_flush.elapsed()
79    }
80}
81
82/// Performance metrics tracker
83#[derive(Debug, Clone)]
84pub struct PerformanceMetrics {
85    pub operations: u64,
86    pub total_duration: Duration,
87    pub min_duration: Option<Duration>,
88    pub max_duration: Option<Duration>,
89}
90
91impl PerformanceMetrics {
92    pub fn new() -> Self {
93        Self {
94            operations: 0,
95            total_duration: Duration::ZERO,
96            min_duration: None,
97            max_duration: None,
98        }
99    }
100
101    pub fn record(&mut self, duration: Duration) {
102        self.operations += 1;
103        self.total_duration += duration;
104
105        self.min_duration = Some(match self.min_duration {
106            Some(min) => min.min(duration),
107            None => duration,
108        });
109
110        self.max_duration = Some(match self.max_duration {
111            Some(max) => max.max(duration),
112            None => duration,
113        });
114    }
115
116    pub fn avg_duration(&self) -> Option<Duration> {
117        if self.operations == 0 {
118            None
119        } else {
120            Some(self.total_duration / self.operations as u32)
121        }
122    }
123
124    pub fn throughput_per_sec(&self) -> f64 {
125        if self.total_duration.as_secs_f64() == 0.0 {
126            0.0
127        } else {
128            self.operations as f64 / self.total_duration.as_secs_f64()
129        }
130    }
131}
132
133impl Default for PerformanceMetrics {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139/// Memory pool for reducing allocations
140///
141/// Pre-allocates buffers and reuses them to avoid allocation overhead.
142///
143/// # SierraDB Pattern
144/// - Reduces GC pressure
145/// - Improves throughput for high-frequency operations
146/// - Thread-local pools avoid contention
147pub struct MemoryPool<T> {
148    pool: Vec<Vec<T>>,
149    capacity: usize,
150    max_pool_size: usize,
151}
152
153impl<T> MemoryPool<T> {
154    pub fn new(capacity: usize, max_pool_size: usize) -> Self {
155        Self {
156            pool: Vec::new(),
157            capacity,
158            max_pool_size,
159        }
160    }
161
162    /// Get a buffer from the pool or create new one
163    pub fn get(&mut self) -> Vec<T> {
164        self.pool.pop().unwrap_or_else(|| Vec::with_capacity(self.capacity))
165    }
166
167    /// Return buffer to pool for reuse
168    pub fn put(&mut self, mut buffer: Vec<T>) {
169        if self.pool.len() < self.max_pool_size {
170            buffer.clear();
171            self.pool.push(buffer);
172        }
173    }
174
175    /// Get current pool size
176    pub fn pool_size(&self) -> usize {
177        self.pool.len()
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_batch_writer_capacity() {
187        let mut writer = BatchWriter::new(3, Duration::from_secs(10));
188
189        writer.add(1);
190        writer.add(2);
191        assert!(!writer.should_flush());
192
193        writer.add(3);
194        assert!(writer.should_flush());
195
196        let items = writer.flush();
197        assert_eq!(items, vec![1, 2, 3]);
198        assert!(writer.is_empty());
199    }
200
201    #[test]
202    fn test_batch_writer_time_threshold() {
203        let mut writer = BatchWriter::new(100, Duration::from_millis(1));
204
205        writer.add(1);
206        std::thread::sleep(Duration::from_millis(2));
207
208        assert!(writer.should_flush());
209    }
210
211    #[test]
212    fn test_performance_metrics() {
213        let mut metrics = PerformanceMetrics::new();
214
215        metrics.record(Duration::from_millis(10));
216        metrics.record(Duration::from_millis(20));
217        metrics.record(Duration::from_millis(30));
218
219        assert_eq!(metrics.operations, 3);
220        assert_eq!(metrics.avg_duration(), Some(Duration::from_millis(20)));
221        assert_eq!(metrics.min_duration, Some(Duration::from_millis(10)));
222        assert_eq!(metrics.max_duration, Some(Duration::from_millis(30)));
223    }
224
225    #[test]
226    fn test_performance_metrics_throughput() {
227        let mut metrics = PerformanceMetrics::new();
228
229        for _ in 0..100 {
230            metrics.record(Duration::from_millis(10));
231        }
232
233        let throughput = metrics.throughput_per_sec();
234        assert!(throughput > 90.0 && throughput < 110.0); // ~100 ops/sec
235    }
236
237    #[test]
238    fn test_memory_pool() {
239        let mut pool: MemoryPool<i32> = MemoryPool::new(10, 5);
240
241        let buf1 = pool.get();
242        assert_eq!(buf1.capacity(), 10);
243
244        let mut buf2 = pool.get();
245        buf2.push(1);
246        buf2.push(2);
247
248        pool.put(buf2);
249        assert_eq!(pool.pool_size(), 1);
250
251        let buf3 = pool.get();
252        assert_eq!(buf3.len(), 0); // Should be cleared
253        assert_eq!(buf3.capacity(), 10);
254    }
255
256    #[test]
257    fn test_memory_pool_max_size() {
258        let mut pool: MemoryPool<i32> = MemoryPool::new(10, 2);
259
260        pool.put(vec![]);
261        pool.put(vec![]);
262        pool.put(vec![]); // Should be dropped
263
264        assert_eq!(pool.pool_size(), 2);
265    }
266
267    #[test]
268    fn test_batch_writer_len() {
269        let mut writer = BatchWriter::new(10, Duration::from_secs(10));
270
271        assert_eq!(writer.len(), 0);
272        writer.add(1);
273        assert_eq!(writer.len(), 1);
274        writer.add(2);
275        assert_eq!(writer.len(), 2);
276
277        writer.flush();
278        assert_eq!(writer.len(), 0);
279    }
280}