FULL AI ANALYSIS

PostgreSQL 14: Rapid table bloat after nightly batch UPDATEs despite autovacuum enabled

Generated Oct 3, 2026, 12:35:03 AM

Analysis summary

UPDATE in PostgreSQL produces dead tuples that must be vacuumed away by autovacuum. If dead tuples cannot be removed (or visibility map/cleanup hindered), disk usage grows. Common blockers: long-lived transactions or replication/logical slots preventing VACUUM from truncating, very conservative autovacuum thresholds not keeping up with churn, bulk UPDATE patterns that touch most rows causing many dead tuples in a short window, or TOAST/large-row interaction. Manual VACUUM FULL reclaims space by rewriting the table but requires an exclusive lock, which fixes space but is disruptive. A safer pattern is to remove blockers, tune autovacuum for aggressive cleanup, and rewrite the table in a minimally disruptive way (e.g., pg_repack or concurrent chunked rewrite).

Possible causes

Bulk UPDATE pattern touches large portions of table at once, creating massive dead tuple churn faster than autovacuum can keep up with under defaults, and TOASTed values accumulating separately.

90%

Default autovacuum settings too conservative for high-update batch workload (scale_factor and thresholds inadequate).

60%

Replication or logical replication slots holding WAL or visibility preventing truncation of dead tuples.

20%

Long-lived transactions or sessions (including monitoring or application read transactions) preventing VACUUM from reclaiming tuples.

5%

Suggested fixes

1

Diagnostic step 1

Check for long-lived transactions: run SELECT pid, state, query_start, age(now(), query_start) FROM pg_stat_activity WHERE state <> 'idle' OR query_start IS NOT NULL; if any long-running non-idle queries exist, identify and coordinate safe termination.

2

Diagnostic step 2

Inspect replication slots and prepared transactions: SELECT * FROM pg_replication_slots; SELECT * FROM pg_prepared_xacts; if slots are holding WAL and are stale, coordinate with application/DBA before dropping or advancing them.

3

Diagnostic step 3

Enable more aggressive autovacuum for the affected table(s) as a reversible configuration change: ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = 0.01, autovacuum_vacuum_threshold = 50); monitor effect for 24–48 hours.

4

Diagnostic step 4

Use a non-blocking table rewrite tool such as pg_repack (preferred) or perform chunked UPDATE/INSERT-SELECT to rewrite partitions rather than VACUUM FULL. Test on a copy/standby first and ensure backups exist before performing on production.

5

Diagnostic step 5

If immediate space reclamation is required during a maintenance window, perform VACUUM FULL on a single partition at a time to reduce lock impact; ensure a recent backup exists and communicate downtime to stakeholders — this is reversible only by restoring from backup if it causes issues but will free space immediately when successful. Consider scheduling during low traffic window and validate application after each partition processed before proceeding to next partition. Escalate to a DBA if replication slots or prepared transactions appear to be root cause or if you cannot safely stop long-running transactions.