Design live streaming for a cricket final
Stream one live match to tens of millions of viewers at once, on patchy mobile networks. The hard parts are delivery capacity (several CDNs and an origin shield), how segment length trades delay against smooth playback, and counting viewers without one overloaded database row.
A live sports streaming app will show a cricket final. Plan for about 30 million viewers watching at the same time at peak, mostly on phones over 4G, some on smart TVs. Design the path from the stadium feed to the viewer, and the live viewer count shown on screen.
Requirements
Functional
- Ingest (receive) the live programme feed from the broadcast production truck.
- Transcode it into an adaptive bitrate ladder: the same video at several quality levels, so each device picks a quality its network can sustain.
- Viewers start playback on any device and can rewind up to 30 minutes (a DVR window).
- Show an approximate live viewer count, refreshed about every 30 seconds.
Non-functional
- 30 million viewers at once at peak, with a surge of several million joins in a few minutes (at the toss, in the last overs).
- Glass-to-glass latency (camera lens to viewer's screen) under about 20 seconds. Target about 8 to 10 seconds with low-latency segments.
- Rebuffering under 0.5% of watch time. Startup under 2 seconds for 90% of viewers (p90).
- Playback 99.99% available for the match. A viewer count off by 1 to 2% is fine.
Out of scope
- DRM licence servers and subscription payments.
- Server-side ad insertion (a good follow-up).
- Live chat, polls and the catalogue of recorded videos.
Estimate
- Blended bitrate: phones mostly at 480p to 720p (~1.2 to 2.5 Mbps), TVs at 1080p (~5 Mbps); assume ~2 Mbps average.
- Egress: 30M viewers x 2 Mbps = 60 Tbps at peak. That is more spare capacity than it is wise to ask of any single CDN, so plan for several CDNs and pre-book capacity.
- Edge requests: with 4 s segments each viewer fetches one segment and one playlist every 4 s: 30M x 2 / 4 = ~15M requests/s at the edges.
- Unique objects: 6 renditions x (1 segment + 1 playlist) = 12 new objects every 4 s. Content is identical for everyone, so cache hit ratio at the edge should be well above 99%.
- Origin load with a shield: say 3 CDNs x ~20 shield locations each fetch 12 objects per 4 s = 60 x 12 / 4 = ~180 requests/s at origin. Without a shield, thousands of edge servers missing at once could send hundreds of thousands.
- Ladder output: 300 + 700 + 1,200 + 2,500 + 5,000 + 8,000 kbps = ~17.7 Mbps per pipeline; a 30-minute DVR window = 17.7 Mbps x 1,800 s / 8 = ~4 GB. Storage is trivial.
- Viewer heartbeats: one per viewer every 30 s = 30M / 30 = 1M events/s. Never one database row; aggregate in a stream.
API
- POST /live/{eventId}/ingest (SRT contribution with a stream key) -> 200 session
- GET /live/{eventId}/playback -> 200 {manifestUrls by CDN, token, dvrWindowS}
- GET /cdn/{eventId}/{rendition}/index.m3u8?token= -> 200 rolling media playlist
- GET /cdn/{eventId}/{rendition}/{seq}.m4s?token= -> 200 segment
- POST /live/{eventId}/heartbeat {sessionId, rendition, cdn, rebuffers} -> 204
- GET /live/{eventId}/viewers -> 200 {approxCount, asOf}
Data model
- events(event_id PK, status, starts_at, ladder_profile, dvr_window_s, cdn_weights)
- Object storage keys: {event}/{rendition}/{seq}.m4s and {event}/{rendition}/index.m3u8 (rolling, last 30 minutes)
- playback_sessions(session_id PK, user_id, event_id, cdn, region, started_at) with a TTL, in a key-value store
- viewer_counts(event_id, minute, region, count): small time series written by the aggregator
A request, step by step
- The production truck sends two independent contribution feeds over SRT (a protocol for sending live video reliably over the internet) to ingest gateways in two regions. If one feed drops, the transcoders switch to the other within a segment or two.
- Two parallel transcode pipelines produce a 6-rung ladder, from 240p to 1080p. They cut the video into 4-second CMAF segments (small video files), with boundaries aligned across qualities. They write the segments and rolling playlists (lists of the latest segments) to origin storage.
- A viewer opens the match. The playback service checks that they are allowed to watch. It picks a CDN for the viewer's region and network, using weights driven by live quality data from players. It returns signed playlist URLs, with a second CDN as a fallback.
- The player fetches playlists and segments from the CDN edge. Edge misses go to the origin shield, a middle cache layer that turns thousands of identical misses into one fetch from origin. When the player's buffer runs low, its ABR logic (adaptive bitrate) switches to a lower quality.
- Every 30 seconds the player sends a heartbeat, a small "I am still watching" message. The playback service puts it on a stream. Counters add them up per region per time window and write the totals to the key-value store. The viewer-count endpoint, and the number on screen, read from there.
Deep dives
Delivering 60 Tbps: multi-CDN and the origin shield
Weak: one CDN, and the origin is a web server. If that CDN has a regional problem, millions of viewers stall together.
At the start of each new segment, every edge server misses its cache and asks the origin. With thousands of edge servers, you create your own thundering herd (a flood of identical requests at the same instant) every 4 seconds.
Good: one CDN plus an origin shield. The shield is a middle cache layer in front of the origin. Each new segment is fetched from origin a handful of times, not thousands.
What makes this work is request collapsing at the shield. Only one fetch per object is in flight, and every other request for that object waits for it.
Great: several CDNs with steering, each with its own shield. Split traffic across two or three CDNs by region and by measured quality of experience (QoE). QoE means startup time and rebuffering, reported by the players.
The playback service hands out a primary URL and a fallback URL. On errors, the player switches to the fallback without the user noticing. Capacity is booked with each CDN before match day, and traffic weights can be shifted in minutes.
Trade-off: multi-CDN costs more engineering. Token schemes, logs and cache rules must be set up in several places. Cache efficiency also drops a little, because each CDN fills its cache separately. In return, you do not depend on one provider at the one moment you cannot afford an outage.
Segment length: latency versus stability
Every live HTTP stream trades delay for stability. A player usually buffers about three segments before it starts. So latency is roughly 3 times the segment length, plus encoding and delivery time.
Weak: 10-second segments. Very stable, and easy for the CDN to cache. But viewers are 30 to 40 seconds behind. Neighbours shout "six!" before your screen shows the ball being bowled, and social media spoils every wicket.
Good: 4-second segments. Latency drops to about 15 to 20 seconds. Request rates rise, because there are more playlists and segments per minute. The player also has less buffer to survive a short 4G drop.
Great: low-latency chunked delivery. Keep 4-second segments for caching, but publish each one in chunks of about 1 second. The CDN can serve a chunk while the rest of the segment is still being written. Playlists announce the upcoming parts in advance.
Latency reaches about 5 to 10 seconds. Offer this where networks are good. When the player detects rebuffering, it falls back to standard latency with a larger buffer.
Trade-off: lower latency means a smaller buffer. That means more rebuffering on weak networks and more requests at the edge. For a cricket final, a stable 10 seconds is better than a fragile 3 seconds.
Counting 30 million viewers
Weak: a counter row. Add one when a viewer joins and subtract one when they leave. At peak join rates, that one hot row takes hundreds of thousands of writes a second.
Leaves are also unreliable. Apps get killed and phones lose signal without saying goodbye. So the number only drifts upward.
Good: heartbeats into a stream. Each player sends a heartbeat every 30 seconds. A stream partitioned by region feeds counters, which count distinct sessions in each 30-second window.
A viewer who stops sending drops out of the next window, so the count corrects itself. About 1 million events per second is a normal load for a partitioned stream.
Great: count early, and count approximately. Each ingest server forwards counts per window, not every raw event. Use HyperLogLog per region: a small data structure that estimates how many distinct items it has seen, using a few KB of memory.
HyperLogLog counts from different regions can be merged cheaply, and the same viewer is not counted twice. When load is extreme, make the heartbeat interval longer. Serve the number from a cache that refreshes every 30 seconds.
Trade-off: you give up an exact count (it is off by 1 to 2%) and second-by-second precision. Nobody watching can notice either. In return, the counter never becomes the bottleneck.
What breaks
- A contribution feed or a transcode pipeline fails. The second pipeline keeps publishing aligned segments, so players see at most a short drop in quality. Alert on gaps in segment publishing for each quality level.
- One CDN degrades in a region. Player reports show rising rebuffering. Steering shifts that region's traffic to the other CDNs, and players have already switched to their fallback URL.
- A storm of joins after a rain delay. The playback service keeps no state and is scaled up in advance. If it is still overloaded, it returns a cached playlist response so playback still starts.
- The viewer-count pipeline falls behind. The screen shows the last good count with its time. Playback is not affected, because counting is not on the playback path.
What each level is expected to cover
Mid level: Describes ingest, transcoding into a bitrate ladder, segments and playlists in object storage, and CDN delivery. Does the egress (outgoing bandwidth) arithmetic. Counts viewers with heartbeats rather than a database counter.
Senior: Calculates egress and request rates. Designs several CDNs with steering, and an origin shield with request collapsing. Explains how segment length trades latency for stability, and how low-latency chunks help. Plans redundancy end to end (two feeds, two pipelines). Makes decisions from player quality metrics.
Common mistakes
- Sending video through application servers or WebSockets instead of cacheable HTTP segments on a CDN.
- Forgetting the origin: thousands of edge misses at every segment boundary, with no shield.
- Promising latency under one second for 30 million viewers without naming the cost in rebuffering.
- Counting viewers by adding and subtracting on one database row.
- A single ingest path or transcode pipeline, with no backup, for a once-a-year event.
Follow-up questions
- How would you insert regional ads during the break between overs?
- A CDN reports healthy, but players in one state are rebuffering. How do you find out and react?
- How do you prepare and load test for a final you only get one chance at?
- How would you add a second commentary language?
- How would you stop people sharing a signed stream URL publicly?