skip to content

You are designing an OLTP table and can choose whether the rows are physically ordered by a clustering key or stored unordered in a heap. What workload characteristics push you toward each, and what do you give up?

level: seniorimportance: should knowfreq 42%

answer

  1. One physical order per table — choose deliberately
  2. Cluster for range/grouped access on a dominant narrow key
  3. Heap for multi-column access, wide keys, random inserts
  4. Clustering cost: 2nd descent for secondaries + key width everywhere
  5. Heap cost: row fetch always, ranges = scattered reads

basics

~20 s

Cluster when one key dominates access, especially range or parent-child locality, and inserts arrive roughly in key order — you get no-row-fetch lookups and sequential ranges. Prefer a heap when access spreads across many columns, the key is wide, or inserts are random with respect to it.

solid answer

~60 s

**Choose clustered storage when:** - One key dominates reads, particularly *range* or grouped access (all rows for a tenant, a time window, a parent id). Rows are physically adjacent, so the range is a sequential leaf walk returning whole rows. - Point lookups on that key are the hot path — the row is in the leaf, so no extra fetch. - Inserts arrive roughly in key order, so pages fill and splits stay cheap. - The clustering key is narrow, since every secondary index carries it. **Choose heap storage when:** - Queries filter on many different columns, so no single ordering helps and you want every index equally cheap (one descent plus a direct row fetch). - The natural key is wide, which under clustering inflates all secondary indexes. - Inserts are random with respect to any candidate key, so clustering would mean splits and bloat. - You value stable physical row addresses. **What you give up by clustering:** you only get one physical order; secondary lookups cost a second descent; random-order inserts hurt badly. **By using a heap:** every lookup pays a row fetch, and key-ordered ranges become scattered random reads rather than sequential ones.

go deeper

for a junior

Know the headline rule: cluster when one key dominates reads and inserts follow that order; otherwise a heap is simpler and symmetric.

for a middle

Explain the tradeoffs on both sides — no row fetch and sequential ranges versus a second descent and key-width propagation.

for a senior

Drive from the workload: rank access patterns by volume, check key width and insert correlation, and quantify the range-locality win against secondary-path cost.

for a principal

Treat it as a schema strategy with system-wide consequences — one ordering per table, key design coupled to all index footprints, engine constraints, and how the choice ages as access patterns change.

## Frame the decision correctly Physical row order is a single, table-wide choice, so the question is: *which one access pattern deserves physical locality, and is the cost of denying it to all the others acceptable?* If no pattern clearly dominates, the symmetry of a heap is the safer default. ## The case for clustering **Range and grouped access.** This is the strongest argument. If queries repeatedly ask for a contiguous run of key values — every event in a time window, every line item for an order, every row for one tenant — clustering makes that run physically contiguous. Instead of N random row fetches, you read a handful of leaf pages sequentially, each carrying many qualifying rows. The saving grows linearly with rows returned, so at hundreds or thousands of rows per query it is a step change rather than a tweak. **Primary-key point lookups.** Under clustering the row is the leaf payload, so a PK lookup is a single descent with no row fetch. In a heap it is a descent plus a fetch. One saved random access per lookup is modest individually but real at high QPS. **Sorted output for free.** Queries ordering by the clustering key can consume rows in order without a sort. **Locality of related rows.** Clustering a child table on a composite key that leads with the parent id physically groups children under their parent — often the single best structural optimization for parent-child read patterns. ## The case for a heap **Access spread across columns.** If a table is queried by five different columns with comparable frequency, clustering privileges one and makes the other four pay a second descent. A heap gives all five identical cost — a descent plus a direct fetch — which is a better shape when there is no dominant pattern. **A wide natural key.** Under clustering, the clustering key is embedded in every secondary index as the row reference, so a wide key multiplies across all of them. A heap uses a compact physical row id regardless of key width, decoupling key design from index footprint. **Random insert distribution.** If no candidate key correlates with arrival order, clustering forces scattered inserts, splits, poor page fill and a large dirty page set. A heap simply places rows where there is room. **Stable row addresses.** Physical row ids do not change on ordinary inserts, so index maintenance stays simple. (Updates that grow a row can still relocate it, leaving a forwarding pointer — the heap's own maintenance wart.) **Bulk load and churn.** Appending arbitrary data into a heap is close to sequential regardless of key values. ## What you sacrifice either way Clustering costs you: only one ordering, a second descent for every secondary-index lookup, sensitivity to insert order, and the key's width propagating into all indexes. Heap costs you: a row fetch on every index lookup, and key-ordered ranges degenerating into many random page reads because logically adjacent rows are physically scattered — the effect grows with result size, and the optimizer may abandon the index entirely for large ranges in favour of a full scan. ## A practical decision procedure 1. **List the top access patterns by volume**, noting for each whether it is a point lookup or a range/group, and on which columns. 2. **Is there a dominant grouped/range pattern?** If yes, that key is your clustering candidate. 3. **Check width.** If the candidate is wide, either narrow it (surrogate key) or reconsider clustering. 4. **Check insert correlation.** Will rows arrive roughly in that key order, or can you make them (time-ordered identifiers, or a leading tenant/parent id that concentrates writes per tenant)? If inserts will be uniformly random over the key, clustering trades read locality for write pain. 5. **Check the secondary paths.** How much traffic will pay the extra descent, and is that acceptable? Sometimes an index that carries the extra columns a query needs neutralizes it. 6. **If nothing dominates, take the heap** and let each index stand on its own. ## Engine reality check worth stating Some engines make this choice for you: certain storage engines always cluster on the primary key, others store heaps by default and offer index-organized tables or a one-off physical reorganization as an option. So in a real design conversation, step zero is knowing which model the target engine gives you and whether it is negotiable at all — the reasoning above then tells you how hard to work on key design within that constraint. ## The sentence to land "Cluster when one key dominates access — especially ranges — and inserts arrive roughly in that order and the key is narrow; otherwise take the heap's symmetry. You are choosing which single access pattern gets physical locality, and you only get to choose once."

  • For a child table read almost entirely by its parent id, what clustering key would you pick?
    A composite leading with the parent id, followed by a discriminator that makes it unique — for example (parent_id, child_id). That physically groups all children of a parent into the same few leaf pages, so fetching a parent's children is a short sequential leaf read instead of many scattered row fetches. Inserts also concentrate per parent rather than scattering uniformly, which keeps the write set reasonable.
  • Under heap storage, why can a range query returning many rows end up slower than a full table scan?
    Because logically adjacent keys are physically scattered, so each qualifying row is a separate random page fetch, and the same page may be visited repeatedly. Beyond a fraction of the table, that random access costs more than reading every page once sequentially, so the optimizer switches to a full scan. Clustering removes the problem by making the range physically contiguous.
  • Does adding all the query's columns to a secondary index reduce the penalty of the extra descent under clustering?
    Yes — if the index contains every column the query needs, the engine can answer from the secondary index leaves alone and never descends the clustered tree. That eliminates the second traversal for that specific query. The cost is a wider index entry, which lowers fanout and grows the index, so it is worth doing for a few high-traffic queries rather than reflexively.

saying these in an interview costs you the question

  • Recommending clustering on a column with no dominant access pattern, just because it is the primary key
  • Ignoring that the clustering key is copied into every secondary index
  • Claiming a table can have more than one physical ordering
  • Treating clustered storage as universally faster without mentioning the secondary-lookup descent
  • Ignoring insert-order correlation when proposing a clustering key

context