tuwunel_database/cork.rs
1//! Write-ahead-log coalescing via a scoped cork guard.
2//!
3//! Each `Map` insert or remove flushes RocksDB's write-ahead log to the OS
4//! immediately after the mutation. "Corking" suppresses that per-write flush:
5//! while one or more `Cork` guards are live, WAL records accumulate in the
6//! in-memory buffer and reach the OS in a single coalesced batch, trading a
7//! syscall per write for one flush per burst.
8//!
9//! Corking is purely a backend write-buffering optimization. It does not change
10//! application logic and has no observable effect on the database API. A write
11//! enters the memtable synchronously within the insert or remove call itself,
12//! so reads return the new value whether or not a cork is held; the cork
13//! governs only when WAL bytes reach the OS (the crash-durability window and
14//! the flush syscall count), never what any reader or caller observes.
15//!
16//! Corks are reference-counted on the `Engine`. `Cork::new` raises the count
17//! and `Drop` lowers it, so a guard's scope delimits the coalescing window;
18//! nested guards compose, and per-write flushing resumes only when the last one
19//! drops.
20
21use std::sync::Arc;
22
23use tuwunel_core::error;
24
25use crate::{Database, Engine};
26
27/// Scoped guard that coalesces write-ahead-log flushes for its lifetime.
28///
29/// Obtain one from `Database::cork`, `Database::cork_and_flush`, or
30/// `Database::cork_and_sync`, hold it across a burst of writes, and drop it to
31/// restore per-write flushing. The `flush` and `sync` variants additionally
32/// push the buffered WAL out when the guard drops, advancing durability timing
33/// only.
34#[clippy::has_significant_drop]
35pub struct Cork {
36 engine: Arc<Engine>,
37
38 /// Flush the WAL buffer to the OS when the guard drops.
39 flush: bool,
40
41 /// Sync (fsync) the WAL to disk when the guard drops; implies a flush.
42 sync: bool,
43}
44
45impl Database {
46 /// Open a coalescing window without forcing a flush when it closes.
47 ///
48 /// Per-write WAL flushing is suppressed for the guard's lifetime; the
49 /// buffered records are left for the next uncorked write (or RocksDB) to
50 /// flush. Use when the burst need not be durable at any particular point.
51 #[inline]
52 #[must_use]
53 pub fn cork(&self) -> Cork { Cork::new(&self.engine, false, false) }
54
55 /// Open a coalescing window that flushes the WAL to the OS on drop.
56 ///
57 /// Behaves like `cork`, but the accumulated WAL is pushed to the OS
58 /// (without an fsync) as the guard drops, bounding the buffered window to
59 /// the burst.
60 #[inline]
61 #[must_use]
62 pub fn cork_and_flush(&self) -> Cork { Cork::new(&self.engine, true, false) }
63
64 /// Open a coalescing window that syncs the WAL to disk on drop.
65 ///
66 /// Behaves like `cork_and_flush`, but the WAL is fsynced as the guard
67 /// drops, so the burst is durable against power loss once the guard has
68 /// gone.
69 #[inline]
70 #[must_use]
71 pub fn cork_and_sync(&self) -> Cork { Cork::new(&self.engine, true, true) }
72}
73
74impl Cork {
75 /// Raise the engine's cork count and capture the on-drop flush policy.
76 #[inline]
77 pub(super) fn new(engine: &Arc<Engine>, flush: bool, sync: bool) -> Self {
78 engine.cork();
79 Self { engine: engine.clone(), flush, sync }
80 }
81}
82
83impl Drop for Cork {
84 /// Lower the cork count, then flush and/or sync the WAL per the policy.
85 fn drop(&mut self) {
86 self.engine.uncork();
87
88 if self.flush {
89 self.engine
90 .flush()
91 .inspect_err(|error| error!(%error, "Failed to flush the write-ahead log."))
92 .ok();
93 }
94
95 if self.sync {
96 self.engine
97 .sync()
98 .inspect_err(|error| error!(%error, "Failed to sync the write-ahead log."))
99 .ok();
100 }
101 }
102}