Build Your Own PostgreSQL
Checkpoints and Crash Recovery
What You Will Learn
- Understand crashes are part of the design in a PostgreSQL-like database
- Understand the recovery starting line in a PostgreSQL-like database
- Understand recovery timeline in a PostgreSQL-like database
- Understand redo makes uneven pages coherent in a PostgreSQL-like database
Crashes are part of the design
A database crash is not exceptional in the design. It is expected. Power can fail after WAL reaches disk but before data pages do. The operating system can stop the process while dirty buffers are scattered across memory. Some pages on disk may reflect recent changes while others are still old. Crash recovery is the procedure that turns this uneven physical state back into a consistent database, and checkpoints keep that procedure bounded.
A checkpoint is a recorded point in the WAL stream where the database has flushed enough dirty data pages that recovery can start from there instead of from the beginning of time. It does not mean every later change is on disk. It means all changes before the checkpoint's redo position are safely represented in data files, so redo only needs to consider WAL after that point. The checkpoint record tells recovery where to begin.
The recovery starting line
During normal operation, the checkpointer writes dirty buffers to disk gradually and emits checkpoint records. This work has to be paced. Checkpoints that are too aggressive create heavy write bursts that compete with user queries. Checkpoints that are too infrequent force crash recovery to replay a large amount of WAL, which extends downtime. Checkpoint tuning balances steady-state I/O against restart time.
Recovery timeline
Redo makes uneven pages coherent
A system with no checkpoints turns recovery into a long, expensive process. If it keeps WAL forever, recovery has to replay from the first log record still needed to construct the database, and that span can be enormous. A system that checkpoints by stopping all work and flushing everything immediately has the opposite problem: recovery is simple, but foreground latency suffers. A practical database checkpoints concurrently, accepting extra complexity to avoid freezing the server.
Redo does most of the work in crash recovery. Recovery reads WAL records and reapplies changes when the corresponding data page does not already reflect them. Page log sequence numbers make redo idempotent. If a page already includes a change, recovery skips it; if not, recovery applies it. This matters because a crash may happen after some data pages were flushed and before others. Recovery has to be safe whether each page is old or new.
Commit records decide which transactions count as durable. If a transaction's commit record was flushed before the crash, recovery preserves its effects. If not, its changes must not become visible as committed work. In an MVCC design, uncommitted tuple versions may physically exist after recovery, but transaction status marks them aborted or in progress and later resolved as aborted. Cleanup removes them afterward. The recovered database can hold physical traces of failed work without exposing them as truth.
Torn pages and timelines
Full-page images solve a subtle storage problem. A page write can be torn, meaning only part of the page reaches disk before failure. If recovery then tries to redo changes against a corrupted page image, it may not have a valid base. By logging a full-page image the first time a page changes after a checkpoint, the database can restore the page to a known state before applying later records. This costs WAL space but protects against partial writes.
Recovery runs in phases. It locates the checkpoint, reads WAL, reconstructs transaction status, replays records, and reaches a consistent point. In archive or standby scenarios, it may continue applying WAL beyond local files. In point-in-time recovery, it stops at a requested target. The same basic machinery supports ordinary crash restart, backups, replicas, and time travel operations.
Checkpoints and WAL retention are closely connected. WAL before a checkpoint may still be needed for replication, backups, or point-in-time recovery even if crash recovery no longer needs it locally. The system has to know which consumers require old WAL segments. Removing them too early can break a standby or invalidate a backup chain. Keeping them forever fills disks. Retention is one more thing the system must coordinate.
Crash recovery succeeds when users never notice it. The server starts, replays, cleans up transaction state, and opens for connections with committed data intact. That quiet restart depends on every earlier layer: WAL records, buffer flush rules, page LSNs, checkpoints, tuple visibility, and transaction status.
Into catalogs
So far, the database has stored user data and maintained physical truth. It also stores the definitions that make user data intelligible: tables, columns, types, indexes, functions, and permissions. The next lesson turns to catalogs, the database's own self-description.
Recovery can restore bytes, but bytes alone do not make a relational system. The server also needs durable meaning: which bytes belong to which table, which columns exist, and which operations are valid. That meaning lives in metadata that has to be protected with the same seriousness as user rows.