How would you determine whether a specific index in a production database is genuinely bloated, rather than simply large or slow for some other reason?
answer
- Ratio, not size: actual pages ÷ estimated pages
- 1.2–1.5× normal, 3×+ real bloat
- Trend: size up while live rows flat
- Scan-shaped degrades, point lookups don't
- Check reclamation blockers before rebuilding
basics
~20 sCompare the index's physical size against an estimate of what its live entries need: live row count times average entry width, divided by page capacity. A large ratio, plus falling page density and rising scan I/O over time, indicates bloat.
solid answer
~60 sBloat is a ratio, not a size, so measure both sides. 1. **Expected size** — live entries × average entry width (key bytes + row pointer + per-entry overhead), divided by usable page capacity, gives the leaf pages a freshly built index would need. 2. **Actual size** — the index's physical page count from the catalog. 3. **Ratio** — 1.2–1.5× is normal for an active index at steady-state density. 3–5× means real bloat. Sharpen that with direct evidence: page-level statistics (average leaf density, dead-entry counts, tree height) where the engine exposes them; the index's size trend over time against the table's live row trend — the divergence *is* the bloat; and physical-versus-logical leaf ordering for fragmentation. Then rule out impostors. A big index may just be a wide composite key. A slow query may be a plan change, stale statistics, lock waits or a cold cache. Ask whether the symptom is scan-shaped (degrades with bloat) or point-lookup-shaped (barely does). And check first whether reclamation is even running: a long-running transaction holding an old snapshot blocks cleanup, and rebuilding without fixing that just re-bloats.
code
text · 9 lineslive rows indexed : 20,000,000
avg entry (key+ptr+ovh) : 24 bytes
usable bytes per 8 KB pg : ~8,000
expected leaf pages = 20,000,000 * 24 / 8,000 = 60,000 pages ≈ 470 MB
actual index size = 1,900 MB (≈ 243,000 pages)
ratio = 4.0x -> bloat, not simply a large index
corroborate: index size +38% over 90 days while live rows -5%go deeper
Know that you compare actual index size against what its live rows should need, and that a much larger actual size means bloat.
Do the estimate explicitly, know what a normal steady-state ratio looks like, and separate bloat from fragmentation.
Lead with the trend and query evidence, rule out plan changes and lock waits, and identify what is blocking reclamation before proposing a rebuild.
Turn it into monitoring: track size versus live rows per index, alert on divergence, and treat structurally bloat-prone designs as a schema problem rather than a maintenance-window problem.
## Bloat is a ratio "This index is 40 GB" is not a finding. Forty gigabytes might be exactly right for a billion rows on a composite key. Bloat means **the index occupies substantially more pages than its live contents require**, so a diagnosis needs a numerator and a denominator. ## Estimating the denominator: what it should cost For each live row that the index covers, an entry costs roughly: `key bytes + row-pointer bytes + per-entry header/slot overhead` Multiply by live rows, divide by usable bytes per page (page size minus page header and any reserved space), and you get the leaf pages a freshly packed index would need. Add a small allowance for internal pages — with a fanout in the hundreds, internal levels are typically 1% or less of leaf size, so they rarely change the verdict. Be careful with the live row count: use a real count or a trusted statistic, not a stale estimate, and account for partial indexes covering only a subset of rows and for entries omitted when a key is null. ## Measuring the numerator: what it actually costs Every engine exposes the physical size or page count of an index in its catalog or system views. That is the numerator. Do not compare it against the *table* size — a small table with a wide composite index legitimately has an index larger than the heap. ## Reading the ratio - **~1.0–1.2×** — freshly built or append-only. Healthy. - **~1.2–1.5×** — normal steady state for an actively written index, since random churn settles near two-thirds density. Healthy. - **~2×** — worth watching; look at the trend rather than acting immediately. - **3× and up** — genuine bloat. Investigate the cause before rebuilding. Estimates carry error, especially with variable-length keys, so treat the number as a strong signal rather than a precise measurement. ## Better evidence, where available Most engines can report page-level statistics for an index: average leaf density, count of dead or reclaimable entries, count of empty pages, tree height, and the ratio between logical (key) leaf order and physical page order. That last one is the fragmentation measure — an index can be dense yet badly ordered, which hurts sequential scan throughput without inflating size at all. These reads scan the index, so schedule them off-peak on large objects. ## The trend beats the snapshot The cleanest evidence is time series. Record index size and table live-row count on a schedule. A healthy index's size tracks its row count. Bloat looks like size climbing while live rows are flat or falling. That divergence also dates the onset, which often points straight at the cause — a bulk delete, a new update pattern, a long-lived reporting transaction that started blocking cleanup. ## Ruling out the impostors Before blaming bloat for a slow query, check the alternatives, because rebuilding an index is disruptive and fixes none of them: - **Plan change** — the optimizer switched access paths. Compare plans, not just timings. - **Stale statistics** — cardinality estimates drifted and the planner is choosing badly. - **Lock or latch waits** — the time is spent waiting, not reading. - **Cold cache** — a restart or a competing workload evicted the index. - **Genuine growth** — the table really did get bigger. - **Wrong index** — the query needs a different key order or a covering column; a compact index that doesn't fit the predicate is still slow. A useful discriminator: bloat degrades scan-shaped and cache-sensitive work gradually, while leaving single-row lookups by unique key almost untouched. A sudden cliff, or uniform slowness including point lookups, points elsewhere. ## Find the cause, not just the symptom When the numbers do say bloat, ask why, because a rebuild only resets the clock: - Is background reclamation keeping up, or is it starved of resources? - Is a long-running transaction, an abandoned session left open inside a transaction, or a stale replication slot or standby feedback holding an old snapshot alive and preventing cleanup? - Is the workload structurally hostile — a queue table, or bulk deletes by date that would be better served by dropping a partition? - Is the index even used? An unused index bloats, costs on every write, and should be dropped rather than maintained. Most engines track per-index usage counters; check them before scheduling maintenance on something nobody reads. ## Reporting the finding A good diagnosis states: measured size versus estimated size, the trend, the affected query shape and its page-level I/O, the suspected cause, and what happens if nothing is done. That is what makes the difference between "the index is bloated" and a case for a maintenance window.
- The size ratio says 4×, but the team says query latency is fine. Do you rebuild?Not automatically. Weigh the ongoing cost — wasted cache memory, longer maintenance and backup times, more write amplification — against the disruption of the rebuild. If the index is small relative to available memory and nothing is being squeezed, schedule it with routine maintenance rather than urgently. If it is evicting hot data from the buffer cache, that is a latency problem waiting to happen and worth acting on.
- How do you tell bloat apart from fragmentation, and does the distinction change what you do?Bloat is low density — the index has far more pages than its live entries need. Fragmentation is logical leaf order diverging from physical page order, which can happen at perfectly good density. Bloat shows up in the size ratio; fragmentation shows up in an ordering or scan-fragmentation statistic, and hurts large ordered scans by turning sequential reads into random ones. Both are fixed by a rebuild that rewrites the index in key order, so the remedy is often the same — but if only fragmentation is present, a lighter in-place reorganize may suffice.
- What would make you drop the index instead of rebuilding it?Per-index usage counters showing no scans over a representative period, or a redundant leading-prefix overlap with another index that already serves the same predicates. An unused index still bloats, still consumes cache, and still costs on every insert, update and delete, so removing it is strictly better than maintaining it — provided you have confirmed it is not there solely to enforce a uniqueness constraint or to support a rare but critical job.
saying these in an interview costs you the question
- Comparing index size to table size instead of to the index's own expected size
- Rebuilding on a size threshold with no trend and no query evidence
- Ignoring that a long-running transaction or stale replication slot is blocking cleanup, so the index re-bloats within days
- Blaming bloat for a latency cliff that is actually a plan change or lock waits
- Never checking per-index usage counters — maintaining an index nobody queries