Lesson 04 · The promise machine

The write-ahead log: what COMMIT actually promises

A handwritten logbook or ledger on a podium where events are recorded chronologically

Lesson 1 left a question hanging: pages are modified in RAM and written to disk later: so what happens when the power dies in between? The answer is the oldest, most load-bearing idea in database engineering. Write-ahead logging: before any change touches a data page on disk, a record describing that change must already be safely on disk in an append-only log. The data files can then be lazy, torn, or stale: because the log can always reconstruct the truth. COMMIT stops meaning "the data is written" and starts meaning something better: "the story of this transaction is durably written, and stories can be replayed."

Crash it yourself

One UPDATE, four dangerous moments

Run UPDATE accounts SET bal=500 WHERE id=7 (old value: 300) step by step. At each step, you can pull the plug: then restart and watch recovery decide what survives. The two crash sites after step 3 are the ones that matter.

RAM (lost on crash)
buffer pool: page 42 → bal=300 (clean)
wal buffer: empty
disk · WAL (survives)
(empty)
disk · data file (survives)
page 42: bal=300

What you should have observed at the two decisive crash sites: crash after the WAL fsync but before anything else → recovery replays the log and the update survives: correctly, because the client was told "committed." Crash before the fsync → the update vanishes: also correctly, because the client never got its acknowledgment. The fsync is the exact instant the promise becomes real; everything else is choreography around it. The protocol's two rules, formally: (1) a page may not be written to disk before the log records that produced it (write-ahead); (2) a commit may not be acknowledged before its log records are fsynced. Everything else: when data pages actually get written: becomes a pure performance decision.

Why the log makes everything else fast

Here's the beautiful inversion: the WAL isn't a tax on performance, it's the source of it. Because the log holds the truth, data pages can be flushed lazily, in bulk, sorted by disk location, during quiet moments: instead of forcing a random 8KB page write on every commit, the engine does one small sequential append. Sequential I/O is the one thing every storage device loves (Lesson 1's ladder; Lesson 3's entire LSM thesis: and notice an LSM database is essentially a system that decided the log should just be the database). One problem remains: at high concurrency, even tiny fsyncs become the bottleneck, because an fsync costs the same whether it carries one commit or forty:

Group commit: the carpool lane

400 clients committing concurrently; each fsync takes 1ms regardless of payload. Compare one-fsync-per-commit against batching every fsync-worth of arrivals into one flush.

fsyncs / sec
:
commits / sec
:
avg commit latency
:
:

Group commit is why databases get faster per transaction under load: the batch fills up, and each fsync amortizes across more commits. Every serious engine does it (PostgreSQL's commit_delay, MySQL's binlog group commit), and the same trick reappears at every layer of the stack: Kafka producer batching, Raft leaders shipping entries in batches (replication course), even your network card coalescing interrupts. When the fixed cost per operation dominates, make the operation carry passengers.

Checkpoints: keeping recovery short

If the log is the truth, why keep data files at all? Because replaying a month of WAL takes a month. A checkpoint periodically flushes all dirty pages to disk and records "everything before LSN X is now reflected in the data files": so recovery only replays the tail since the last checkpoint. The tuning tension writes itself: frequent checkpoints → fast recovery, constant flush I/O competing with your workload; rare checkpoints → smooth running, long recovery (and every page carries the LSN from Lesson 1's page header so recovery knows exactly which records it can skip: that planted detail, cashed). Postgres's checkpoint_timeout/max_wal_size, MySQL's fuzzy checkpointing: same dial. And the full industrial-strength version of all this (handling crashes during recovery, undo for uncommitted changes that did reach disk) is the ARIES algorithm: three rules deep, decades wide.

The log escapes the building: once a database maintains an ordered, durable record of every change, everyone wants a copy. Physical replication ships WAL to replicas verbatim (replication course, lesson 5's wire formats); logical decoding turns it into the CDC streams feeding your search indexes and caches (field guide, lesson 7); point-in-time recovery replays archived WAL to any past second; Aurora's "the log is the database" design ships only WAL to storage. One structure, invented for crash safety in the 1970s, quietly became the backbone of modern data infrastructure.

Check yourself

1. A developer disables fsync ("fsync=off") in production because benchmarks showed 5× throughput. What did they actually trade away?

"fsync=off" converts the durability promise into a hope: writes sit in OS buffers for seconds before reaching disk. Kernel panic, power cut, hypervisor kill: anything in that window erases acknowledged commits. (Storage that lies about fsync: some consumer SSDs, some virtualized stacks: causes the same disease without the config change; serious operators test for it.) The honest fast paths: group commit, synchronous_commit=off for explicitly loss-tolerant tables, or batching at the application layer.

2. Why can the database acknowledge a commit when only the LOG record is on disk, while the actual data page still sits dirty in RAM: isn't that data "not saved"?

This is the WAL inversion that takes a moment to internalize: durability lives in the log; data files are an optimization for read speed and recovery brevity. Once the record is fsynced, the change is as safe as it will ever be. The lab's "crash after WAL fsync" run is the proof: the page was never written, and recovery restored bal=500 anyway.

3. Your database's p99 write latency spikes sharply every 5 minutes, like clockwork. First suspect?

Metronomic latency spikes → look for the metronome, and the checkpointer is the loudest one in the engine. The dirty-page backlog accumulated since the last checkpoint all hits the disk at once. Modern engines smear the flush across the whole interval precisely to flatten this spike: but an undersized max_wal_size can force early, urgent checkpoints that ignore the smearing. The general diagnostic habit: periodic pain = periodic process; find it in the config.