{"post":{"slug":"autovacuum-tuning-2026","title":"Autovacuum Tuning: Keeping Cleanup Ahead of Churn","excerpt":"The background process reclaiming dead rows is fine until it is not. When it falls behind on a hot table, bloat grows, queries slow, and nothing in your application changed.","category":"code","date":"January 6, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">U</span>nder multi-version concurrency control, updating or deleting a row leaves a dead version behind. A background process reclaims that space so it can be reused. When it keeps up, nobody thinks about it.\n\nWhen it falls behind on a high-churn table, the table grows far beyond its live data, scans read pages full of invisible rows, indexes bloat alongside, and query performance degrades for reasons entirely invisible in the application.\n\n## What changed in 2026\n\n- **Managed database defaults improved.** Better out-of-the-box settings reduced how often manual tuning was needed.\n- **Per-table configuration became standard advice.** Recognition that global settings cannot serve both a small reference table and a high-churn queue table.\n- **Monitoring moved into standard dashboards.** Dead row counts and last-vacuum times became visible rather than something you queried during an incident.\n- **The long-transaction interaction got emphasised.** Awareness that no tuning helps while an old transaction pins versions.\n\n## Why defaults underserve big tables\n\nThe default triggering rule combines a fixed number of dead rows with a *fraction* of the table's size. The fractional component is the problem.\n\nA table with a thousand rows triggers vacuum after a small number of dead rows. A table with a hundred million rows must accumulate a proportionally enormous number of dead rows before the same fraction is reached.\n\nSo the largest, busiest tables — the ones where bloat hurts most — are vacuumed least often relative to their churn. That is exactly backwards, and it is why per-table settings matter.\n\n| Table | Default behaviour | Better |\n| --- | --- | --- |\n| Small, low churn | Frequent enough | Leave alone |\n| Large, low churn | Infrequent | Usually fine |\n| Large, high churn | Far too infrequent | Lower the scale factor substantially |\n| Queue-like table | Far too infrequent | Aggressive per-table settings |\n\nFor a high-churn table, reducing the scale factor so vacuum triggers on a much smaller proportion — or effectively on an absolute row count — keeps cleanup proportional to churn rather than to size.\n\n## Throttling and workers\n\nVacuum is deliberately throttled so it does not saturate I/O. A cost limit governs how much work it does before pausing.\n\nThat is sensible protection and it means cleanup can be *unable* to keep up on a busy system, even when triggered promptly. If dead rows accumulate despite vacuum running, the throttle is the constraint and raising the cost limit is the lever.\n\nWorker count matters too. A fixed number of workers process tables one at a time; if you have many tables needing attention, they queue. On a database with many high-churn tables, more workers helps — each still throttled individually, so total I/O impact scales with worker count.\n\nThe tradeoff is honest: more aggressive vacuum means more background I/O competing with queries. That is nearly always better than the alternative, because bloat degrades everything permanently while vacuum I/O is transient.\n\n## Long transactions defeat everything\n\nThe most important thing to know, and the one no amount of tuning addresses.\n\nVacuum can only reclaim versions no active transaction might need. A transaction that started an hour ago pins every version created since, across the whole database. Vacuum runs, finds nothing reclaimable, and dead rows accumulate regardless of how aggressively you configured it.\n\nThe usual causes are an idle-in-transaction application connection, a long-running analytical query, or an abandoned session — see [MVCC explained](/blog/mvcc-explained-2026).\n\nWhich means **monitoring the age of the oldest transaction is more valuable than any vacuum setting**. If cleanup is falling behind, check that first; tuning is pointless while a transaction from this morning is still open.\n\n## Common mistakes\n\n- **Global tuning only.** Cannot serve tables with different churn profiles.\n- **Ignoring the scale factor on large tables.** The core reason defaults fail.\n- **Disabling autovacuum.** The problem compounds into an emergency.\n- **Tuning while a long transaction is open.** Nothing will help.\n- **Not monitoring dead row counts.** Bloat is invisible until it is severe.\n- **Raising cost limits without watching I/O.** Can affect query latency.\n- **Forgetting indexes bloat too.** They need attention alongside — see [index bloat](/blog/index-bloat-explained-2026).\n\n## FAQ\n\n**How do I know if vacuum is keeping up?**\nMonitor dead row counts and time since last vacuum per table. Rising dead rows on a table being vacuumed means the throttle or the trigger threshold is the constraint.\n\n**Should I run manual vacuum?**\nOccasionally useful after a large bulk operation. Regular manual vacuum on a schedule suggests autovacuum is misconfigured — fix the configuration instead.\n\n**Does more aggressive vacuum hurt performance?**\nIt uses I/O that queries would otherwise have. Almost always a better trade than bloat, which degrades performance permanently rather than transiently.\n\n**What about very large tables that never get vacuumed?**\nLower the scale factor substantially for those specifically. A fractional threshold on a huge table is effectively never.\n\n## Where to go next\n\nFor what vacuum is cleaning up, read [MVCC explained](/blog/mvcc-explained-2026). For measuring the problem, [bloat monitoring](/blog/bloat-monitoring-2026), and for the index side, [index bloat](/blog/index-bloat-explained-2026).","hue":310,"metaTitle":"Autovacuum Tuning: Keeping Cleanup Ahead of Churn","metaDescription":"Why default autovacuum thresholds underserve large tables, how per-table tuning helps, and why long transactions defeat cleanup entirely.","keywords":["autovacuum tuning","vacuum settings","table bloat prevention","dead tuples","vacuum cost limit","per table autovacuum","postgres maintenance","database operations 2026"],"tldr":["**Default thresholds scale with table size** — so large tables get vacuumed less often, exactly backwards.","**Set per-table settings on hot tables** — global tuning is a blunt instrument.","**Long transactions block reclamation entirely** — no tuning fixes a transaction open for an hour.","**Cost limits throttle it deliberately** — raise them if cleanup cannot keep up.","**Skip:** disabling autovacuum to reduce I/O. The problem compounds until it becomes an emergency."],"readTime":"4 min read","wordCount":844},"related":[{"slug":"bloat-monitoring-2026","title":"Bloat Monitoring: Measuring Space You Cannot See","excerpt":"A table twice the size of its live data reads twice as many pages for every scan. Bloat is invisible in ordinary metrics and degrades performance continuously until someone measures it.","category":"code","date":"January 7, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":320},{"slug":"mvcc-explained-2026","title":"MVCC Explained: How Readers Stop Blocking Writers","excerpt":"Your database serves a consistent snapshot to every transaction without locking anything. The trick is keeping several versions of every row, and it has a cost.","category":"code","date":"August 14, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":301},{"slug":"index-bloat-explained-2026","title":"Index Bloat: Why Your Indexes Get Slower Over Time","excerpt":"The index was fast when you built it. A year of updates later it is three times the size, half empty, and the queries using it have quietly degraded.","category":"code","date":"August 18, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":316}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"small-cap-value-explained-2026","title":"Small Cap Value: The Premium That Keeps Disappearing","excerpt":"Decades of data suggested small, cheap companies outperform. Then came a long stretch where they did not, which is the honest starting point for any discussion of factor investing.","category":"finance","date":"January 7, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":276},"older":{"slug":"backup-verification-2026","title":"Backup Verification: A Backup You Have Not Restored Is a Guess","excerpt":"Backup jobs report success by writing a file. Whether that file can be restored into a working database is a different question, and it is the only one that matters.","category":"code","date":"January 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":313}}