Scenarios
Longer problems, like those in senior system design interviews. A strong answer estimates with numbers first, names the mechanism, then recommends a fix and says what it costs.
1An orders table of 100 million rows has an index on status, which has 6 values. A report for status = 'refunded', 4% of rows, is slow. A colleague says the index is broken. Diagnose.Show the answer
Estimate
At 100 rows per page, the table is 1,000,000 pages. 4% is 4 million rows scattered across pages, so the index path is up to 4 million random page reads at a cost of 4.0 each, 16 million units, against 1 million for a sequential scan. Even on NVMe, with a random cost of 1.1, it is 4.4 million against 1 million. The planner is right to scan.
Options
- Accept the scan, and make it cheaper with columnar storage or partitioning by status.
- Cluster the table by status, if that is the dominant access path.
- Make the filter more selective by combining predicates (
status = 'refunded' AND created_at > …) with a composite or bitmap index. - Use a partial index if only a narrow, recent subset is queried.
Trade-off
Every extra index taxes writes. Drop the status index if no query benefits from it.
Read moreThe crossover with real numbersClustered indexesPartial indexes
2Design the indexes for a multi-tenant SaaS orders table (tenant_id, created_at, status, customer_id). Queries: recent orders per tenant, orders per customer, pending orders per tenant, and a daily export.Show the answer
Start from the query shapes
Spend the clustered order on the most valuable range scan: (tenant_id, created_at) serves recent orders per tenant and, as a prefix, all orders of a tenant.
Per customer
A secondary index on (customer_id, created_at). If the primary key is wide in InnoDB, remember every secondary entry carries a copy of it.
Pending per tenant
A partial index on (tenant_id) WHERE status = 'pending': pending orders are a small, static subset. Confirm the application writes the predicate as a literal, not a parameter.
Daily export
A time range, best served by clustering or zone maps rather than a new index.
Failure modes
One giant tenant (skew), correlated tenant_id and status throwing off estimates, and indexes that are never used: review scan counts monthly.
Recommend
Three indexes, not eight.
Read moreClustered indexesPrimary versus secondaryPartial indexesZone maps
3A search feature built on an inverted index across 12 shards has a p99 latency three times worse than in a single-shard test, and new documents take 30 seconds to appear. Explain both, and decide what to change.Show the answer
The latency
With an index local to each shard, every query goes to all 12. If each shard is slow 1% of the time, a query meets at least one slow shard with probability 1 − 0.99¹² ≈ 11.4%, so the tail grows with the shard count. Fix: fewer, larger shards; a routing key that sends most queries to one shard; or hedged requests.
The freshness
New documents are written as small immutable segments that must be opened for search and later merged. The 30 seconds is the refresh interval. Shortening it creates many small segments: more merge work, and more segments per query.
Decide with the product
If 1–5 seconds of delay is acceptable, set the refresh to that and watch segment counts and merge load. Where users must see their own writes, read from the primary store by ID.
Read moreLocal versus global secondary indexesThe consistency boundaryInverted indexes
4You need nearest-neighbour search over 200 million float vectors of 1,024 dimensions, with p95 latency under 50 ms. What do you do first?Show the answer
Count memory first
200 million × 1,024 × 4 bytes = 819 GB raw. That does not fit one normal machine, so the memory cliff decides the architecture.
Compress
Product quantization with, say, 128 one-byte sub-codes gives 128 bytes per vector: 25.6 GB, which fits, at a recall cost you must measure.
Narrow the search
Add a coarse inverted-file (IVF) layer with about √(N × nprobe) cells: for nprobe = 8, √(1.6 × 10⁹) ≈ 40,000 cells, so each query scans about N × 8 ÷ 40,000 = 40,000 vectors. Or use a graph index (HNSW) if memory allows: 32 links × 4 bytes × 200 million = 25.6 GB for the links alone, plus the compressed vectors.
Trade-offs
Recall against memory against build time. Deletes need tombstones and periodic rebuilds. Re-ranking the candidates with full-precision vectors read from disk can recover recall cheaply.
Recommend
IVF with product quantization on a few nodes first; measure recall against exact search on a sample; then decide.
Read moreHigh dimensionIVF methodsGraph methods: HNSWQuantization
5Design geo search for 'restaurants within 1 km' over 50 million places, with category and open-now filters.Show the answer
The problem
Proximity in two dimensions: no single sort order preserves it, so you choose which locality to break.
Options
Cells (S2 or H3 at about 1 km) keep ordinary B-tree machinery: a 1 km radius covers a handful of cells, a number of ranges that grows with the perimeter, followed by an exact distance check. An R-tree gives exact containment, but its boxes overlap and the number of subtrees to visit is unpredictable.
Category
A composite key (category, cell_id) if a category is nearly always given; otherwise intersect candidates with a bitmap per category.
Open now
It changes constantly: do not index it; filter afterwards.
Failure modes
Dense cities with many places per cell (use adaptive cell levels), hot cells, and skew.
Recommend
Cells as the key, an exact distance check as refinement, and category as the leading key or as a bitmap.
Read moreTwo and three dimensionsEscape 1: linearizeEscape 2: partitionBitmap indexes
6A sharded user table is searched by email. Compare a local index, a global index updated synchronously, and a global index updated asynchronously.Show the answer
Local
Cheap, consistent writes, but every email search fans out to all N shards: with N = 32, 1 − 0.99³² ≈ 27.5% of searches meet a slow shard, and cost grows with N.
Global, synchronous
Searches hit one shard, but every user write touches two shards and needs a distributed transaction (two-phase commit): higher write latency and more ways to fail; a stuck coordinator blocks writes.
Global, asynchronous
One-shard searches and cheap writes, but results are stale or missing for seconds after a change, and a uniqueness rule on email cannot be enforced reliably.
Recommend
If email must be unique and strongly consistent, as at sign-up, keep a dedicated mapping table whose single row per email is the authority, written first, with the uniqueness check, inside one shard. Use asynchronous global or local indexes for search features that tolerate delay. The consistency boundary can be moved, never removed.
Read moreLocal versus global secondary indexesThe consistency boundary
7A query over an Iceberg table of Parquet files on object storage reads 40 times more data than its filter suggests. Where do you look?Show the answer
The real index
There is no B-tree here: the main index is the min/max statistics per row group and per file. So the question is not "is there an index?" but "how does the filtered column relate to the file layout?"
Check
- Is the table sorted or Z-ordered on the filtered column?
- Did late-arriving data, or many small files, break the clustering?
- Do the row groups' min/max ranges overlap? Inspect the footer statistics.
- Can the predicate be pushed down at all? A function wrapped around the column prevents it.
Fix
Rewrite with the right sort key or clustering, compact small files, and stop wrapping filtered columns in functions.
Trade-off
Reclustering costs write work and compute, so do it for the columns that appear in most queries.
Read moreZone mapsTime: the nearly redundant indexExpression indexes
8A data lake has 200,000 Parquet files. Queries filtering on customer_id read almost every file, although min/max statistics exist. Why, and what do you do?Show the answer
Diagnose
Min/max statistics prune only if values follow the physical order. Written in arrival order, every file's customer_id range spans nearly the whole domain, so nothing is pruned. Measure the average range width as a fraction of the domain.
Fix
Sort or cluster by customer_id during compaction (or Z-order on several columns), which narrows the ranges. Add a per-file Bloom filter for equality on this high-cardinality column. Look for sentinel values (null stored as 0, or 9999-12-31) and replace them with real nulls.
Also
Reading 200,000 file entries on every query makes planning cost grow with the file count: add a hierarchy of manifests and compact small files.
Read moreZone mapsTwo practitioner facts