DBRaven
Failure Mode · capacity

WAL Saturation

critical

Summary

PostgreSQL WAL (Write-Ahead Log) generation rate exceeds wal_buffers flush capacity or downstream replica/WAL archive bandwidth, causing write transactions to stall waiting for WAL flush and replication lag to grow unboundedly.

Description

Every write operation in PostgreSQL generates WAL records. WAL is the authoritative record of all changes; a transaction cannot commit until its WAL records are durably flushed to disk. WAL generation saturates when the rate of write operations produces more WAL bytes per second than the WAL disk can absorb at the required latency.

The WAL pipeline has multiple potential saturation points. First, wal_buffers: shared memory buffers that hold WAL records before fsync. The default is -1, which auto-sizes wal_buffers to roughly 1/32 of shared_buffers, bounded between a minimum of 64kB and a maximum of one WAL segment (16MB by default); this auto-sizing default has been stable since PostgreSQL 9.1, and an explicit numeric setting overrides it. If transactions generate WAL faster than the background WAL writer flushes wal_buffers, transactions block waiting to write WAL. Second, WAL disk I/O: the WAL disk must sustain the WAL write bandwidth plus fsync IOPS. For a workload generating 500MB/s WAL (1 million 500-byte updates/second), the WAL device must sustain 500MB/s sequential write throughput: a GP3 EBS volume's max is 1,000 MB/s (125 MB/s baseline), so this workload can run but a GP2 volume would saturate. Third, WAL archiving: pg_wal_archive must copy WAL segments to S3, NFS, or another target faster than segments are generated. If archiving falls behind, pg_wal fills with unarchived segments and can exhaust disk space.

For streaming replication, WAL saturation manifests as replication lag: the replica WAL receiver cannot apply WAL faster than it arrives. At 500MB/s WAL generation and a replica with 100MB/s network bandwidth, replication lag grows at 400MB/s : 1.44TB/hour. The replica cannot catch up until the primary write rate drops below the replica network bandwidth.

The failure is particularly dangerous for CDC systems (Debezium, logical replication slots): a lagging replication slot retains WAL on the primary's pg_wal directory. If the consumer falls behind, pg_wal grows without bound until it fills the primary's disk: at which point the primary will not be able to generate new WAL and will halt all write operations.

PostgreSQL's WAL design is a single stream doing two jobs at once: the same records that let a crashed instance replay to a consistent state are also what streaming replication ships to standbys. MySQL/InnoDB splits this into two independent logs. The InnoDB redo log records physical page changes for crash recovery only, sized via innodb_redo_log_capacity (MySQL 8.0.30 and later; earlier versions size it via innodb_log_file_size times innodb_log_files_in_group). The binary log (binlog) is a separate, logical record of statements or row changes used for replication and point-in-time recovery, controlled independently (max_binlog_size, sync_binlog, binlog_expire_logs_seconds). These are not the same log: they are written by different code paths and each can independently become the bottleneck. A redo log undersized for the write rate forces synchronous checkpointing that stalls commits; a binlog that replicas or CDC consumers (Debezium reading the binlog, not the redo log) cannot keep up with grows lag the same way a lagging PostgreSQL replication slot does, but it is a different file with different retention controls than the redo log.

LSM engines take a narrower approach again. Cassandra's commit log, like RocksDB's write-ahead log, exists only to make the current unflushed memtable durable across a crash; once the memtable is flushed to an SSTable, the corresponding commit log segments are no longer needed for recovery and are recycled. Neither log is what carries data to other replicas. Cassandra replicates at write time through its own replication protocol (the coordinator writes to each replica node responsible for the key, per the keyspace's replication factor), not by shipping the commit log downstream, so commit log pressure and replication lag are separate concerns there in a way they are not under PostgreSQL's single-stream design.

Characteristics

Propagationfan out
Time to detectReplication lag detection: seconds with pg_stat_replication monitoring. WAL disk space accumulation: minutes if disk monitoring is active. Write stall from WAL buffer exhaustion: immediate (visible as write latency spike). WAL disk full causing complete write halt: 0 seconds (instant detection from write errors in application).
Blast radiusWAL saturation stalls all write transactions. If WAL disk is full, PostgreSQL halts all write operations until WAL is cleared: complete write unavailability. Replication lag affects all read replicas simultaneously. If CDC replication slots are lagging and filling pg_wal, even read-only queries may be affected if the data disk fills completely. Replica lag means all reads-from-replica return stale data, potentially hours stale.

Triggers

  • ·Bulk data import or ETL generating WAL at storage bandwidth ceiling (>200MB/s)
  • ·High UPDATE rate on wide tables with many indexes (high WAF producing large WAL records)
  • ·full_page_writes = on generating full 8KB page images to WAL after each checkpoint
  • ·Logical replication consumer falling behind, causing WAL retention to accumulate on disk
  • ·WAL archive destination slow or unavailable, causing pg_wal directory to fill

Detection Signals

queue depthdisk saturationreplication laglatency spike

Mitigation Strategies

Drop or pause idle replication slots immediatelycomplexity: low

An idle or lagging replication slot is the most common cause of unconstrained WAL accumulation. SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff( pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag FROM pg_replication_slots. Drop slots for consumers that have been inactive >24 hours.

Set max_slot_wal_keep_size to bound WAL retention per slotpreventscomplexity: low

In PostgreSQL 13+, max_slot_wal_keep_size limits how much WAL a lagging slot can retain. When the limit is reached, the slot is invalidated rather than filling the disk. Set to 10–50GB based on disk headroom. Trade-off: invalidated slots lose their position and must restart from scratch.

Throttle bulk write workloads to bound WAL generation ratecomplexity: low

For bulk imports or ETL jobs, add rate limiting (INSERT N rows, sleep M ms). At 10,000 rows/batch with 10ms sleep: ~1M rows/minute. This bounds WAL generation to a rate that replicas and WAL archive can sustain.

Provision separate high-throughput disk for pg_walcomplexity: medium

Place pg_wal on a dedicated NVMe volume or high-IOPS EBS io2 volume, separate from data files. WAL I/O is sequential; NVMe achieves 3–7 GB/s sequential writes, far exceeding EBS limits. Data I/O and WAL I/O no longer compete.

Recovery Steps

  1. 1.Check pg_replication_slots for lagging slots: any slot with >5GB retained WAL is an immediate risk
  2. 2.Drop lagging slots that belong to offline or slow consumers
  3. 3.If pg_wal is filling disk: pg_switch_wal() to force WAL segment rotation, then check archive_status
  4. 4.Throttle any active bulk write workload to reduce WAL generation rate
  5. 5.Monitor pg_stat_replication.write_lag on all replicas: should decrease after load reduction
  6. 6.Post-recovery: implement max_slot_wal_keep_size and add pg_wal disk space monitoring

Estimated recovery time: Seconds to minutes once lagging replication slots are dropped and bulk writes are throttled. Replica lag recovery depends on how far behind replicas have fallen (1GB lag at 100MB/s replication bandwidth takes 10s; 1TB lag takes ~3 hours). Disk space recovery from dropped slots is near-immediate.

Affected Systems

Patterns

write ahead log cdcevent sourcingoutbox patternread replica

Technologies

postgresqlmysqlcassandra

Basis

Precisely specified PostgreSQL WAL internals with measurable parameters; replication slot WAL accumulation is a well-documented production hazard. The MySQL redo log/binlog split and the Cassandra/RocksDB commit-log scope are standard documented mechanisms; exact InnoDB redo log sizing knobs are version-scoped in the text and should be re-checked against the deployed MySQL version.

Sources & Claims

PostgreSQL wal_buffers defaults to -1, which auto-sizes it to roughly 1/32 of shared_buffers, bounded between 64kB and one WAL segment (16MB by default); this auto-sizing default has been in place since PostgreSQL 9.1

pending

official documentation · PostgreSQL documentation, Write Ahead Log configuration (wal_buffers)

storage-engine-internals-spine batch 3

MySQL 8.0.30 replaced innodb_log_file_size and innodb_log_files_in_group with the single innodb_redo_log_capacity parameter for sizing the InnoDB redo log; earlier 8.0.x and 5.7 versions size the redo log via the two older parameters

pending

official documentation · MySQL 8.0 Reference Manual, InnoDB Redo Log

storage-engine-internals-spine batch 3

PostgreSQL uses a single WAL stream for both crash recovery and streaming replication, while MySQL/InnoDB uses two independent logs: the redo log for crash recovery and the binary log (binlog) for replication and point-in-time recovery, with independent retention controls

pending

official documentation · PostgreSQL WAL documentation; MySQL InnoDB Redo Log and Binary Log documentation

storage-engine-internals-spine batch 3

Cassandra's commit log provides crash recovery for data not yet flushed from the memtable and is not itself used for inter-node replication, which Cassandra performs via its own replication protocol at write time

pending

official documentation · Apache Cassandra documentation, Commit log

storage-engine-internals-spine batch 3

Run This Failure

Blast radius analysis for this failure mode within each scenario that carries it.

Related Architecture Knowledge

Inbound: affects this entity

Vulnerable ToWorkload
write heavy transactional
Grounded

Write-heavy transactional workloads generate high WAL volume that can saturate WAL writer throughput, fill the WAL buffer, and: in the extreme: cause write transactions to block waiting for WAL to be flushed to disk or consumed by replicas.

Tradeoffs

  • ·Increasing wal_buffers improves burst write performance but consumes more shared memory
  • ·synchronous_commit=off reduces WAL durability window (last 200-400ms of commits unconfirmed on crash)
  • ·Logical replication slots (CDC) hold WAL longer than streaming replication: additional WAL retention risk
Full relationship →

Used In Architecture Scenarios

Audit and Compliance Platformhigh

Financial Ledger

An append-only audit log architecture for capturing system actions: user operations, data access events, configuration changes, and financial operations: with cryptographic integrity chaining, immutable storage, and dual-path query serving. PostgreSQL stores the authoritative append-only event log in time-partitioned tables; ClickHouse serves historical aggregate queries and retention analytics; Kafka streams audit events in real time to SIEM integrations and security dashboards; Redis caches hot audit query results for compliance-facing read paths. No record is ever updated or deleted: only inserts are permitted. Each record includes a hash of the previous record, forming a tamper-evident chain verifiable without external tooling.

IoT Telemetry Ingestion Platformhigh

Write-Heavy Application

A high-rate device telemetry ingestion architecture designed for millions of devices emitting metrics at 1–60 second intervals. Kafka absorbs device writes as an ingestion buffer, decoupling device-facing ingest endpoints from the storage write path so that downstream storage pressure never propagates back to devices. TimescaleDB provides time-series storage with automatic chunk partitioning by time range, native compression, and continuous aggregate views for rollup queries. ClickHouse serves as the OLAP layer for device fleet analytics queries. Redis caches last-known device state (current readings per device) for real-time alerting queries that must not scan historical storage. Backpressure on the Kafka consumer side prevents storage write throughput from being overwhelmed by burst ingestion events from device reconnect storms.

Observability Platformhigh

Analytics Pipeline

A metrics, logs, and traces ingestion and query platform built to absorb the telemetry output of a production system fleet: including the telemetry volume spikes that accompany the incidents the platform is meant to detect. ClickHouse stores metrics data with automatic time-based rollup via continuous materialized views; TimescaleDB provides complementary time-series storage for high-cardinality alert evaluation; Kafka buffers the ingestion stream against downstream write pressure, decoupling ingest acceptance rate from storage write throughput; Elasticsearch serves log full-text search and structured field filtering; Redis caches dashboard query results and active alert state for sub-100ms alert evaluation latency. The alert engine evaluates threshold and anomaly rules against pre-computed materialized views, not raw data, to bound alert evaluation cost independent of ingestion volume.

Write-Heavy Transactional Platformhigh

Write-Heavy Application

A high-volume transactional write architecture anchored on PostgreSQL, where write throughput, durability guarantees, and audit completeness must coexist. The outbox pattern ensures reliable event publishing to Kafka without two-phase commit, and WAL-based CDC provides a durable change log that can reconstruct system state. Connection pooling via PgBouncer bounds connection overhead at the database layer.

WAL Saturation: DBRaven