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 crate::{Database, Engine};
24
25/// Scoped guard that coalesces write-ahead-log flushes for its lifetime.
26///
27/// Obtain one from `Database::cork`, `Database::cork_and_flush`, or
28/// `Database::cork_and_sync`, hold it across a burst of writes, and drop it to
29/// restore per-write flushing. The `flush` and `sync` variants additionally
30/// push the buffered WAL out when the guard drops, advancing durability timing
31/// only.
32#[clippy::has_significant_drop]
33pub struct Cork {
34 engine: Arc<Engine>,
35
36 /// Flush the WAL buffer to the OS when the guard drops.
37 flush: bool,
38
39 /// Sync (fsync) the WAL to disk when the guard drops; implies a flush.
40 sync: bool,
41}
42
43impl Database {
44 /// Open a coalescing window without forcing a flush when it closes.
45 ///
46 /// Per-write WAL flushing is suppressed for the guard's lifetime; the
47 /// buffered records are left for the next uncorked write (or RocksDB) to
48 /// flush. Use when the burst need not be durable at any particular point.
49 #[inline]
50 #[must_use]
51 pub fn cork(&self) -> Cork { Cork::new(&self.engine, false, false) }
52
53 /// Open a coalescing window that flushes the WAL to the OS on drop.
54 ///
55 /// Behaves like `cork`, but the accumulated WAL is pushed to the OS
56 /// (without an fsync) as the guard drops, bounding the buffered window to
57 /// the burst.
58 #[inline]
59 #[must_use]
60 pub fn cork_and_flush(&self) -> Cork { Cork::new(&self.engine, true, false) }
61
62 /// Open a coalescing window that syncs the WAL to disk on drop.
63 ///
64 /// Behaves like `cork_and_flush`, but the WAL is fsynced as the guard
65 /// drops, so the burst is durable against power loss once the guard has
66 /// gone.
67 #[inline]
68 #[must_use]
69 pub fn cork_and_sync(&self) -> Cork { Cork::new(&self.engine, true, true) }
70}
71
72impl Cork {
73 /// Raise the engine's cork count and capture the on-drop flush policy.
74 #[inline]
75 pub(super) fn new(engine: &Arc<Engine>, flush: bool, sync: bool) -> Self {
76 engine.cork();
77 Self { engine: engine.clone(), flush, sync }
78 }
79}
80
81impl Drop for Cork {
82 /// Lower the cork count, then flush and/or sync the WAL per the policy.
83 fn drop(&mut self) {
84 self.engine.uncork();
85 if self.flush {
86 self.engine.flush().ok();
87 }
88 if self.sync {
89 self.engine.sync().ok();
90 }
91 }
92}