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
165            .pop()
166            .unwrap_or_else(|| Vec::with_capacity(self.capacity))
167    }
168
169    /// Return buffer to pool for reuse
170    pub fn put(&mut self, mut buffer: Vec<T>) {
171        if self.pool.len() < self.max_pool_size {
172            buffer.clear();
173            self.pool.push(buffer);
174        }
175    }
176
177    /// Get current pool size
178    pub fn pool_size(&self) -> usize {
179        self.pool.len()
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_batch_writer_capacity() {
189        let mut writer = BatchWriter::new(3, Duration::from_secs(10));
190
191        writer.add(1);
192        writer.add(2);
193        assert!(!writer.should_flush());
194
195        writer.add(3);
196        assert!(writer.should_flush());
197
198        let items = writer.flush();
199        assert_eq!(items, vec![1, 2, 3]);
200        assert!(writer.is_empty());
201    }
202
203    #[test]
204    fn test_batch_writer_time_threshold() {
205        let mut writer = BatchWriter::new(100, Duration::from_millis(1));
206
207        writer.add(1);
208        std::thread::sleep(Duration::from_millis(2));
209
210        assert!(writer.should_flush());
211    }
212
213    #[test]
214    fn test_performance_metrics() {
215        let mut metrics = PerformanceMetrics::new();
216
217        metrics.record(Duration::from_millis(10));
218        metrics.record(Duration::from_millis(20));
219        metrics.record(Duration::from_millis(30));
220
221        assert_eq!(metrics.operations, 3);
222        assert_eq!(metrics.avg_duration(), Some(Duration::from_millis(20)));
223        assert_eq!(metrics.min_duration, Some(Duration::from_millis(10)));
224        assert_eq!(metrics.max_duration, Some(Duration::from_millis(30)));
225    }
226
227    #[test]
228    fn test_performance_metrics_throughput() {
229        let mut metrics = PerformanceMetrics::new();
230
231        for _ in 0..100 {
232            metrics.record(Duration::from_millis(10));
233        }
234
235        let throughput = metrics.throughput_per_sec();
236        assert!(throughput > 90.0 && throughput < 110.0); // ~100 ops/sec
237    }
238
239    #[test]
240    fn test_memory_pool() {
241        let mut pool: MemoryPool<i32> = MemoryPool::new(10, 5);
242
243        let buf1 = pool.get();
244        assert_eq!(buf1.capacity(), 10);
245
246        let mut buf2 = pool.get();
247        buf2.push(1);
248        buf2.push(2);
249
250        pool.put(buf2);
251        assert_eq!(pool.pool_size(), 1);
252
253        let buf3 = pool.get();
254        assert_eq!(buf3.len(), 0); // Should be cleared
255        assert_eq!(buf3.capacity(), 10);
256    }
257
258    #[test]
259    fn test_memory_pool_max_size() {
260        let mut pool: MemoryPool<i32> = MemoryPool::new(10, 2);
261
262        pool.put(vec![]);
263        pool.put(vec![]);
264        pool.put(vec![]); // Should be dropped
265
266        assert_eq!(pool.pool_size(), 2);
267    }
268
269    #[test]
270    fn test_batch_writer_len() {
271        let mut writer = BatchWriter::new(10, Duration::from_secs(10));
272
273        assert_eq!(writer.len(), 0);
274        writer.add(1);
275        assert_eq!(writer.len(), 1);
276        writer.add(2);
277        assert_eq!(writer.len(), 2);
278
279        writer.flush();
280        assert_eq!(writer.len(), 0);
281    }
282}