Sign in
TodayMapLearnPracticeReview
Library
15 MINexpertDatabases & Data SystemsNot started

Write-Ahead Log (WAL)

The Write-Ahead Log (WAL) is the durability mechanism at the heart of every transactional database. Before any change is applied to the data files, a record of that change is appended to the WAL and fsynced. If the database crashes, the WAL is replayed on restart to bring the data files back to a consistent state — committed transactions survive, uncommitted ones don't. The WAL also powers streaming replication, point-in-time recovery, and transactional outboxes.

Why this matters

Without the WAL, the database cannot make the Durability guarantee in ACID. A power loss mid-write would corrupt the data files with no way to recover. The WAL is also the foundation of every other major database feature — replication, backups, point-in-time recovery, change data capture, and transactional outbox patterns all flow from the WAL. Understanding it explains why `fsync` settings matter, why replication lag exists, and why 'commit returned' doesn't always mean 'safe.'

Prerequisites
  • Transactions & ACID
Related
  • Event Sourcing
  • Transactions & ACID
  • Replication
  • MVCC
Used in
  • Event Sourcing
Lesson

How it works

The Write-Ahead Log is, in concept, beautifully simple: before any change is made to the data files, write a record describing the change to a separate append-only log, and fsync that log. On crash, replay the log to reconstruct the data files.

This solves a fundamental storage problem: data files are large, structured, and randomly accessed. Writing a small update to a 1TB file is expensive — you have to seek to the right page, modify it, and write it back, ideally atomically. But random I/O with crash safety is hard.

The WAL turns this into sequential I/O, which is dramatically faster and crash-safe:

  1. Transaction begins; the engine stages changes in memory.
  2. As changes are made, they're appended to the WAL (an append-only file).
  3. On COMMIT, the WAL is fsynced — the changes are now durable on disk.
  4. The data files are updated later, in the background ('checkpointing').
  5. If a crash happens, the WAL is replayed on restart to bring the data files up to date.

The key insight: durability is guaranteed by the WAL, not by the data files. The data files can lag behind the WAL — that's fine, because the WAL is the source of truth that can always be replayed. As long as the WAL is fsynced before COMMIT returns, the transaction is durable.

The single most important setting in any transactional database is the fsync policy on the WAL. The guarantee chain is:

  • The application calls COMMIT.
  • The database appends the commit record to the WAL.
  • The database calls fsync() on the WAL file.
  • The kernel flushes the WAL to physical storage.
  • Only after fsync returns does the database tell the application 'committed.'

If fsync is honored, the transaction is durable — a power loss immediately after COMMIT returns will not lose the transaction. The WAL is on disk; on restart, it's replayed and the data files are brought up to date.

If fsync is disabled (e.g., synchronous_commit = off in PostgreSQL), COMMIT can return before the WAL is on disk. This is dramatically faster but trades durability for speed: a power loss can lose the last few committed transactions. Acceptable for some workloads (analytics, caches) — unacceptable for others (payments).

Related settings that affect durability:

  • wal_level (PostgreSQL) — minimal, replica, or logical. Determines how much info is in the WAL (more for replication, even more for logical decoding).
  • synchronous_standby_names — if set, COMMIT waits for at least one synchronous standby to confirm receipt of the WAL. Provides durability across machines.
  • full_page_writes — writes the full page image on first modification after a checkpoint, defending against partial page tears. Slightly slower but safer.

The lesson: tune the fsync / synchronous_commit knobs deliberately, knowing exactly which guarantee you're trading for speed.

WAL powers replication, PITR, and CDC

Once the WAL exists, a cascade of features fall out for free: (1) Streaming replication — a standby connects to the primary, receives WAL records over a socket, and replays them locally. Eventual consistency between primary and replica, with sub-second lag typically. (2) Point-in-time recovery (PITR) — keep WAL archives; restore a base backup and replay WAL up to a chosen timestamp. Recover from DROP TABLE mistakes. (3) Change Data Capture (CDC) — Debezium reads the WAL (or binlog in MySQL) and publishes every change as an event to Kafka, without placing load on the primary. This is the foundation of the outbox pattern, event sourcing, and CQRS read-model updates.

If every change is in the WAL, why do we need data files at all? Because the WAL is optimized for appends, not for queries. To answer SELECT * FROM users WHERE id = 42, you need an indexed, structured data file — not a sequential log.

The bridge between WAL and data files is the checkpoint:

  • Periodically (every few minutes by default), the database writes all dirty buffers from shared_buffers to the data files.
  • It records the WAL position (LSN — Log Sequence Number) at which the checkpoint occurred.
  • WAL records older than the checkpoint LSN are no longer needed for crash recovery and can be recycled.

On crash recovery:

  1. The database reads the last checkpoint record from the WAL.
  2. It replays the WAL forward from that point, applying each change to the data files.
  3. Uncommitted transactions (those without a commit record) are rolled back via the undo log (in PostgreSQL, the abort is recorded in the WAL itself).

The checkpoint bounds recovery time: you only need to replay WAL from the last checkpoint. If checkpoints are far apart, recovery takes longer; if too frequent, they slow down normal operation. PostgreSQL auto-tunes this with checkpoint_timeout (default 5 minutes) and max_wal_size.

This is also why pg_start_backup() and pg_stop_backup() for backups interact with the WAL — they create a checkpoint and ensure WAL archiving captures everything needed to make the backup consistent.

Different databases call the WAL by different names, but the concept is identical:

  • PostgreSQL — pg_wal/ (formerly pg_xlog/). Records every change. wal_level=logical enables logical decoding for CDC.
  • MySQL/InnoDB — separate redo log (InnoDB's WAL) and binlog (MySQL server-level, used for replication). They're different logs and reconciled by an internal XA.
  • Oracle — redo log files, in groups for rotation. Archive logs (saved redo logs) enable PITR.
  • SQL Server — transaction log (.ldf file).
  • Cassandra — CommitLog, similar concept; SSTables are the equivalent of data files. Same LSM pattern.
  • RocksDB / LevelDB — also use a WAL, similar to Cassandra.

The architectural pattern is universal: sequential log for durability, structured files for queries, background process to bridge them. Whether you're debugging PostgreSQL recovery, MySQL replication, or Cassandra compaction, the underlying mechanism is the same.

This is also why the WAL is the foundation of the transactional outbox pattern: write your business data AND an event-to-publish row in the same transaction (so they're in the same WAL fsync), then a separate process reads the outbox table and publishes events to Kafka. Because the outbox write is in the same WAL as the business write, either both survive or neither does — atomicity across the database and the message queue, without 2PC.

Check yourself
interview

Why does the database fsync the WAL but not the data files on COMMIT?

Pick one answer.

Check yourself
interview

Your PostgreSQL primary crashes and restarts. The last checkpoint was 5 minutes ago. What happens to transactions committed in the last 5 minutes? To transactions that were in-progress at crash time?

Pick one answer.

Check yourself
solid

What's the trade-off when you set `synchronous_commit = off` in PostgreSQL?

Pick one answer.

Check yourself
interview

How does the WAL enable the transactional outbox pattern for reliable event publishing?

Pick one answer.

Engineering mental model

Mental model. Think of Write-Ahead Log (WAL) as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Write-Ahead Log (WAL) mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Write-Ahead Log (WAL), name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Write-Ahead Log (WAL).
Image unavailable. Original NO CAP systems visual for Write-Ahead Log (WAL).
Write-Ahead Log (WAL): a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = wal(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about Write-Ahead Log (WAL).

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Write-Ahead Log (WAL)

Change the variables below and predict what breaks first in Write-Ahead Log (WAL). The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Write-Ahead Log (WAL), start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Write-Ahead Log (WAL). What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Write-Ahead Log (WAL)?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Write-Ahead Log (WAL), traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Write-Ahead Log (WAL), and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

For Write-Ahead Log (WAL), start with access patterns rather than brand names. Identify the dominant reads/writes, data relationships, consistency requirements, partition key, hot keys and failure behavior before choosing a storage strategy.

Numerical sanity check

A rough capacity check: required write throughput ≈ peak writes/s × average record size. At 5,000 writes/s and 2 KB average payloads, the raw incoming data stream is about 10 MB/s before indexes, replication and overhead.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

A team proposes Write-Ahead Log (WAL) because it 'scales'. What workload characteristic would make that choice a poor fit?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Enables ACID durability — committed transactions survive crashes.
  • +Sequential appends are fast and crash-safe; data files can be updated lazily.
  • +Foundation for streaming replication, PITR, and CDC.
  • +Foundation for the transactional outbox pattern (atomic DB + event publishing).
  • +Recovery is automatic and correct on restart.
Cons
  • −Every commit pays an fsync latency cost (mitigated by group commit, NVMe, or async commit).
  • −WAL storage grows; must be archived or recycled after checkpoints.
  • −Synchronous replication adds round-trip latency to commits.
  • −Crash recovery time is bounded by the distance since the last checkpoint.
  • −WAL throughput can become the write bottleneck on high-write workloads.
Failure modes

How this breaks in production

  • fsync disabled or ignored by storage layer (e.g., lying SSDs) — silent data loss on power failure.
  • WAL disk full — database refuses new commits until space is freed.
  • Replication slot lag — replicas fall behind, WAL accumulates on primary.
  • Checkpoint spikes — aggressive checkpointing saturates I/O periodically.
  • Long recovery time — checkpoints too infrequent means long replay on crash.
  • Corrupted WAL — rare but catastrophic; the database cannot start.
Common mistakes

Don't fall into these traps

  • •Setting `fsync = off` for performance without understanding the durability loss.
  • •Disabling `synchronous_commit` on critical (e.g., payment) workloads.
  • •Not sizing WAL storage — a disk-full WAL halts all writes.
  • •Not archiving WAL — losing the ability to do point-in-time recovery.
  • •Leaving replication slots open on replicas that no longer consume — primary WAL grows indefinitely.
  • •Forgetting that lying storage (consumer-grade SSDs that lie about fsync) breaks durability silently.
Where you see it

Real systems using this

Every transactional database — PostgreSQL, MySQL, Oracle, SQL Server.Streaming replication setups — primary ships WAL to standbys.Point-in-time recovery (PITR) — archived WAL replayed up to a chosen timestamp.Change Data Capture (CDC) — Debezium reads WAL/binlog to stream changes.Cassandra and RocksDB commit logs — same concept, different storage model.
Teardowns

How real systems implement this

  • PostgreSQL — pg_wal/ directory holds WAL segment files (default 16MB each). Streaming replication sends WAL records over a socket; physical replicas replay them. Logical decoding (wal_level=logical) powers CDC tools like Debezium and AWS DMS.
  • MySQL / InnoDB — Has TWO logs: the InnoDB redo log (crash recovery) and the MySQL binlog (server-level replication). They're coordinated by an internal XA. The binlog is the foundation of statement-based and row-based replication in MySQL ecosystems.
  • Oracle — Redo log files in groups (online redo logs) cycle through; archive logs are saved copies used for PITR. Oracle's Data Guard uses redo log shipping for standby databases — same pattern as PostgreSQL streaming replication.
  • Debezium (CDC across many databases) — Reads the WAL (PostgreSQL), binlog (MySQL), redo log (Oracle), or transactional replication (SQL Server) to stream row-level changes to Kafka. No load on the application, no dual-write problem — the WAL is the source of truth.
Interview prompts

Practice saying it out loud

  • Q1What is a write-ahead log, and why does every transactional database have one?
  • Q2Walk through what happens when a PostgreSQL database crashes mid-write. How does it recover?
  • Q3What does `synchronous_commit = off` do, and when is it acceptable?
  • Q4How does the WAL enable the transactional outbox pattern?
  • Q5How does streaming replication use the WAL? What does 'replication lag' actually mean?
  • Q6What's the relationship between checkpoints, WAL size, and crash recovery time?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Databases & Data Systems reference
Reference
Databases & Data Systems reference
Reference
Databases & Data Systems reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

Event Sourcing