Skip to main content

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}