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.
1A report takes 3 hours and must run in under a minute. Where do you look?Show the answer
Find the peak
Multiply row counts along the plan and find where they peak. The usual cause is a join or an unnest that runs before a filter or an aggregation.
Check the order of work
Filters should sit below joins, and aggregates should be reduced to the join key's grain before joining: a join that fans out rows is a granularity mismatch.
Count enforcers
How many distinct sort orders and shuffles does the plan add?
Recommend
Reduce rows early, then join; verify with EXPLAIN ANALYZE.
Risk
The optimizer's estimates may be wrong because correlated predicates were assumed independent.
Read moreThe five questions behind any operator's costFilter and projectionRequires, provides and enforcers
2A table of 2 billion orders, 200 bytes each, joins a 500 MB dimension table on a cluster of 200 executors with 16 GB of memory each. Broadcast or shuffle?Show the answer
Network
Broadcast moves 500 MB × 200 = 100 GB; shuffling moves about 400 GB. The size ratio, 800, exceeds P = 200, so the network favours broadcast.
Memory
Broadcast means 100 GB of copies across the cluster and 200 hash-table builds, and 500 MB is far above typical broadcast thresholds of tens of MB.
A bonus
Broadcasting keeps the orders table's existing partitioning, which can save a later shuffle.
Recommend
Broadcast if each executor has room in its 16 GB and no driver becomes a bottleneck; otherwise shuffle with a runtime filter.
Failure mode
A central driver collecting the whole table before broadcasting it.
Read moreBroadcast, shuffle, and the partitioning you haveRuntime filters
3A join job has a single straggler task that runs for 2 hours after all the others finish. Diagnose and fix.Show the answer
Diagnose
Check the frequency distribution of join keys, and the fraction of NULL keys. With Zipf-distributed keys, the top key carries about 7% of rows, which caps the speedup near 14× however many workers you add.
Fix, in order
- Filter NULL keys out before the shuffle: free.
- Split the hot key: let the engine split skewed partitions at run time, or salt it with s copies of the other side if the hot keys are stable.
- Use sampled range partitioning.
When nothing helps
If the hot key is hot on both sides, the output itself is huge; pre-aggregate one side.
4An analyst's report that uses NOT IN suddenly returns no rows. What happened, and what should the platform do?Show the answer
Diagnose
A NULL entered the subquery's results. Under three-valued logic, x NOT IN (…, NULL) is UNKNOWN for every row, so nothing passes.
Fix
Rewrite with NOT EXISTS, or filter NULLs out of the subquery.
Platform
Add a data-quality check on the column, and consider a lint rule against NOT IN on nullable columns. While there, also check for right-side conditions in WHERE on LEFT JOINs, and joins that fan out rows before a SUM.
Read moreThe join family
5Choose a join strategy for a 10 TB fact table and a 50 GB table, where the join condition is a range on time.Show the answer
Rule out
A hash join: the condition is a range, not an equality.
Check the shape
If it is "the latest row at or before t", use an as-of join operator.
Otherwise
A band merge join, or range partitioning with replication. Estimate the replication factor (average interval length ÷ bucket width), and check the distribution of interval lengths for a heavy tail. A merge needs memory only for the longest run of duplicates.
Caveat
There is no simple anchor for the output size, so test it on a sample.
Read moreRange and interval joinsAs-of joins
6With 16 GB of memory and 8 MB I/O buffers, one hash join's build side is 1.1 times memory and another's is 3 TB. What happens in each case?Show the answer
1.1 × memory
A hybrid hash join spills only (1.1M − M) / 1.1M ≈ 9%: a small penalty.
3 TB
A Grace hash join splits it into 3 TB ÷ 16 GB ≈ 188 partitions. The possible fan-out is 16 GB ÷ 8 MB = 2,048, so one level of partitioning is enough (the threshold for a second is about 32 TB), at the cost of 2 × (|R| + |S|) extra I/O.
The real danger
Neither case is a cliff. A single huge key would be, since it cannot be partitioned. A sort-merge join is the constant-memory alternative.
Read moreHybrid hash: a ramp, not a cliffWhen the build side does not fitOut-of-core: the threshold
7Should the platform build a runtime filter for every join?Show the answer
Estimate the leverage
About (row size ÷ roughly 1.2 bytes per key in the filter) × (1 − the fraction of rows that match). For 200-byte rows with 5% matching: about 167 × 0.95 ≈ 158×. For 8-byte rows with 90% matching: about 7 × 0.1 = 0.7, so no gain.
Cost
A barrier: the probe side must wait for the build side to finish the filter.
Choose the filter
A min/max range when the data is clustered, an IN-list for a few keys, a Bloom filter otherwise.
Read moreSideways information passing
8A dashboard query has six window functions and an ORDER BY, and the plan shows six sorts. How do you reduce them?Show the answer
Count
Each distinct (PARTITION BY, ORDER BY) specification is a full sort.
Share orders
Reorder or unify the specifications so they share prefixes: one sort on (a, b) also provides (a). An equality predicate makes a column constant, so it drops out of the required order. Where input is already sorted on (a) and the requirement is (a, b), a segmented sort needs memory only for the largest group, and streams.
Distinguish grouping from ordering
A window that only needs each partition's rows together does not need a global order.
Check frames
Default RANGE frames with ties cost more than ROWS. Sliding MAX or MIN costs O(log w) per row, and holistic functions over a frame cost O(w).
Longer term
A clustered or bucketed layout pays for the sort once, for every query, at write time, at the cost of constraining other queries on that table. And whenever specifications change, verify the results, since semantics may change too.
Read moreWindow functionsSort avoidance
9A report computing COUNT(DISTINCT user_id) GROUP BY page over 50 billion events fails with out-of-memory on one of 200 executors, each with 16 GB. Diagnose and fix.Show the answer
Diagnose
The function is holistic, so partial aggregation cannot shrink anything, and the group key is skewed. If the home page has 10% of events, that is 5 billion rows; at 8 bytes per user ID, 40 GB sent to one executor with 16 GB.
If about 1% error is acceptable
HyperLogLog: 12 KB per page, mergeable, one shuffle, immune to skew, and its merges are exactly reproducible.
If it must be exact
Rewrite as SELECT page, COUNT(*) FROM (SELECT DISTINCT page, user_id …) GROUP BY page. The first shuffle is by the (page, user) pair, so the hot page spreads over all executors. It costs two shuffles but runs in parallel. Plain random salting would double-count users.
Note
Several distinct aggregates in one query mean several copies of the data on the wire.
Recommend
Sketches for dashboards, where the error has no meter and is paid silently by the reader; the exact two-shuffle plan for billing numbers.
Read moreTwo-phase aggregationThe parallel plan for exact COUNT DISTINCTWhat a sketch actually does
10A nightly SUM(amount) GROUP BY customer_id over 2 billion rows, with 40 million customers on 200 executors, runs slower with partial aggregation enabled than without. Why, and what do you change?Show the answer
Estimate
Each executor sees 10 million rows. With 40 million customers, it can expect about 40M × (1 − e^(−0.25)) ≈ 8.85 million distinct customers, so partial aggregation shrinks its data only about 1.13×. It pays a hash and a memory write per row, plus an 8.85-million-entry table, for a 12% smaller shuffle.
Fix
Turn partial aggregation off for this query, or enable an adaptive mode that samples the first tens of thousands of rows and abandons it when few rows hit existing groups. A plan-time estimate of distinct values is the least reliable statistic, so prefer sampling at run time.
Contrast
For GROUP BY country, the ratio is in the tens of thousands, and partial aggregation is a huge win.
11Design the sort of a 20 TB table on 100 nodes with 64 GB of memory each and NVMe storage. What are the risks?Show the answer
Estimate
200 GB per node. With 32 GB of memory for the sort and 8 MB buffers, M² / B = 32 GB × 4,000 = 128 TB, so one merge pass suffices, and I/O is about 4N = 800 GB per node. At 3 GB/s that is under 5 minutes, so the node is likely CPU-bound if keys are strings with collation: use normalized keys and sort compact (prefix, pointer) pairs.
Distributed shape
Range-partition on boundaries chosen from a random sample (a few hundred samples per partition), sort locally, and concatenate.
Risks
- Biased samples from time-ordered files: sample at random, never the first rows.
- A single key with more rows than one partition should hold defeats any boundaries.
- Sampling is a barrier before any rows can be routed.
- On object storage, larger buffers bring the third pass closer.
Read moreExternal merge sortChoosing the splittersThree failure modesNormalized keys
12A deduplication step uses a Bloom filter to drop events it has already seen. Finance reports that totals are 0.1% too low. Review the design.Show the answer
Diagnose
The filter has no false negatives but does have false positives. In deduplication, a false positive says "seen before" about a new event, so a real row is dropped, and nothing downstream checks. At 10 billion rows, 0.1% is 10 million lost rows, invisible to monitoring.
Repair
Use the filter only as a negative oracle: "definitely new" emits immediately, "maybe seen" consults an exact store or an exact sort- or hash-based dedup. Or deduplicate exactly on a 128-bit digest (with 64 bits, a collision is about 93% likely at 10¹⁰ rows), using a sort-based dedup if the final stage does not fit in memory.
When approximate is fine
Best-effort telemetry, consumers that are idempotent anyway, and bounded-window filters that reset.
Monitor
Reconcile row counts against the source after every run.
Read moreThe same filter, the opposite consequenceUse 128 bits
13A pipeline joins on a key, aggregates on the same key, and orders the output by it. Choose between hash-based operators with shuffles and a sort-based plan.Show the answer
Count the requirements
A merge join needs order on the key, sort-based aggregation needs order (or grouping) on the key, and ORDER BY needs order on the key: three consumers of one order.
Decide
Sort once, merge-join, aggregate in one streaming pass with constant memory, and the output is already ordered. A hash plan would build one hash table for the join, another for the aggregation, then sort for ORDER BY. With only one consumer of the order, hashing would win.
Better still
If the table is clustered or bucketed on the key, the sort is free.
Risks
A hot key breaks range partitioning and hashing alike, and the sort needs N ≤ M² / B per node to finish in one merge pass.
Read moreAmortization across requirementsTwo ways to make equal things meet
14Your team wants a metric that is reproducible bit for bit across runs, but it is built from floating-point sums and p99 latency estimates. What do you do?Show the answer
Sums
Floating-point results depend on the order of additions. Use decimal or fixed-point types for money, compensated (Kahan or Neumaier) summation, or sort before summing.
Percentiles
Choose the sketch by how it merges: t-digest merges are lossy and depend on the merge tree, so they are not reproducible; KLL has a bounded error; exact quantiles come from sorting by (group, value) and reading positions.
State the trade-off
Exactness costs a sort; a sketch costs an error with no meter. t-digest is good for accuracy at p99 and p99.9, but not for reproducibility.
Read moreFloating-point summationMergeability is not uniform
15Estimate the cost of SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id ORDER BY customer_id over 2 billion rows and 40 million customers on 200 executors, where customer_id = 0 holds 7% of rows and 2% are NULL. Choose a plan.Show the answer
Estimate
The output is 40 million rows. Partial aggregation reduces data only about 1.13×, so the shuffle moves nearly all 2 billion rows × about 16 bytes (key and value) ≈ 32 GB. Each executor then owns 200,000 customers, which fits easily in memory (about 5 MB).
Plan
Make the shuffle a range partitioning on customer_id, and aggregate with a sort on each executor: the output is grouped and ordered at once, so ORDER BY costs nothing extra.
The hot key
customer_id = 0 is 140 million rows. SUM is distributive, so wherever partial aggregation applies it collapses that key to one row per executor and the skew is harmless; an adaptive engine keeps partial aggregation on when hot keys give a high hit rate.
NULLs
The 2% of NULL keys form one more group; decide whether the report should include it.
Read moreThe core tension, as one ratioSort-based aggregationSample-based range-partitioned sort