Databases internals · Lesson 11 of 12 · 20 min

Storage Engine: WAL, Checkpoints and Crash Recovery

Why databases log before they write, how a commit becomes durable, what recovery replays, and how the same log drives replication.

✓ State the write-ahead rule and trace a commit✓ Explain checkpoints and what recovery redoes and discards✓ Compare synchronous and asynchronous log shipping
Say it in 30 seconds
  • log firstRule: log before data page
  • 500 → 50,000Commits a second: solo versus group of 100
  • 1 fsyncDisk wait on the commit path
Show what to say

“A database appends a small sequential log record before it changes a data page, and a commit is durable once that record is fsynced. Dirty pages are flushed lazily, a checkpoint bounds how much log recovery must replay, and recovery redoes committed work and discards the rest. The same log, shipped to replicas, drives replication.”

Trade-offs

  • Frequent vs rare checkpoints: frequent ones shorten recovery but add page-write load; rare ones leave a long replay.
  • Synchronous vs asynchronous replica: synchronous loses no commits on failover but waits on the network; asynchronous is fast and can lose the tail.
  • Strict fsync vs relaxed commit: fsync every commit is safest; relaxing it raises throughput and risks the last few milliseconds.

Bring it up before they ask

A full log disk or a stuck replica that stops log recycling stops all writes, so alert on log size and replication lag.

0 of 0 sections ticked
30-second answer

A database writes a small, sequential log record before it changes data pages, so a commit is durable once the log is flushed. After a crash, recovery replays the log from the last checkpoint, and the same log, shipped to other servers, is .

Why a database needs a log

Data pages are slow to write in place, so the database keeps hot ones in a buffer pool and flushes later. A committed change can exist only in RAM.

Read more · 84 words

The fix is the (WAL): before a change may reach a data page, a small record of it is appended to the log, and the commit is acknowledged once that record is on disk.

The write path

The only disk wait in a commit is the log flush (fsync), which caps commits per second. Group commit shares one fsync: at 2 ms, one at a time allows 500 a second, batches of 100 allow 50,000.

Press Play to watch it run · 4 steps

A commit changes the page in memory and appends a log record, and is acknowledged once the log is on disk. Data pages are flushed later. After a crash the log is replayed to rebuild what was only in memory.

Commit and crash recovery with a write-ahead logA commit changes the page in memory and appends a log record, and is acknowledged once the log is on disk. Data pages are flushed later. After a crash the log is replayed to rebuild what was only in memory.TransactionBuffer pooldirty pages in RAMWrite-ahead logsequential, fsyncData filespages on disk

Tap any box to see what it is and what it talks to.

  • Client
  • Cache
  • Object storage
  • Database
Open in playground
Text description
  1. UPDATE changes the page in memory. It is now dirty, but not on disk.
  2. A small log record describing the change is appended to the log.
  3. COMMIT: the log is flushed to disk (fsync), then the client is told it worked.
  4. Later, in the background, dirty pages are written to the data files. A checkpoint records how far this has got.

Checkpoints and crash recovery

A checkpoint flushes dirty pages and records a log position. Everything before it is on disk, so recovery starts there. Frequent checkpoints mean faster recovery but more write work.

Read more · 30 words

Recovery redoes committed changes the data files lack and ignores a torn tail with no commit record. Replay is harmless to repeat, so a crash during recovery is safe too.

// A toy database with a write-ahead log. `memory` is the buffer pool and is lost in a crash.
// `disk` holds data pages and is updated lazily. `log` is durable: appending to it is the fsync.
// A transaction counts as committed only when its commit marker is in the log.
export class Db {
  constructor() {
    this.disk = {};
    this.log = [];
    this.memory = {};
    this.nextTx = 1;
  }

  begin() {
    return { id: this.nextTx++, writes: [] }; // private until commit
  }

  set(tx, key, value) {
    tx.writes.push({ tx: tx.id, key, value });
  }

  // Log first, then memory. `tornAfter` simulates a crash while the log was being written.
  commit(tx, { tornAfter = null } = {}) {
    const records = tx.writes.slice(0, tornAfter ?? tx.writes.length);
    this.log.push(...records);
    if (tornAfter !== null) return this.crash(); // died before the commit marker was written
    this.log.push({ tx: tx.id, commit: true }); // <- the commit point
    for (const { key, value } of tx.writes) this.memory[key] = value; // data pages are only dirty in memory
  }

  // Flush every page and forget the log up to here: recovery will start from this point.
  checkpoint() {
    this.disk = { ...this.disk, ...this.memory };
    this.log = [];
  }

  crash() {
    this.memory = {}; // RAM is gone; disk and log survive
  }

  // Start from the data pages, then redo every transaction that has a commit marker.
  recover() {
    const committed = new Set(this.log.filter((r) => r.commit).map((r) => r.tx));
    this.memory = { ...this.disk };
    for (const r of this.log) if (!r.commit && committed.has(r.tx)) this.memory[r.key] = r.value;
  }

  get(key) {
    return this.memory[key];
  }
}

// Each commit waits for one fsync. Group commit lets many transactions share it.
export const commitsPerSecond = (fsyncMs, groupSize = 1) => (groupSize * 1000) / fsyncMs;

The replication log

The log is an ordered record of every change, so a applying the same records ends up with the same data.

Read more · 98 words
  • Physical shipping sends page-level records (Postgres streaming): exact copies.
  • Logical shipping sends row changes (MySQL binlog): works across versions and feeds change-data-capture.

A synchronous replica confirms before the commit is acknowledged: no loss on , but each commit waits on the network. An asynchronous one lags and can lose the last . More in the HLD path.

Interactive

Replication lag and read-your-writes

Writes go to the primary and reach the replicas a little later. Read from a replica inside that gap and you get the old value.

replicationYou (app)no reads yetPrimaryprice v1Replica 1price v1caught upReplica 2price v1caught up

The primary and both replicas hold price v1. Write v2, then read before the replicas catch up. Stale reads so far: 0 of 0.

Read-your-writes keeps your own reads on the primary for 5 s after a write (or until the replica has caught up), so your change never vanishes. Other users can still hit a lagging replica.

Recent reads (0)

Read-your-writes keeps your own reads on the primary for 5 s after a write (or until the replica has caught up), so your change never vanishes. Other users can still hit a lagging replica.

No reads yet.

What fsync really means

write() only hands data to the operating system's page .

Read more · 75 words

Durability needs fsync, and disks and controllers can still hold data in a volatile cache unless they are told to flush or are battery-backed.

  • Relaxed durability. Postgres synchronous_commit = off acknowledges before the flush: a crash can lose the last few milliseconds of commits but never corrupts the database.
  • Torn pages. A crash can leave an 8 KiB page half written. Engines guard against it with full-page images in the log or a doublewrite buffer.

Log-structured and copy-on-write engines

Some engines make the log the data.

Read more · 115 words

An LSM-tree appends to a log and sorted files and never updates in place, as in the indexes lesson. Copy-on-write (LMDB) write new pages and switch a root pointer atomically, so they need no separate . is an append-only log offered as a product.

Backups and point-in-time recovery

A base backup plus the archived log gives point-in-time recovery: restore the backup, then replay the log up to just before the bad DELETE.

Read more · 115 words

Keeping the log is what turns "we have last night's backup" into "we can restore to 14:32:05".

Follow-up questions

What is a write-ahead log and why does it make commits durable?

A record of each change is appended to a sequential log before the page changes, and the commit is acknowledged once it is flushed. After a crash the log can redo the change.

What happens when a database restarts after a crash?

It starts from the last checkpoint, replays the log to redo committed changes, discards with no commit record, then accepts connections.

Synchronous or asynchronous replication?

Synchronous loses no committed data on but adds network to every commit. Asynchronous is faster but can lose the last transactions.

Check yourself

Question 1 of 7

Score 2 out of 3 or better to mark this page complete.

1. What is the write-ahead rule?

Next: Last: how data is laid out in the first place, normalization against denormalization, and how joins run.