1#[derive(Clone, Debug, PartialEq)]
13pub enum WriteOp {
14 Put {
16 cid: String,
18 data_hash: u64,
20 size_bytes: u64,
22 },
23 Delete {
25 cid: String,
27 },
28 Update {
30 cid: String,
32 new_hash: u64,
34 },
35}
36
37#[derive(Clone, Debug)]
43pub struct BufferedEntry {
44 pub seq: u64,
46 pub op: WriteOp,
48 pub buffered_at_secs: u64,
50 pub flushed: bool,
52}
53
54#[derive(Clone, Debug, PartialEq)]
60pub struct FlushResult {
61 pub flushed_count: usize,
63 pub from_seq: u64,
65 pub to_seq: u64,
67 pub total_bytes: u64,
69}
70
71#[derive(Clone, Debug)]
77pub struct BufferConfig {
78 pub max_buffered_entries: usize,
80 pub max_buffered_bytes: u64,
82 pub flush_interval_secs: u64,
84}
85
86impl Default for BufferConfig {
87 fn default() -> Self {
88 Self {
89 max_buffered_entries: 1_000,
90 max_buffered_bytes: 64 * 1024 * 1024, flush_interval_secs: 5,
92 }
93 }
94}
95
96pub struct StorageWriteAheadBuffer {
110 pub entries: Vec<BufferedEntry>,
112 pub config: BufferConfig,
114 pub next_seq: u64,
116 pub total_buffered_bytes: u64,
118}
119
120impl StorageWriteAheadBuffer {
121 pub fn new(config: BufferConfig) -> Self {
127 Self {
128 entries: Vec::new(),
129 config,
130 next_seq: 0,
131 total_buffered_bytes: 0,
132 }
133 }
134
135 pub fn write(&mut self, op: WriteOp, now_secs: u64) -> u64 {
145 let seq = self.next_seq;
146 self.next_seq += 1;
147
148 if let WriteOp::Put { size_bytes, .. } = &op {
149 self.total_buffered_bytes = self.total_buffered_bytes.saturating_add(*size_bytes);
150 }
151
152 self.entries.push(BufferedEntry {
153 seq,
154 op,
155 buffered_at_secs: now_secs,
156 flushed: false,
157 });
158
159 seq
160 }
161
162 pub fn should_flush(&self, now_secs: u64) -> bool {
173 let pending: Vec<&BufferedEntry> = self.pending_entries();
174
175 if pending.len() >= self.config.max_buffered_entries {
176 return true;
177 }
178
179 if self.total_buffered_bytes >= self.config.max_buffered_bytes {
180 return true;
181 }
182
183 if let Some(oldest) = pending.first() {
184 let age = now_secs.saturating_sub(oldest.buffered_at_secs);
185 if age >= self.config.flush_interval_secs {
186 return true;
187 }
188 }
189
190 false
191 }
192
193 pub fn flush(&mut self, _now_secs: u64) -> FlushResult {
203 let mut flushed_count: usize = 0;
204 let mut from_seq: u64 = u64::MAX;
205 let mut to_seq: u64 = 0;
206 let mut total_bytes: u64 = 0;
207
208 for entry in self.entries.iter_mut().filter(|e| !e.flushed) {
209 entry.flushed = true;
210 flushed_count += 1;
211
212 if entry.seq < from_seq {
213 from_seq = entry.seq;
214 }
215 if entry.seq > to_seq {
216 to_seq = entry.seq;
217 }
218
219 if let WriteOp::Put { size_bytes, .. } = &entry.op {
220 total_bytes = total_bytes.saturating_add(*size_bytes);
221 }
222 }
223
224 self.total_buffered_bytes = 0;
225
226 FlushResult {
227 flushed_count,
228 from_seq,
229 to_seq,
230 total_bytes,
231 }
232 }
233
234 pub fn pending_entries(&self) -> Vec<&BufferedEntry> {
241 self.entries.iter().filter(|e| !e.flushed).collect()
242 }
243
244 pub fn replay_from(&self, seq: u64) -> Vec<&BufferedEntry> {
247 self.entries.iter().filter(|e| e.seq >= seq).collect()
248 }
249
250 pub fn trim_flushed(&mut self) {
259 self.entries.retain(|e| !e.flushed);
260 }
261
262 pub fn stats(&self) -> (usize, u64) {
264 let total = self.entries.len();
265 let unflushed = self.entries.iter().filter(|e| !e.flushed).count() as u64;
266 (total, unflushed)
267 }
268}
269
270#[cfg(test)]
275mod tests {
276 use super::*;
277
278 fn default_buf() -> StorageWriteAheadBuffer {
279 StorageWriteAheadBuffer::new(BufferConfig::default())
280 }
281
282 fn put_op(cid: &str, size: u64) -> WriteOp {
283 WriteOp::Put {
284 cid: cid.to_string(),
285 data_hash: 0xDEAD_BEEF,
286 size_bytes: size,
287 }
288 }
289
290 fn delete_op(cid: &str) -> WriteOp {
291 WriteOp::Delete {
292 cid: cid.to_string(),
293 }
294 }
295
296 fn update_op(cid: &str, hash: u64) -> WriteOp {
297 WriteOp::Update {
298 cid: cid.to_string(),
299 new_hash: hash,
300 }
301 }
302
303 #[test]
305 fn test_write_assigns_sequential_seqs() {
306 let mut buf = default_buf();
307 let s0 = buf.write(put_op("cid0", 100), 0);
308 let s1 = buf.write(put_op("cid1", 200), 0);
309 let s2 = buf.write(delete_op("cid0"), 0);
310 assert_eq!(s0, 0);
311 assert_eq!(s1, 1);
312 assert_eq!(s2, 2);
313 }
314
315 #[test]
317 fn test_next_seq_increments() {
318 let mut buf = default_buf();
319 assert_eq!(buf.next_seq, 0);
320 buf.write(put_op("a", 10), 0);
321 assert_eq!(buf.next_seq, 1);
322 buf.write(put_op("b", 20), 0);
323 assert_eq!(buf.next_seq, 2);
324 }
325
326 #[test]
328 fn test_put_increments_buffered_bytes() {
329 let mut buf = default_buf();
330 buf.write(put_op("a", 512), 0);
331 assert_eq!(buf.total_buffered_bytes, 512);
332 buf.write(put_op("b", 1024), 0);
333 assert_eq!(buf.total_buffered_bytes, 1536);
334 }
335
336 #[test]
338 fn test_delete_does_not_increment_bytes() {
339 let mut buf = default_buf();
340 buf.write(delete_op("a"), 0);
341 assert_eq!(buf.total_buffered_bytes, 0);
342 }
343
344 #[test]
346 fn test_update_does_not_increment_bytes() {
347 let mut buf = default_buf();
348 buf.write(update_op("a", 42), 0);
349 assert_eq!(buf.total_buffered_bytes, 0);
350 }
351
352 #[test]
354 fn test_should_flush_on_count() {
355 let cfg = BufferConfig {
356 max_buffered_entries: 3,
357 max_buffered_bytes: u64::MAX,
358 flush_interval_secs: u64::MAX,
359 };
360 let mut buf = StorageWriteAheadBuffer::new(cfg);
361 buf.write(put_op("a", 1), 0);
362 buf.write(put_op("b", 1), 0);
363 assert!(!buf.should_flush(0));
364 buf.write(put_op("c", 1), 0);
365 assert!(buf.should_flush(0));
366 }
367
368 #[test]
370 fn test_should_flush_on_bytes() {
371 let cfg = BufferConfig {
372 max_buffered_entries: usize::MAX,
373 max_buffered_bytes: 100,
374 flush_interval_secs: u64::MAX,
375 };
376 let mut buf = StorageWriteAheadBuffer::new(cfg);
377 buf.write(put_op("a", 60), 0);
378 assert!(!buf.should_flush(0));
379 buf.write(put_op("b", 40), 0);
380 assert!(buf.should_flush(0));
381 }
382
383 #[test]
385 fn test_should_flush_on_age() {
386 let cfg = BufferConfig {
387 max_buffered_entries: usize::MAX,
388 max_buffered_bytes: u64::MAX,
389 flush_interval_secs: 5,
390 };
391 let mut buf = StorageWriteAheadBuffer::new(cfg);
392 buf.write(put_op("a", 1), 100); assert!(!buf.should_flush(104)); assert!(buf.should_flush(105)); }
396
397 #[test]
399 fn test_should_flush_empty() {
400 let buf = default_buf();
401 assert!(!buf.should_flush(9999));
402 }
403
404 #[test]
406 fn test_flush_marks_entries_flushed() {
407 let mut buf = default_buf();
408 buf.write(put_op("a", 10), 0);
409 buf.write(delete_op("b"), 0);
410 buf.flush(1);
411 assert!(buf.entries.iter().all(|e| e.flushed));
412 }
413
414 #[test]
416 fn test_flush_result_count() {
417 let mut buf = default_buf();
418 buf.write(put_op("a", 10), 0);
419 buf.write(put_op("b", 20), 0);
420 buf.write(delete_op("c"), 0);
421 let result = buf.flush(1);
422 assert_eq!(result.flushed_count, 3);
423 }
424
425 #[test]
427 fn test_flush_result_seq_range() {
428 let mut buf = default_buf();
429 buf.write(put_op("a", 10), 0); buf.write(put_op("b", 20), 0); buf.write(delete_op("c"), 0); let result = buf.flush(1);
433 assert_eq!(result.from_seq, 0);
434 assert_eq!(result.to_seq, 2);
435 }
436
437 #[test]
439 fn test_flush_result_total_bytes() {
440 let mut buf = default_buf();
441 buf.write(put_op("a", 100), 0);
442 buf.write(delete_op("b"), 0);
443 buf.write(put_op("c", 200), 0);
444 buf.write(update_op("d", 7), 0);
445 let result = buf.flush(1);
446 assert_eq!(result.total_bytes, 300);
447 }
448
449 #[test]
451 fn test_flush_resets_buffered_bytes() {
452 let mut buf = default_buf();
453 buf.write(put_op("a", 512), 0);
454 assert_eq!(buf.total_buffered_bytes, 512);
455 buf.flush(1);
456 assert_eq!(buf.total_buffered_bytes, 0);
457 }
458
459 #[test]
461 fn test_pending_entries_after_flush_is_zero() {
462 let mut buf = default_buf();
463 buf.write(put_op("a", 10), 0);
464 buf.write(delete_op("b"), 0);
465 buf.flush(1);
466 assert_eq!(buf.pending_entries().len(), 0);
467 }
468
469 #[test]
471 fn test_pending_entries_partial_flush() {
472 let mut buf = default_buf();
473 buf.write(put_op("a", 10), 0); buf.write(put_op("b", 20), 0); buf.flush(1); buf.write(put_op("c", 30), 0); let pending = buf.pending_entries();
478 assert_eq!(pending.len(), 1);
479 assert_eq!(pending[0].seq, 2);
480 }
481
482 #[test]
484 fn test_replay_from() {
485 let mut buf = default_buf();
486 buf.write(put_op("a", 10), 0); buf.write(put_op("b", 20), 0); buf.write(delete_op("c"), 0); buf.flush(1);
490
491 let replayed = buf.replay_from(1);
492 assert_eq!(replayed.len(), 2);
493 assert_eq!(replayed[0].seq, 1);
494 assert_eq!(replayed[1].seq, 2);
495 }
496
497 #[test]
499 fn test_replay_from_zero_returns_all() {
500 let mut buf = default_buf();
501 buf.write(put_op("a", 10), 0);
502 buf.write(put_op("b", 20), 0);
503 buf.flush(1);
504 assert_eq!(buf.replay_from(0).len(), 2);
505 }
506
507 #[test]
509 fn test_trim_flushed_removes_flushed() {
510 let mut buf = default_buf();
511 buf.write(put_op("a", 10), 0); buf.write(put_op("b", 20), 0); buf.flush(1);
514 buf.write(put_op("c", 30), 0); buf.trim_flushed();
516 assert_eq!(buf.entries.len(), 1);
517 assert_eq!(buf.entries[0].seq, 2);
518 }
519
520 #[test]
522 fn test_trim_flushed_noop_when_nothing_flushed() {
523 let mut buf = default_buf();
524 buf.write(put_op("a", 10), 0);
525 buf.write(put_op("b", 20), 0);
526 buf.trim_flushed();
527 assert_eq!(buf.entries.len(), 2);
528 }
529
530 #[test]
532 fn test_stats_unflushed_count() {
533 let mut buf = default_buf();
534 buf.write(put_op("a", 10), 0); buf.write(put_op("b", 20), 0); buf.write(delete_op("c"), 0); let (total, unflushed) = buf.stats();
538 assert_eq!(total, 3);
539 assert_eq!(unflushed, 3);
540
541 buf.flush(1);
542 let (total2, unflushed2) = buf.stats();
543 assert_eq!(total2, 3);
544 assert_eq!(unflushed2, 0);
545 }
546
547 #[test]
549 fn test_stats_after_trim() {
550 let mut buf = default_buf();
551 buf.write(put_op("a", 10), 0);
552 buf.write(put_op("b", 20), 0);
553 buf.flush(1);
554 buf.write(put_op("c", 30), 0);
555 buf.trim_flushed();
556 let (total, unflushed) = buf.stats();
557 assert_eq!(total, 1);
558 assert_eq!(unflushed, 1);
559 }
560
561 #[test]
563 fn test_flush_empty_buffer_sentinels() {
564 let mut buf = default_buf();
565 let result = buf.flush(0);
566 assert_eq!(result.flushed_count, 0);
567 assert_eq!(result.from_seq, u64::MAX);
568 assert_eq!(result.to_seq, 0);
569 assert_eq!(result.total_bytes, 0);
570 }
571
572 #[test]
574 fn test_second_flush_is_empty() {
575 let mut buf = default_buf();
576 buf.write(put_op("a", 10), 0);
577 buf.flush(1);
578 let result2 = buf.flush(2);
579 assert_eq!(result2.flushed_count, 0);
580 }
581
582 #[test]
584 fn test_write_op_derives() {
585 let op1 = WriteOp::Put {
586 cid: "c1".to_string(),
587 data_hash: 1,
588 size_bytes: 64,
589 };
590 let op2 = op1.clone();
591 assert_eq!(op1, op2);
592 let _ = format!("{op1:?}");
593 }
594}