<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Fsync on MongrelDB</title><link>https://www.mongreldb.com/articles/tags/fsync/</link><description>Recent content in Fsync on MongrelDB</description><image><title>MongrelDB</title><url>https://www.mongreldb.com/logo.png</url><link>https://www.mongreldb.com/logo.png</link></image><generator>Hugo</generator><language>en-US</language><lastBuildDate>Fri, 17 Jul 2026 09:30:00 -0500</lastBuildDate><atom:link href="https://www.mongreldb.com/articles/tags/fsync/index.xml" rel="self" type="application/rss+xml"/><item><title>WAL Fast-Commit: One fsync, One Transaction</title><link>https://www.mongreldb.com/articles/2026/07/wal-fast-commit-one-fsync-one-transaction/</link><pubDate>Fri, 17 Jul 2026 09:30:00 -0500</pubDate><guid>https://www.mongreldb.com/articles/2026/07/wal-fast-commit-one-fsync-one-transaction/</guid><description>MongrelDB commits a transaction with a single fsync on the WAL, because the WAL is the only part of durable storage that has to be synchronous.</description><content:encoded><![CDATA[<p>Durability in a database usually looks like a wall of fsyncs, and every fsync is a synchronous disk round-trip that your application pays whether the row is large or small, whether the table is hot or cold, and whether the query touched one page or fifty. The observation that matters is that only one of those writes actually has to survive a crash for the whole transaction to be recoverable, and everything else can follow later; that is the entire idea behind write-ahead logging, and MongrelDB pushes it to the extreme by making the WAL append the only synchronous write in the commit path.</p>
<h2 id="what-one-fsync-buys-you">What one fsync buys you</h2>
<p>A transaction in MongrelDB commits when its records are appended to the WAL and that WAL segment is fsync&rsquo;d to disk, full stop. The data pages, index pages, and column metadata are updated in memory first and flushed to disk afterwards by a background writer, but they do not block the commit. If the process crashes right after the fsync, recovery replays the WAL from the last checkpoint and reconstructs the same pages; if the process crashes before the fsync, the transaction never happened. There is no intermediate state where the database is confused, because the WAL is the only source of truth that matters at crash time.</p>
<p>The latency win is straightforward. A single fsync on a sequential WAL append is one disk seek on a rotating drive or one flush on an SSD, and on modern NVMe it is typically sub-millisecond. Writing and fsyncing the actual data pages would mean one or more random writes per modified page, each with its own rotational latency or flash write-amplification cost, and those costs multiply quickly on a transaction that touches many rows or many indexes. By separating &ldquo;must survive crash&rdquo; from &ldquo;nice to have on disk,&rdquo; the commit path stays flat even when the transaction is large.</p>
<p>Throughput benefits too, because group commit can share one fsync across multiple transactions. When several transactions are ready to commit at the same time, MongrelDB batches their WAL records into the same segment and pays one fsync for the batch instead of one per transaction. The published <code>cargo bench -p mongreldb-core --bench write_path</code> numbers show group commit at roughly 686 µs for 1,000 rows, which is the fsync cost amortized across many writers; the single-row no-fsync <code>put</code> is around 618 ns, and the durable <code>commit</code> is around 6.79 µs, because that measurement is the fsync-bound path. The difference between a single-row commit and a group commit is how many transactions ride inside that single disk barrier.</p>
<h2 id="what-the-rest-of-the-storage-layer-does-asynchronously">What the rest of the storage layer does asynchronously</h2>
<p>Data pages are written out by a background thread that watches dirty-page counts and checkpoint intervals, and the rule it follows is simple: keep the WAL from growing forever, but never let a page reach disk before the WAL record that describes its change. That ordering is what makes recovery possible; if a data page were written before its WAL record, a crash between the two would leave an updated page with no log to explain it. The background writer respects the log-sequence-number ordering, so pages are flushed only after their covering WAL records are durable.</p>
<p>Checkpoints are the mechanism that bounds recovery time. A checkpoint marks a point in the WAL before which all dirty pages are known to be on disk, which means recovery only has to replay from the last checkpoint rather than from the beginning of time. MongrelDB checkpoints on a configurable interval and on dirty-page thresholds, and the checkpoint itself is a lightweight metadata write; the heavy work of flushing pages happens continuously in the background, so checkpoint stalls are small.</p>
<h2 id="the-tradeoffs-that-come-with-this-design">The tradeoffs that come with this design</h2>
<p>Recovery time is the obvious one. Because data pages are not necessarily on disk at commit time, a crash means replaying every WAL record since the last checkpoint, and the length of that replay is the product of your write rate and your checkpoint interval. On a write-heavy workload with a long checkpoint interval, recovery can take seconds or minutes, which is fine for most applications but unacceptable for a system that needs sub-second restart. The fix is shorter checkpoints, but shorter checkpoints mean more foreground disruption and more write amplification; that is the standard WAL checkpoint tradeoff and MongrelDB does not pretend to have escaped it.</p>
<p>The second tradeoff is that the single fsync is still the latency floor for synchronous commits, and on a filesystem or storage stack with slow fsync behavior, that floor is visible. Some filesystems stall fsyncs while other writes drain, some virtualized disks lie about fsync completion, and some network-attached storage introduces multi-millisecond latency on every barrier. MongrelDB cannot make a bad disk fast, but it can make sure you only pay that cost once per transaction rather than once per page.</p>
<p>The third tradeoff is that group commit adds jitter. A transaction that is ready to commit just after a group has been dispatched waits for the next fsync window, and that wait is bounded by the group-commit timeout. For latency-sensitive single-row writes, the default timeout is short enough that the jitter is small; for throughput-bound workloads, the batching is the whole point. The settings are exposed if you need to tune them, but the default is biased toward the common case where one slow fsync is worse than a few microseconds of scheduling delay.</p>
<h2 id="why-this-is-the-modern-equivalent-of-an-old-pattern">Why this is the modern equivalent of an old pattern</h2>
<p>In the simpler embedded engines of that era, the way to get durability was often to open the data file with <code>O_SYNC</code> or <code>O_DIRECT</code> and pay the synchronous write cost on every page, because the code path was simpler and the gap between WAL and direct-write was smaller on the hardware available then. Modern equivalent is WAL fast-commit: the same crash-recovery guarantee, but the synchronous work is concentrated in one sequential append instead of scattered across random writes, and the storage layer can schedule the rest of the work when the disk is least busy. The guarantee did not change; the shape of the work did.</p>
]]></content:encoded></item></channel></rss>