Fix PostgreSQL WAL Files Filling the Disk on a Django Server

Fix PostgreSQL pg_wal growing until the disk is full on a Django database: find inactive replication slots, broken WAL archiving, or high max_wal_size, free space safely without deleting WAL by hand, and add alerts so it does not happen again.

Problem statement

The database disk alert fires, and du shows the culprit is not your tables:

$ sudo du -sh /var/lib/postgresql/17/main/pg_wal
183G    /var/lib/postgresql/17/main/pg_wal

When the disk fills, PostgreSQL stops accepting writes and may shut down with PANIC: could not write to file "pg_wal/xlogtemp.1234": No space left on device. Django requests then fail with OperationalError. WAL (write-ahead log) files are normally recycled after each checkpoint. They pile up only when something tells PostgreSQL to keep them: a replication slot nobody reads, a failing archive_command, or settings that keep far more than needed.

Why WAL is keptWrites happenWAL segments createdCheckpointold WAL recyclableSomething holds itslot or archiverpg_wal growsuntil disk fullPostgreSQL deletes old WAL by itself unless a slot or archiver still needs it.
  1. Check replication slots: SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) FROM pg_replication_slots;
  2. Drop slots that are inactive and no longer needed: SELECT pg_drop_replication_slot('old_slot');
  3. Check archiving: SELECT * FROM pg_stat_archiver; A rising failed_count means archive_command is failing.
  4. Never delete files from pg_wal by hand; it can corrupt the database.
  5. Set max_slot_wal_keep_size so a forgotten slot cannot fill the disk again.

Step-by-step solution

1. Look for a stuck replication slot

SELECT slot_name, slot_type, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots
ORDER BY pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) DESC;

A slot with active = false and a large retained value is the usual cause. Typical owners: a replica that was deleted without dropping its slot, a logical replication subscription from a finished migration, or a change data capture tool (Debezium, Airbyte) that stopped.

2. Drop slots that are no longer used

Confirm nobody needs the slot, then drop it:

SELECT pg_drop_replication_slot('migration_to_new_server');

PostgreSQL removes the retained WAL at the next checkpoint. Run CHECKPOINT; to make it happen now. If the slot belongs to a live replica that is just behind, fix the replica instead; dropping its slot forces you to rebuild it.

find the holderWhat you seeCause and fixinactive slot, large retainedforgotten replica orCDC; drop the slotpg_stat_archiver failed_count uparchive_command failing;fix target or credsactive slot, lag growingreplica too slow or down; repair itno slots, archiver finelarge max_wal_size orwal_keep_size; lower itFind which mechanism retains WAL before freeing anythingdjango-deployment.com
Each cause has a different safe fix; deleting files by hand is never one of them.

3. Fix a failing archive command

With archive_mode = on, PostgreSQL keeps every WAL segment until archive_command reports success:

SELECT archived_count, failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;

Look at the database log for the command's error, such as expired cloud credentials for WAL-G or pgBackRest, a full backup bucket, or a missing directory. Once the command succeeds again, the backlog archives and is recycled. If you no longer need archiving, set archive_mode = off (requires a restart), but understand that this ends point-in-time recovery.

4. Free space in an emergency

If the disk is already full and PostgreSQL is down:

  • Free other space on the volume first: old logs, core dumps, package caches.
  • Grow the volume (cloud disks can usually be resized online), then extend the file system.
  • Start PostgreSQL, then drop the slot or fix archiving as above.

Do not delete files in pg_wal, and do not use pg_resetwal unless you are restoring from backup anyway. Both can lose committed data.

5. Prevent a repeat

# postgresql.conf (PostgreSQL 13+)
max_slot_wal_keep_size = 50GB

With this limit, a slot that falls too far behind is invalidated instead of filling the disk. The replica then needs rebuilding, which is a much smaller problem than a stopped primary. Add alerts on disk usage, on pg_replication_slots retained size, and on pg_stat_archiver.failed_count.

keep pg_wal boundedmax_slot_wal_keep_size setcaps slot retentionAlert on retained WAL per slotpg_replication_slotsAlert on archiver failurespg_stat_archiverDrop slots when retiring replicaspart of runbookDisk usage alert at 80 percenttime to actOne capped setting and three alerts prevent nearly every incidentdjango-deployment.com
Most WAL incidents start weeks earlier with a slot nobody noticed.

Explanation

Every change to the database is written to WAL before it reaches the table files. PostgreSQL normally keeps only enough WAL to recover from a crash since the last checkpoint, bounded by max_wal_size, and recycles the rest. Replication slots and archiving change that rule on purpose: a slot guarantees its consumer can resume from where it stopped, and archiving guarantees each segment reaches the backup store. Both guarantees mean "keep the file until I say so". If the consumer disappears, the files are kept forever.

Edge cases / notes

  • Managed databases. RDS and Cloud SQL handle archiving themselves, but a logical replication slot you created can still fill storage. RDS exposes OldestReplicationSlotLag in CloudWatch.
  • Bulk loads. Large imports or index builds generate WAL quickly. Spread them out or temporarily raise disk headroom.
  • Separate volume. Putting pg_wal on its own volume protects table data, but the database still stops when that volume fills.

Next steps

FAQ

Can I delete old files from pg_wal?

No. PostgreSQL may still need them for crash recovery or replicas, and removing them can corrupt the cluster. Remove the reason they are retained instead.

Why did WAL grow after my database migration finished?

Logical replication slots created for the migration stay until dropped. Drop the slot on the old server once the move is complete.

Does VACUUM FULL free WAL space?

No. It rewrites tables and generates more WAL. It does nothing for retained segments.

What size should max_slot_wal_keep_size be?

Enough to cover a replica's normal maintenance downtime, while leaving free disk. Many teams use 20 to 30 percent of the volume.

Keep going