If PostgreSQL is not deleting WAL (Write-Ahead Log) files, it usually means either archive_command is failing, replication slots are holding old segments, or max_wal_size and checkpoint settings are misconfigured. This guide explains the real reasons, how to diagnose each one, and the safe steps to reclaim disk space without corrupting your database.
What WAL Files Are and Why They Accumulate
WAL files are PostgreSQL’s transaction log. Every change to the database is first written to a WAL segment (normally 16 MB each) before the actual data pages are updated. This ensures crash recovery: if the server dies, PostgreSQL replays the WAL to restore consistency.
Under normal operation, PostgreSQL removes old WAL files after a checkpoint, once the data they protect has been flushed to disk. If files are not being deleted, something is telling PostgreSQL that those segments are still needed. The disk fills up, and the database eventually stops accepting writes to avoid data loss.
Checkpoint and wal_keep_size: The Basics
Checkpoints are moments when PostgreSQL flushes all dirty data pages to disk. After a checkpoint, WAL files older than the checkpoint are no longer needed for recovery, so they can be recycled or deleted. Two settings control how much WAL is retained:
- max_wal_size – soft limit on total WAL size that triggers a checkpoint (default 1 GB).
- wal_keep_size – minimum amount of WAL retained for standby servers (default 0, meaning no extra retained).
If you set wal_keep_size to a large value, PostgreSQL will keep that many WAL segments even if checkpoints complete. This is a common cause of unexpected WAL accumulation on a primary server that has no standby replicas.
Archive Command Failures: The Most Common Culprit
When archive_mode = on, PostgreSQL expects archive_command to successfully copy each WAL segment to a safe location. If the command returns a non-zero exit status, PostgreSQL keeps the WAL file and retries indefinitely. The file is never deleted because the archive is incomplete.
Typical reasons for failure:
- The backup directory path is wrong or not writable by the postgres user.
- The archive command uses a network mount that is temporarily unavailable.
- The command references a script that has a bug or missing executable permission.
Check the PostgreSQL log for messages like archive command failed with exit code 1. The failed file is named explicitly. Fix the underlying issue, and PostgreSQL will automatically archive the pending files and then remove them.
Replication Slots: Silent WAL Retention
Replication slots are a feature that guarantees a standby server never loses data. When a slot is created, PostgreSQL tracks the WAL position the standby has consumed. If the standby is disconnected or slow, the slot’s restart_lsn stays behind, and PostgreSQL retains all WAL from that point onward.
This is the most dangerous cause because it is invisible: no error messages, no archive failures, just a growing pg_wal directory. To diagnose:
- Run
SELECT slot_name, restart_lsn, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes FROM pg_replication_slots; - If
retained_bytesis large, the slot is holding WAL. - If the standby is permanently gone, drop the slot with
SELECT pg_drop_replication_slot('slot_name');
If the standby is still active, fix the replication lag instead. Never drop a slot for an active standby, or that standby will need to be rebuilt.
How to Diagnose Which Reason Applies
Follow this checklist in order:
- Check disk usage:
du -sh $PGDATA/pg_wal - Check for archive errors in the log:
grep "archive" $PGDATA/log/* - Check replication slots: query
pg_replication_slotsas above. - Check
wal_keep_sizeandmax_wal_size:SHOW wal_keep_size; SHOW max_wal_size; - Check if a long-running transaction is holding back the
oldest xmin:SELECT pid, state, xact_start FROM pg_stat_activity WHERE state = 'idle in transaction';
An idle-in-transaction session can also prevent WAL cleanup because the transaction’s snapshot must be able to see old versions of rows, which may require old WAL for recovery. Terminate such sessions if they are not needed.
Safe Ways to Reclaim Space
Once you have identified the cause, the fix is usually straightforward:
- Archive failures: Correct the command, then run
SELECT pg_switch_wal();to force a new segment and trigger a retry. - Replication slots: Drop unused slots or fix the standby lag.
- wal_keep_size too large: Lower it and restart, or run a checkpoint.
- Idle transactions: Terminate them with
pg_terminate_backend(pid).
After the fix, run CHECKPOINT; to trigger cleanup. PostgreSQL will recycle or delete the now-unneeded WAL files automatically. Do not manually delete files from pg_wal – that can corrupt the database and make recovery impossible.
Comparison: WAL Accumulation vs. Normal Growth
Normal WAL growth is expected during heavy write activity. A burst of inserts can temporarily produce many segments, but after a checkpoint, they should be removed. The key difference is persistence:
- Normal: WAL size rises and falls with workload; after idle periods, it returns to baseline.
- Problem: WAL size stays high or grows monotonically even when the server is idle.
If your WAL directory is 50 GB and the server has been quiet for hours, something is wrong. A healthy system typically keeps only a few GB of WAL, depending on checkpoint settings.
Frequently Asked Questions
Can I just delete old WAL files manually?
No. Manually removing WAL files can make the database unrecoverable if a crash occurs before the next checkpoint. Always fix the underlying cause and let PostgreSQL manage its own WAL lifecycle.
Why does archive_command keep failing even though the path is correct?
Check file permissions – the postgres user must have write access to the archive directory. Also test the command manually as the postgres user. A common mistake is using a relative path that works in your shell but not in the PostgreSQL environment.
What is the difference between wal_keep_size and replication slots?
wal_keep_size is a simple minimum amount of WAL retained for any standby. Replication slots are more precise – they track the exact position of each standby and retain only what is needed. Slots are safer but can cause unbounded growth if a standby is lost.
How do I know if a replication slot is causing the problem?
Run the query from the diagnosis section. If retained_bytes is large and the slot’s active column is false, the slot is orphaned. If it is active but the standby is lagging, you need to fix the standby network or disk speed.
Will lowering max_wal_size reduce WAL accumulation?
It can help indirectly by forcing more frequent checkpoints, but if the root cause is archive failure or a slot, lowering max_wal_size will not help. It may even hurt performance by causing too many checkpoints.
Does a long-running query prevent WAL deletion?
Yes, but only if it is in an idle-in-transaction state or a very long transaction. PostgreSQL must keep WAL to support the transaction’s snapshot for MVCC. Terminate or commit those transactions to allow cleanup.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Mac Start Up Automatically After Power Failure?
- Expose Self-Hosted Service Securely Without Cloudflare Tunnel
- Nano Delivery for Functional Foods: What It Can and Can’t Do4.00
- Disable Apple Watch Turn-by-Turn Vibration While Driving
- Convert Play FLAC in iTunes: Full Guide
- Soda Foundation Charter: What It Means