The Same Stream Twice
A hard Pipeline Design interview practice problem on DataDriven. Design the pipeline on the canvas: sources, transforms, storage and checks, then get feedback on it.
- Domain
- Pipeline Design
- Difficulty
- hard
- Seniority
- senior
- Asked in
- The Walt Disney Company
Problem
A global streaming-video platform collects about 2 billion playback heartbeat events a day from 150 million subscribers, and two teams read the same feed: reliability needs rebuffering spikes per title and region detected within seconds and pushed straight to an on-call paging tool, while finance pays studios royalties on exact minutes-watched and cannot tolerate a single double-counted or dropped event. Design the pipeline so both teams consume the same durable ingest independently, with the live path detecting spikes fast and paging on-call while the daily royalty report counts each raw event exactly once instead of reusing the live aggregation. Keep a malformed heartbeat from a bad device build from stalling the live path.
Business requirements
- When a title starts rebuffering in a region, I need on-call paged within seconds, not in tomorrow's report.
- We pay studios on exact minutes-watched, so the daily total cannot double-count a retried event or drop one that slipped a window.
- A buggy device firmware release once sent malformed heartbeats and took our whole pipeline down; that can never block live alerting again.
- I don't want two separate collection paths drifting apart; both teams should be reading the same source of truth, each on their own offsets.
Worked solution and explanation
Why this problem exists in real interviews
This is two consumers with opposite correctness budgets reading one event stream, disguised as a streaming dashboard. Reliability wants approximate-and-instant: a rebuffering spike per title and region in seconds, off-by-a-few is fine. Finance wants exact-and-patient: royalties on minutes-watched, where one double-counted retry is a contract dispute. The trap is the single aggregation serving both: too rough to bill on, and once you bolt exactness onto it, too slow to page on-call.
The whiteboard answer is one stream writing a minutes_watched table both the QoE dashboard and the royalty report read. It looks clean for a week. Then a producer retries during a deploy, both counts inflate, and finance overpays a studio. Or a malformed heartbeat crashes the consumer, it replays the same offset, and reliability goes blind for hours. Three requirements failed quietly because they were never separated.
Walk the requirements
Step 1: Land everything in one durable queue
Heartbeats land in a partitioned message queue first. The stream processor and the daily batch each read from their own committed offsets, so neither starves the other and either can replay alone. The subtle failure is chaining the royalty batch off the stream processor's output instead of the raw queue: royalties then inherit the live path's approximations and lose independent replay. Two separate collection paths drift just as badly, and the day they disagree nobody can say which number is right.
Step 2: Stream the QoE path; approximate is the budget
A stream processor windows heartbeats by title and region, flags rebuffering above a baseline, and emits to an alert destination inside < 1min. Counting a viewer mid-transition slightly wrong is fine; live-feel is the budget. The 'compute it exactly before we alert' version is where on-call learns of the incident from Twitter.
Step 3: Make royalty exactly-once at the sink, not in the stream
The daily batch reads raw heartbeats straight from the queue, where each event carries a stable event_id from the player. It dedups on that id and writes idempotently to the warehouse, so a producer retry is absorbed and a window-edge event is not dropped. Exactly-once is enforced at the sink with a key, not hoped for in the streaming layer. Billing from the approximate live table is the mistake that becomes an overpayment.
Step 4: Quarantine poison pills, keep draining
Validation lives on the consumer: a heartbeat that fails schema (the missing title_id from the bad firmware build) routes to a dead-letter quarantine and its offset is acked, so the main path keeps moving. A separate process triages the quarantine, and its depth is alerted on so a firmware regression is visible. If a bad event can halt the consumer, one device build blinds reliability during the incident it most needed to see.
The shape that fits
- Playback heartbeat ingest (source, Kafka)
- Live QoE stream processor (transform, Flink)
- Rebuffering alert path (consumer, PagerDuty)
- Dead-letter quarantine (storage, S3)
- Daily royalty batch (transform, Airflow)
- Royalty warehouse (storage, Snowflake)
- Finance royalty report (consumer, Tableau)
- Playback heartbeat ingest -> Live QoE stream processor
- Playback heartbeat ingest -> Daily royalty batch
- Live QoE stream processor -> Rebuffering alert path
- Live QoE stream processor -> Dead-letter quarantine
- Daily royalty batch -> Royalty warehouse
- Royalty warehouse -> Finance royalty report
- Ingest: Playback heartbeat ingest
- Two paths: Live QoE stream processor, Daily royalty batch
- Sinks: Rebuffering alert path, Dead-letter quarantine, Royalty warehouse, Finance royalty report
Common follow-up questions
- A heartbeat arrives two hours after its day already closed in the royalty warehouse. What does the batch do with it? (Tests bounded-lateness handling plus an `event_id`-keyed correction run that lands the late event in the next cycle without double-counting.)
- A tentpole release pushes the live path to 5x volume and consumer lag climbs. What keeps on-call from going blind? (Tests partitioning, autoscaling on lag, and the decoupling that stops the royalty batch from stealing the live path's resources.)