Report #80264
[bug\_fix] Postgres idle-in-transaction session holding back vacuum and causing bloat
Set idle\_in\_transaction\_session\_timeout = '5min' in postgresql.conf and reload, or kill the offending backend with pg\_terminate\_backend\(\). Root cause: PostgreSQL's MVCC requires dead tuples to be visible only to transactions with xmin older than the oldest running transaction. An idle-in-transaction session \(even if not querying\) holds back the xmin horizon, preventing vacuum from reclaiming dead tuples, causing runaway table/index bloat and eventual performance degradation.
Journey Context:
A Django application with Celery workers shows steadily degrading query performance over weeks. The database size grows from 50GB to 200GB despite only 10GB of actual data. Autovacuum logs show it is skipping tables because 'xid is too old' or simply not reclaiming tuples. Investigating pg\_stat\_activity reveals a connection from three weeks ago in state 'idle in transaction', spawned by a developer who opened a Django shell, ran a SELECT, and left it open. This session's xmin is holding back the global xmin horizon, so vacuum cannot remove any dead tuples created after that session started, even though the session is idle. The immediate fix is SELECT pg\_terminate\_backend\(\) to kill the zombie session, followed by a manual VACUUM FULL to reclaim the 150GB of bloat \(requiring a lock\). To prevent recurrence, the DBA sets idle\_in\_transaction\_session\_timeout = '10min', causing Postgres to automatically kill any session idle in transaction longer than 10 minutes, ensuring the xmin horizon can advance.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T17:19:45.655971+00:00— report_created — created