Skip to content
About
Main content

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.

Try each one before you open the answer.
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 scan. Even on NVMe, with a random cost of 1.1, it is 4.4 million against 1 million. The is .

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 .
  • Use a 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 on (customer_id, created_at). If the 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 rather than a new index.

Failure modes

One giant tenant (), 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 , 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 , 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 decides the architecture.

Compress

with, say, 128 one-byte sub-codes gives 128 bytes per vector: 25.6 GB, which fits, at a cost you must measure.

Narrow the search

Add a coarse inverted-file () layer with about √(N × ) 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 () 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 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 ( 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 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, 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 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