Disk usage is at ninety percent and climbing. Someone deleted several million old rows and the free space did not change. This surprises people the first time, but it is expected behaviour: in Postgres a delete marks rows as dead rather than removing them, and reclaiming the space is a separate operation with real trade-offs.
01Deletes create dead tuples, not free space
Postgres uses multi-version concurrency control, so an update writes a new row version and a delete marks the existing version as no longer visible. Transactions already running may still need the old version, so the space cannot be released immediately. What you get after a large delete is a table the same size on disk containing a great deal of dead space.
Autovacuum marks that space reusable for future inserts in the same table, which stops growth but does not shrink the file. The operating system continues to see the original allocation, which is why the disk graph does not move.
| Operation | Effect | Cost |
|---|---|---|
| Autovacuum | Marks space reusable in-table | Runs continuously, low impact |
| VACUUM FULL | Rewrites table, returns space to OS | Exclusive lock - table unavailable |
| pg_repack | Same result, no long exclusive lock | Needs free space and an extension |
| TRUNCATE | Instantly frees the whole table | Removes all rows |
| DROP partition | Instantly frees that partition | Requires partitioned design |
02VACUUM FULL is not the answer during business hours
VACUUM FULL rewrites the table and does return space to the operating system, but it takes an access exclusive lock for the duration. On a large table that means the table is unreadable and unwritable for minutes to hours, which converts a disk warning into a self-inflicted outage.
It also requires roughly as much free space as the table occupies, because it writes a new copy before removing the old one. Running it on a nearly full disk can fail partway and leave you worse off than before.
03Find the bloat before choosing a fix
Measure which tables and indexes are actually bloated rather than assuming. Compare the on-disk size against the live tuple count, and check the dead tuple counts in pg_stat_user_tables. A table that is ninety percent dead space is worth reorganising; one at ten percent is not worth the disruption.
Indexes bloat independently and are frequently the larger problem, particularly under heavy update workloads. Rebuilding an index concurrently is far less disruptive than rewriting the table and often recovers most of the space.
| Cause | Signature |
|---|---|
| Long-running transaction | Autovacuum cannot remove recent dead rows |
| Abandoned replication slot | WAL accumulates without bound |
| High update rate on a wide table | Table and index bloat grow together |
| Autovacuum too conservative | Dead tuples climb steadily |
| Unarchived WAL | pg_wal directory grows until disk is full |
04Check for a stuck replication slot
An inactive replication slot prevents Postgres from discarding write-ahead log segments, because it assumes a replica will eventually need them. A replica that was decommissioned without dropping its slot will fill the disk with WAL indefinitely, and this is one of the more common causes of sudden unexplained growth.
Look at pg_replication_slots for slots marked inactive and drop any that correspond to systems no longer in use. A long-running transaction has a similar effect on vacuum and is worth checking at the same time.
05Design so that deletion is cheap
For time-series and log-style data, partitioning by time converts deletion into dropping a partition, which is effectively instantaneous and returns space immediately. This is a far better long-term position than periodically wrestling with vacuum on a single enormous table.
Combine that with retention decided when the table is created. Data kept because nobody chose a retention period tends to be kept forever, and the cost is paid in storage, backup duration and every restore you ever perform.
Topics
Priya Iyer
Staff Engineer · SyncTrix
Writes about the engineering decisions behind production systems - architecture, delivery and the trade-offs that only show up at scale.
Building something like this?
SyncTrix engineers AI, SaaS, platform and cloud systems for enterprises and high-growth teams. Tell us what you're shipping and we'll scope it with you.
Talk to an engineer