PostgreSQL's Write-Ahead Logging (WAL) is at the heart of its durability and crash recovery. If you've ever wondered how PostgreSQL ensures your data is safe even in the event of a crash, this post walks through the architecture, flow, and the actual source code that makes it work.
What is WAL?
WAL is a mechanism that ensures all changes to the database are first recorded in a log before being applied to the data files. This guarantees that even if the system crashes, PostgreSQL can recover to a consistent state.
The core idea: write the log first, apply the change second. If a crash happens between those two steps, recovery replays the log to reconstruct the correct state.
Key components
- WAL Buffers — in-memory staging area for WAL records before they hit disk
- WAL Files — on-disk files in
pg_wal/, each 16MB by default - WAL Writer — background process that flushes WAL buffers to disk
- Checkpointer — periodically flushes dirty data pages and records a checkpoint in WAL
- Archiver — optionally copies completed WAL files to an archive location
The write sequence
When a transaction commits, PostgreSQL follows this sequence:
- A change is initiated (INSERT, UPDATE, DELETE)
- A WAL record is created describing the change
- The record is placed in WAL buffers (
XLogInsert) - At commit, WAL is flushed to disk (
XLogFlush) - The data page change is applied
- On crash,
StartupXLOGreplays WAL records from the last checkpoint
Key source code locations
The WAL implementation lives in src/backend/access/transam/:
xlog.c— core WAL logic,XLogInsert(),XLogFlush(),XLogWrite()walwriter.c— the WAL writer background processxlogarchive.c— WAL archiving
Key structs: XLogRecPtr (a WAL position), XLogRecord (a single WAL record), XLogCtlData (shared memory control structure).
Relevant postgresql.conf parameters
| Parameter | Default | Purpose |
|---|---|---|
wal_level |
replica |
How much information is written to WAL |
wal_buffers |
-1 (auto) |
Size of WAL buffer in shared memory |
wal_writer_delay |
200ms |
How often the WAL writer flushes |
max_wal_size |
1GB |
Maximum size before a checkpoint is triggered |
archive_mode |
off |
Enable WAL archiving |
What I learned
Reading the PostgreSQL source alongside the documentation changes how you think about durability. The WAL code is surprisingly clean and well-commented for a system of its age. XLogInsert does less than you'd expect; most of the complexity is in the flush path and recovery.
If you want to explore the source: start with XLogInsert() in xlog.c, follow the call to XLogFlush(), then trace StartupXLOG() to see how recovery works.