Skip to content
All posts

What the Write-Ahead Log Question Is Really Testing

June 13, 2026·Read on Medium·

Most candidates say ‘crash recovery.’ The correct answer has three more layers.

The question lands somewhere in the middle of a senior backend interview. You have survived the distributed systems warmup and the indexing trap. Then the interviewer shifts:

“Walk me through what actually happens when PostgreSQL writes data to disk.”

Or sometimes: “What is the Write-Ahead Log and why does it exist?”

Both are the same question. And the thing being tested is not whether you can recite the phrase “durability guarantee.” It is whether you understand why the WAL format is designed the way it is, and what that design makes possible beyond crash recovery.

Most candidates explain one layer. The full answer has four.

What the Interviewer Is Actually Testing

The WAL question probes three things simultaneously.

First, whether you understand the fundamental durability contract at the storage level. Not “what does ACID stand for” but what the physical mechanism that enforces durability actually looks like.

Second, whether you know that WAL is not just a safety net. It is the foundation that replication and point-in-time recovery are built on. An engineer who only knows the crash recovery story has a mental model with unexplained gaps. They cannot reason about why a standby replica stays consistent, or how you restore a database to 11:47am on a Tuesday.

Third, and most valuable to the interviewer: whether you understand how WAL design decisions create production trade-offs. The wal_level configuration is not a dial that goes from "less safe" to "more safe." It controls how much information gets written, and each level unlocks a different set of capabilities. Getting this wrong in production costs you either disk space, replication, or both.

The Naive Answer

Most candidates give a version of this:

“The Write-Ahead Log is a log of changes that PostgreSQL writes before applying those changes to the actual data files. This way, if the server crashes, it can replay the log to recover any committed transactions that hadn’t been flushed to disk yet.”

That is correct. It is also roughly the first paragraph of the PostgreSQL documentation. The interviewer already knows you read it. They are waiting for what comes after.

Why It Is Incomplete

The naive answer describes crash recovery but does not explain the mechanism that makes it work, and it stops before any of the interesting consequences.

Three things are missing.

The explanation of WHY pre-writing works. It is not obvious. If you crash mid-transaction, why is the WAL safe to replay but the partially-written data pages are not? The answer involves full-page writes and the fact that WAL is written sequentially while data files are modified randomly. Most candidates glide past this.

The entire domain of replication. Streaming replication works by shipping WAL records to a standby, which replays them. The standby is not a copy of the primary’s data files; it is a continuously-replaying WAL follower. The WAL is not just a crash safety net. It is the replication protocol.

The domain of logical decoding, which is a different consumer of the same WAL stream. Tools like Debezium, pglogical and PostgreSQL’s built-in logical replication all work by reading WAL and translating its low-level storage records into row-level change events. This is only possible at wal_level=logical. And it has production consequences the naive answer says nothing about.

The Full Technical Answer

How WAL actually works

PostgreSQL’s WAL guarantee, stated precisely: a transaction is not committed until its WAL records have been flushed to durable storage. Not until the data pages are written. Not until the data pages are even touched.

When you execute an UPDATE, PostgreSQL writes a WAL record describing the change to an in-memory WAL buffer. On commit, those buffers are flushed to a WAL segment file on disk using fsync. Only after that flush succeeds does the client receive confirmation that the transaction committed. The modified data pages might still be in memory (PostgreSQL's buffer pool) and will be written to disk later, during a checkpoint.

This is the bargain. You pay for a sequential write to the WAL on every commit. You avoid random writes to data pages on every commit. Sequential writes are fast. Random writes are slow. Especially on spinning disk; less dramatically on NVMe, but the principle holds.

The WAL file is written sequentially, in 16MB segments stored in the pg_wal directory. Each record carries a Log Sequence Number (LSN), a 64-bit position in the WAL stream. The database knows exactly where it is and what has been replayed.

There is one more piece that most explanations skip: full_page_writes. On the first modification of any data page after a checkpoint, PostgreSQL writes the entire page content into the WAL record, not just the change. This matters because a page write can be torn. If power fails mid-write, you get half old page and half new page. The row-level WAL record cannot reconstruct a torn page on its own. The full page image guarantees recovery is possible even in that scenario. It is on by default. It makes WAL records significantly larger after every checkpoint. That is the trade-off: larger WAL in exchange for the ability to recover from partial writes.

The three capabilities WAL unlocks

After crash recovery, the same WAL stream enables three other things, and which ones you get depends on wal_level.

wal_level = minimal

  • Logs only what is needed to recover from a crash or immediate shutdown
  • Certain bulk operations log no row information at all, making them faster
  • No streaming replication, no PITR, no WAL archiving
  • PostgreSQL will refuse to start with this level if max_wal_senders is non-zero
  • Use case: throwaway clusters, batch loads you plan to discard, or benchmark environments

wal_level = replica (the default)

  • Logs enough data to support streaming binary replication and WAL archiving
  • Enables read-only queries on standby servers
  • Enables point-in-time recovery by replaying archived WAL to a target timestamp or LSN
  • This is where most production databases should live

wal_level = logical

  • Everything in replica, plus the additional information needed for logical decoding
  • Required for logical replication consumers (Debezium, pglogical, native logical replication)
  • Increases WAL volume, particularly on tables with heavy UPDATE and DELETE traffic
  • Requires a replication slot, which has its own production implications

The replication story at replica level: a standby server connects to the primary and receives WAL records as they are generated. It replays them continuously. The standby is not a point-in-time copy of the primary's data files; it is a process that has been replaying WAL since the moment it was first provisioned. pg_stat_replication on the primary shows where each standby sits in the WAL stream.

The PITR story: take a base backup, archive WAL continuously, and you can restore to any point in time within your WAL retention window. Specify recovery_target_time (a timestamp) or recovery_target_lsn (an exact WAL position) and PostgreSQL replays just that far. This is how you recover from a developer running DELETE FROM orders WHERE id > 0 at 14:32 on a Wednesday.

The logical decoding story: at logical level, WAL carries enough information to reconstruct row-level changes (INSERT, UPDATE, DELETE) for specific tables. This is what logical replication consumers read. Debezium reads it to stream changes to Kafka. PostgreSQL's native logical replication reads it to maintain subscribers on a different schema or a different cluster entirely. The WAL becomes a change event stream.

What happens during a checkpoint

Checkpoints are worth understanding because they connect WAL to disk writes. At each checkpoint (triggered by checkpoint_timeout, default 5 minutes, or when WAL reaches max_wal_size, default 1 GB), PostgreSQL flushes dirty data pages from the buffer pool to disk. Once a checkpoint completes, WAL records before that checkpoint's LSN are no longer needed for crash recovery. PostgreSQL recycles or removes them.

This is why WAL does not grow forever during normal operation. Checkpoints bound it. WAL grows between checkpoints; checkpoints release the oldest segments. The exception is replication slots.

Production Reality

A standard monitoring query worth running regularly:

-- Check replication lag across connected standbys
SELECT
application_name,
state,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)
) AS replay_lag_bytes,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication;

That shows you where each standby sits in the WAL stream. The scarier query is this one:

-- Check for replication slots holding WAL hostage
SELECT
slot_name,
active,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
) AS wal_retained
FROM pg_replication_slots;

Replication slots persist across crashes and know nothing about whether their consumer is running. If a logical replication consumer goes offline (Debezium goes down, a subscriber falls behind, a standby goes dark), the slot does not clean itself up. It holds its position in the WAL stream. PostgreSQL cannot delete WAL files that the slot might need. The pg_wal directory grows without bound.

In a high-write system, this can fill your disk in hours. In extreme cases, PostgreSQL will shut down to prevent transaction ID wraparound rather than continue accumulating WAL. The slot does not warn you. It holds, silently, until someone checks or until something breaks.

The fix is operational: monitor wal_retained on all replication slots. Alert when it crosses a threshold that gives you enough runway to respond. Drop slots you are not using. For logical replication consumers, test what happens when the consumer goes down for maintenance. Not in theory. In a staging environment, with actual write volume. That last scenario is the one every team skips, and it is always the one that pages someone at 3am.

There is also max_wal_size (default 1 GB), which controls how large WAL is allowed to grow during normal operation. On a busy server with a lagging standby or an idle replication slot, WAL will exceed this. PostgreSQL documents this as a soft ceiling explicitly. The hard ceiling is your disk.

How to Answer It in the Room

Lead with the physical mechanism, not the concept. Start here: “WAL guarantees that a transaction’s changes are flushed to the log before the client receives commit confirmation. Data pages get written later, during checkpoints. The key is that WAL is written sequentially, which is fast, while data page writes are random, which is slow.”

Then pivot immediately: “That’s crash recovery. But WAL is also the replication protocol. Streaming replication works by the standby replaying WAL records in real time, not by copying data files.”

Then mention the third layer: “At wal_level=logical, WAL carries enough additional information to reconstruct row-level changes. That is what logical replication consumers like Debezium or native logical replication read."

Then show you have thought about the production side: “The thing that catches teams is replication slots. If a logical replication consumer stops reading, the slot holds its WAL position indefinitely and pg_wal grows until someone notices."

Four moves. Crash recovery mechanism, streaming replication, logical decoding, production trap. The interviewer is not expecting you to recite every wal_* configuration parameter. They want to know you have thought past the textbook answer, ideally because you have had to think about it in production rather than just in preparation for the question.

The engineers who get this right are not the ones who memorized PostgreSQL internals from a blog post the night before. They are the ones who at some point watched a disk fill because nobody was monitoring replication slot lag. That experience is what the question is probing for. Show the scar.

Found this helpful?

If this article saved you time or solved a problem, consider supporting — it helps keep the writing going.

Originally published on Medium.

View on Medium
What the Write-Ahead Log Question Is Really Testing — Hafiq Iqmal — Hafiq Iqmal