skip to content

An index is defined on a wide key — say a 60-byte composite of text columns — instead of an 8-byte integer. Structurally, what changes inside the B+tree, and what are the practical consequences?

level: seniorimportance: should knowfreq 44%

answer

  1. fanout = page bytes / entry bytes
  2. 5x wider key ≈ 5x lower fanout
  3. Height +1 level; footprint several-fold — footprint hurts more
  4. Cache residency is a cliff, not a slope
  5. Clustered storage: wide PK inflates every secondary index

basics

~20 s

Wider entries mean fewer per page, so fanout drops several fold. The tree grows taller and, more importantly, the index occupies far more pages, so much less of it fits in cache. Result: more page reads per lookup and more memory pressure.

solid answer

~60 s

Fanout is page size divided by entry size, so a 60-byte key instead of 8 bytes cuts entries per page by roughly a factor of five — from around 500 to around 100. Three consequences: 1. **Height.** `log_f(N)` with f=100 instead of 500: a billion rows goes from about 4 levels to about 5. One extra level per lookup — real but modest. 2. **Index size — the bigger effect.** The leaf level holds one entry per row, so a 7x wider entry means roughly a 7x larger index in bytes. A 2 GB index becomes 14 GB and no longer fits in the buffer pool, so lookups that used to be memory hits become physical reads. This dominates the height effect. 3. **Ripple into other indexes.** In clustered storage, every secondary index leaf carries the primary key, so a wide PK inflates *every* index in the table. Mitigations: narrow surrogate keys, prefix-truncated or hashed keys, indexing only the leading discriminating columns, and putting the widest column last or leaving it out of the key.

code

text · 5 lines
text
key 8B  -> entry ~16B -> fanout ~500 -> height 4 -> leaf level ~16 GB
key 60B -> entry ~70B -> fanout ~110 -> height 5 -> leaf level ~70 GB

height: +1 level per lookup
footprint: ~4x more pages to cache  <-- the effect that changes behaviour

go deeper

for a junior

Know that wider keys mean fewer entries per page, so the index is bigger and lookups cost more.

for a middle

Do the arithmetic: entry width to fanout to height, and note that the leaf level grows proportionally with entry width.

for a senior

Lead with cache residency and total footprint, mention comparison cost and the ripple into secondary indexes, and propose concrete mitigations.

for a principal

Frame it as a key-design decision with system-wide effects — memory budget, replication and backup volume, index build time — and weigh natural versus surrogate keys accordingly.

## The arithmetic Fanout is a byte budget: ``` entries per page ≈ usable page bytes / (key bytes + pointer bytes + slot overhead) ``` On an 8 KB page with ~8 KB usable minus headers, an 8-byte key plus a pointer and slot overhead lands near 16-20 bytes per entry — roughly 400-500 entries. A 60-byte key pushes the entry to ~70 bytes — roughly 100-110 entries. Fanout falls by about 5x. ## Effect one: tree height Height ≈ `ceil(log_f N)`. For a billion rows: - f = 500 → `log500(1e9) ≈ 3.6` → 4 levels - f = 100 → `log100(1e9) ≈ 4.5` → 5 levels So one extra level. Each level is one more page access per lookup — but the upper levels are cached, so this alone might cost close to nothing. ## Effect two: index footprint — the one that actually hurts The leaf level contains one entry per indexed row, so leaf bytes scale linearly with entry width. A 7x wider entry is a roughly 7x larger index. This is the effect that changes system behaviour, because caching is a cliff, not a slope: - If the index fits in the buffer pool, lookups are memory accesses: microseconds. - If it does not, leaf accesses become random reads against storage: hundreds of microseconds on SSD, milliseconds on spinning disk. An index that was comfortably resident at 2 GB and is now 14 GB may push not just itself but *other* hot data out of cache, degrading unrelated queries. The blast radius is the whole instance, not just queries using that index. Secondary costs follow the size: longer index builds and rebuilds, more write-ahead-log volume for index maintenance, more backup bytes, slower replication catch-up. ## Effect three: comparison cost Wide keys are usually strings, and string comparison under a collation is far more expensive than an integer compare. Every page descent does a binary search — around `log2(fanout)` comparisons per page — so a collation-aware comparison in the inner loop turns a formerly free CPU cost into a measurable one on high-QPS lookups. ## Effect four: the ripple through other indexes In a clustered (index-organized) storage model, every secondary index entry stores the primary key as its row reference. So a wide primary key is not paid once — it is paid in *every* secondary index on that table, inflating all of them and lowering their fanout too. This is the classic reason engines that cluster on the primary key push hard for narrow, ideally integer, primary keys. ## Effect five: what stays fine Correctness and query capability do not change. Range predicates, ordering, and prefix matching all still work. The index is not "broken" by being wide — it is simply more expensive per row it indexes, and the question is always whether the selectivity it buys is worth the bytes. ## Mitigations worth naming - **Narrow the key.** Use a compact surrogate key rather than a natural composite of text columns; store the wide natural value as a non-key column with a uniqueness constraint if you still need it enforced. - **Index a derived, narrow value.** Index a fixed-length hash or a truncated prefix of the wide column when equality lookup is the access pattern (accepting that you then verify the full value after the index probe, and that ranges no longer work on a hash). - **Trim the column list.** Composite indexes often carry columns that add little selectivity; the leading discriminating columns usually do nearly all the filtering work. - **Order columns by width and usefulness.** Once selectivity requirements are met, avoiding a very wide trailing column in the key keeps entries smaller. - **Rely on engine-side prefix truncation for separators.** Many engines already store abbreviated separators in branch pages, so the branch levels suffer less than the leaves — which reinforces that the leaf level is where the cost lives. ## How to answer aloud "Fanout is page bytes over entry bytes, so a 60-byte key cuts fanout roughly five-fold, from ~500 to ~100. That adds about one level of height for a billion rows, which is minor. The real cost is the leaf level: it holds one entry per row, so the index gets several times bigger and stops fitting in memory, turning cached lookups into random reads and evicting other hot pages. And under clustered storage a wide primary key inflates every secondary index too. I'd reach for a narrow surrogate key, or index a hash or prefix if the access pattern is equality."

  • If the extra height is only one level, why do you call the wide key expensive?
    Because the level count is not where the bytes are. The leaf level holds one entry per row, so widening the entry multiplies total index size, and cache residency behaves like a cliff: once the working set stops fitting, formerly in-memory lookups become random storage reads and the evicted pages hurt other queries too. One extra cached level costs almost nothing; several extra gigabytes of leaf pages costs a lot.
  • When is indexing a hash of a wide column a reasonable alternative?
    When the access pattern is pure equality lookup, since a hash of a long text value gives a fixed narrow key with good fanout. You must re-check the original column after the probe to guard against hash collisions, and you lose range predicates, ordering and prefix matching entirely because the hash destroys key order. If any query needs ranges or sorted output on that column, the tradeoff does not work.

saying these in an interview costs you the question

  • Saying a wide key only matters because the tree gets taller, ignoring the index footprint and cache effect
  • Assuming fanout is a constant of the engine rather than page size divided by entry size
  • Believing a wide primary key is paid only once, not in every secondary index under clustered storage
  • Ignoring collation-aware string comparison cost inside each page's binary search
  • Proposing to hash the column while still expecting range queries to use the index

context