{"post":{"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","content":"<span class=\"dropcap\">A</span> query that ran in two milliseconds a year ago now takes eleven. The plan is unchanged — it still uses the index you expect. The table has grown, but only by a third, and eleven milliseconds is not a third worse than two.\n\nThe index has grown by 300%. Most of that growth is empty space and entries pointing at rows that no longer exist. The lookup still works; it just reads several times as many pages to do the same job, and those pages are pushing genuinely useful data out of memory.\n\n## What changed in 2026\n\n- **Concurrent rebuilds became reliably available.** Rebuilding an index without blocking writes moved from a specialist operation to something you can schedule during business hours.\n- **Bloat monitoring reached default dashboards.** Managed database offerings started surfacing index size against estimated live size, which is the measurement most teams previously never took.\n- **High-churn workloads got more common.** Status columns, counters, and timestamps updated constantly — the exact pattern that bloats indexes fastest — are everywhere in event-driven systems.\n- **Automated maintenance improved without solving it.** Background cleanup keeps up with typical churn and still loses against sustained update-heavy workloads.\n\n## Where the bloat comes from\n\nTwo mechanisms, and they compound.\n\n**Dead entries from row versions.** Under MVCC, updating a row creates a new version rather than editing in place. The index needs an entry pointing at the new version, and the old entry remains until cleanup determines nobody can see it. An index on a column updated frequently accumulates these continuously — and note that this happens even when the *indexed value* did not change, because the row moved. [MVCC explained](/blog/mvcc-explained-2026) covers the underlying mechanism.\n\n**Page splits.** B-tree indexes store entries in fixed-size pages. Insert into a full page and it splits into two half-full pages. Delete most entries from those pages afterwards and you are left with pages holding a handful of entries each. The pages stay allocated. A logically small index can occupy many mostly-empty pages, and ordinary cleanup reclaims the dead entries without consolidating the pages.\n\nThat second mechanism is why indexes typically bloat worse than their tables. Table cleanup can reuse freed space for new rows; index pages are ordered by key, so a page can only accept entries that belong in its key range.\n\n## Why it costs more than disk\n\nDisk is cheap and this is not really a disk problem.\n\n| Effect | Consequence |\n| --- | --- |\n| More pages per lookup | Each query does more I/O |\n| Larger memory footprint | Index competes for buffer cache |\n| Useful data evicted | Other queries slow down too |\n| Longer maintenance | Cleanup takes longer, falls further behind |\n| Slower backups | Backing up mostly-empty pages |\n\nThe cache row is the one that matters. A database's speed comes largely from serving reads out of memory. An index three times larger than necessary occupies three times the cache for the same information, evicting data other queries needed. The visible symptom is often not the query using the bloated index — it is unrelated queries getting slower because their working set no longer fits.\n\n## Detecting and fixing it\n\n**Measure before acting.** Compare an index's actual size to a rough estimate of what its live entries require. A ratio around 1.2 is normal and healthy. Meaningfully above 2 is worth attention. Most databases have a standard query or extension for this, and managed offerings increasingly show it directly.\n\nAlso check whether the index is used at all. Unused indexes are pure cost — write overhead, space, and cache pressure — and dropping one is a bigger win than rebuilding it.\n\n**Rebuild concurrently.** The concurrent form builds a fresh copy alongside the old one and swaps at the end, taking only a brief lock. The non-concurrent form takes a lock that blocks all writes to the table for the duration, which on a large index in production is an outage. The concurrent version is slower and uses more space temporarily; use it anyway.\n\nTwo caveats: a concurrent rebuild can fail partway and leave an invalid index behind, so check afterwards and drop any that failed. And a rebuild is pointless if the workload that caused the bloat continues unchanged — you will be back in three months.\n\n**Address the cause where you can.** An index on a column updated on every request will bloat, permanently. Sometimes the right fix is not indexing that column, moving the high-churn field to a separate narrow table, or accepting a scheduled rebuild as ongoing maintenance for that one index.\n\n## Common mistakes\n\n- **Rebuilding without measuring.** Most indexes are fine; you are spending I/O to fix nothing.\n- **Using the non-concurrent rebuild in production.** Blocks writes for the entire operation.\n- **Ignoring unused indexes.** Dropping beats rebuilding, and it is permanent.\n- **Assuming table cleanup handles indexes.** It removes dead entries and does not consolidate half-empty pages.\n- **Not checking for failed concurrent rebuilds.** An invalid index is skipped by the planner, so queries silently get slower.\n- **Rebuilding while a long transaction is open.** Cleanup cannot reclaim anything pinned by it, so bloat returns immediately — see [MVCC explained](/blog/mvcc-explained-2026).\n- **Over-indexing.** Every index bloats. Fewer, better-chosen indexes beat many marginal ones.\n\n## FAQ\n\n**How often should I rebuild?**\nRarely on schedule, and by measurement. Track the ratio and rebuild the specific indexes that cross your threshold. A blanket monthly rebuild of everything wastes I/O on indexes that were fine.\n\n**Does this happen in every database?**\nThe mechanisms differ by engine — where old versions live and how pages are managed varies considerably — but B-tree page splits and dead entries are near-universal. The remedies differ in name more than in substance.\n\n**Will bloat affect the query plan?**\nIndirectly and importantly. The planner uses index size in its cost estimates, so a sufficiently bloated index can look expensive enough that the planner stops choosing it — and a sequential scan replaces your index without anything appearing broken. See [query planners](/blog/query-planner-explained-2026).\n\n**Is a smaller index always better?**\nSmaller for the same coverage, yes. A partial index covering only the rows you query is often dramatically smaller and bloats less, because fewer updates touch it. That is frequently the better fix.\n\n## Where to go next\n\nFor the version churn that produces dead entries, read [MVCC explained](/blog/mvcc-explained-2026). For how the planner weighs a bloated index, [query planners](/blog/query-planner-explained-2026), and for choosing which indexes to have at all, [database indexing explained](/blog/database-indexing-explained-2026).","hue":316,"metaTitle":"Index Bloat: Why Indexes Get Slower Over Time","metaDescription":"Why database indexes accumulate dead entries and empty space, how bloat degrades query performance through cache pressure, and how to rebuild safely.","keywords":["index bloat","database index maintenance","reindex concurrently","page splits","index fragmentation","postgres index bloat","slow index performance","database maintenance 2026"],"tldr":["**Updates create new index entries, they do not edit old ones** — so an index on a frequently-updated column accumulates dead entries continuously.","**Bloat costs cache, not just disk** — a bloated index reads more pages for the same lookup and evicts useful data from memory.","**Indexes usually bloat worse than tables** — page splits leave partly-empty pages that ordinary cleanup does not reclaim.","**Rebuild concurrently, never with a plain rebuild in production** — the non-concurrent form takes a lock that stops all writes to the table.","**Skip:** rebuilding on a schedule without measuring. Most indexes never need it; the few that do need it repeatedly."],"readTime":"5 min read","wordCount":1095},"related":[{"slug":"indexes-explained-2026","title":"Database indexes explained in 2026: what every developer must know","excerpt":"Indexes make or break query performance. Here is the 2026 guide covering B-tree, GIN, GiST, and partial indexes with concrete examples and tradeoffs.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":309},{"slug":"how-to-optimize-sql-queries-2026","title":"How to optimize SQL queries in 2026: indexes, plans, and real fixes","excerpt":"Slow SQL is rarely a mystery — the query plan tells you exactly what is wrong. This 2026 guide covers the tools and techniques to find and fix slow queries fast.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":309},{"slug":"clustered-indexes-explained-2026","title":"Clustered Indexes: When Physical Order Matters","excerpt":"Storing rows in index order makes range scans dramatically faster and makes the choice of key a decision you live with, because inserting in the wrong order fragments everything.","category":"code","date":"January 8, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":317}],"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":"home-title-fraud-protection-2026","title":"Home Title Fraud: The Risk and the Overpriced Cure","excerpt":"Title fraud is real, rare, and heavily marketed against by services that do less than they imply. Here is how the fraud works and what free protection exists.","category":"guides","date":"August 18, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":316},"older":{"slug":"iso-amt-explained-2026","title":"ISOs and AMT: The Tax Bill on Stock You Did Not Sell","excerpt":"Exercising incentive stock options can trigger alternative minimum tax on paper gains. Here is how the trap works and how to exercise without it.","category":"finance","date":"August 18, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":292}}