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 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}