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.
1Design a streaming job that joins 1 million orders per second with a clickstream, attributing each order to clicks within the previous 10 minutes. Estimate the state, and say what can go wrong.Show the answer
Estimate
At about 200 bytes per record, with both sides buffered for 600 seconds: 1,000,000 × 200 B × 600 s × 2 = 240 GB of join state, more if both streams run at that rate.
Design
That does not fit on a heap, so use an embedded LSM state store with incremental checkpoints, and expect checkpoint traffic to follow the changes, not the 240 GB. Use an interval join keyed by user, with an explicit bound, a watermark per partition, and handling for idle partitions.
What can go wrong
- Events later than the bound are lost silently: measure the real distribution of arrival delays, and count records dropped because their match was evicted.
- A hot user key cannot be split: check key skew.
- Compaction debt causes write stalls, which show up as backpressure.
Recommend
Run at about 50–60% of capacity, so a 10-minute outage is caught up in about as long. And decide whether a 20-minute bound, which doubles the state, is worth 1–2% more matches.
Read moreTime-bounded stateStream-stream joinsIncremental checkpointingThe failure is silent
2A streaming job holds 600 GB of state and suffers a 15-minute outage. How long until it is current again, and how do you shorten that?Show the answer
Restore
Restore time = state ÷ bandwidth: at 1 GB/s, 600 seconds, 10 minutes. Incremental restore from local disk can be faster; from remote storage, slower.
Catch up
The backlog is the arrival rate × 900 seconds. A job running at 90% of capacity drains at only 10% of capacity, so catching up takes 9 times the outage, 135 minutes; at 60%, it drains at 40%, taking 1.5 times the outage, 22.5 minutes. In total, restore plus catch-up.
Shorten it
More headroom (the recovery budget); faster restore (local recovery on the same machine where possible, and smaller state through TTLs and tighter retention rules); or replaying faster than normal by scaling out temporarily. Scaling out moves state too, so decide on it before the incident.
Check
The input log's retention must be longer than the outage plus the catch-up.
Read moreRecovery time and headroomDynamic scalingWhat the design assumes
3Finance requires that charges reach the billing database exactly once. Compare a transactional sink with idempotent writes.Show the answer
Transactional sink
Two-phase commit driven by checkpoints. Output is invisible until the checkpoint completes, so latency is at least the checkpoint interval. The broker's transaction timeout must exceed the longest checkpoint plus the worst recovery, or a long outage silently loses pre-committed data.
Idempotent writes
Derive a deterministic key, such as a charge ID from the order ID, and upsert, so replays overwrite the same row. No timeout coupling, output visible at once; it needs a sink that supports upserts.
Both need
Deterministic replay: no now(), no random(), no lookups whose results depend on timing.
Recommend
Idempotent upserts for the charges table. Use transactions only for sinks that cannot upsert, such as append-only logs, and then set the timeout above checkpoint duration plus recovery, and document the dependency.
4You need to scale a stateful streaming job from 8 to 16 instances without a long outage, and the team wants autoscaling. Is that a good idea?Show the answer
What rescaling costs
It is a stop and restore with state moved (downtime ≈ state moved ÷ bandwidth), followed by a catch-up that usually dominates.
Check the maximum parallelism
It must be at least 16, and ideally divisible by both counts (for example 128 or 720), or key-group ranges get split unevenly.
Autoscaling
An actuator that is expensive, slow and itself increases lag will oscillate. Use hysteresis (scale up at 60%, down at 30%), cooldowns, and no scaling during a backlog. Prefer scaling ahead of known peaks, during quiet hours.
Longer term
State stored remotely makes rescaling cheap, at the price of a round trip per access and uncertain performance for hot loops.
Read moreDynamic scalingWhy autoscaling is hardThe permanent decision
5Developers want to add a field to the per-user state and deploy daily. How do you keep deployments safe?Show the answer
Principle
The state's schema is a public interface: treat changes to it like API changes.
Practice
- Explicit operator IDs from day one.
- Only compatible changes, such as adding a field with a default; never remove key fields or change types without a migration.
- A savepoint before every deployment, and a test restore in staging with a copy of real state.
- For incompatible changes, run in parallel and cut over: a second job started from an earlier offset, double compute during the replay, source retention long enough, and idempotent sinks so the overlap is harmless.
- Avoid starting from empty state for jobs with long windows, which would give, say, 30 days of degraded output.
The law
Long-lived state turns every change into a migration.
Read moreSavepoints and state schema evolutionThe escape hatches and their prices
6A dashboard shows hourly counts 2% lower than the warehouse, with no errors anywhere. Diagnose.Show the answer
Check, in order
- Every bound chosen to save memory: the join interval, state TTL, allowed lateness, maximum session length. Measure the real distribution of event delays against each.
- Watermarks: idle partitions that wake up with old events create late records, which are dropped.
- Determinism: did something non-deterministic make a replay after a crash change the counts?
- The sink: an append-only sink receiving a changelog, or an aborted transaction.
Fix
Widen the bound that is cutting data, add counters and alerts for records dropped as late or evicted, and reconcile against the warehouse as a routine check.
Read moreTTL is a silent correctness changeThe failure is silentIdle channelsWhat streaming forbids
7A job counting orders per restaurant is dominated by one restaurant with 30% of the traffic, and throughput does not grow with more instances. What do you do?Show the answer
Diagnose
A key's state cannot be split, so throughput is capped at about the capacity of one instance ÷ 0.3 ≈ 3.3 instances' worth, however many you add.
Fix
Counting is associative and commutative, so aggregate in two phases: salt the key into, say, 8 sub-keys, count locally, strip the salt, then sum. It costs an extra shuffle and a short additional stage of latency.
When that fails
For order-sensitive or non-decomposable operations, such as a pattern match or an exact median: reduce the work for that key at the source (pre-aggregate in the producer), or accept the cap.
Monitor
Per-key skew, so the problem shows up before it hits the limit.
Read moreThe indivisible key
8Product asks for real-time, exactly-once, 24-hour sliding counts of distinct users per restaurant, refreshed every second. Which parts are expensive?Show the answer
Sliding 24 hours, every second
That is 86,400 overlapping windows, impossible naively. Panes of 1 second help, but combining 86,400 panes per output is costly, so use larger panes (say 1 minute) and a coarser refresh, or a structure that merges cheaply.
Distinct users
Not decomposable exactly; use HyperLogLog sketches, which merge.
Exactly-once
A transactional sink adds latency of at least the checkpoint interval, which conflicts with refreshing every second, unless the sink is an idempotent upsert on (restaurant, refresh time).
Recommend
Approximate distinct counts, 1-minute panes, idempotent upserts, and a clear statement of which numbers are approximate.
Read moreSliding windowsExactly-once