Live sports is the hardest shape of video there is, and it is hard for a reason that is the exact opposite of YouTube. On a VOD platform you do the expensive work (transcoding) once, offline, days before anyone watches, and the hard part is serving immutable bytes cheaply forever. On live, the match is happening right now, there is no pre-encode, the stream has to be transcoded and packaged and pushed to the edge in the same few seconds the ball is being bowled, and every one of 50 million viewers is watching the same 6 seconds of video at the same wall-clock moment. There is no long tail to smooth the load. The whole audience arrives when the toss happens, surges when a wicket falls, and all 50M of them scream at the CDN within the same 2-second window when a six is hit. And they will notice if the stream is 30 seconds behind, because their neighbour on a different app (or just the apartment next door) will cheer first and spoil it.
So the problem is this: take a single live camera feed, transcode it on the fly into an adaptive bitrate ladder, package it into tiny segments, and fan those identical segments out to tens of millions of concurrent viewers through a regional CDN, keeping every viewer under ~10 seconds behind the live edge, while the audience doubles in 30 seconds and every exciting moment creates a synchronized request spike. Everything hard here is in service of two numbers: glass-to-glass latency (camera to viewer screen) and surviving a flash crowd that converges on the single newest segment. Let me build it properly.
Functional Requirements (FR)
In scope:
- Ingest a live feed. A broadcast encoder at the stadium pushes a single high-bitrate contribution feed (via RTMP/SRT) into the platform. The system accepts it continuously for the duration of the match.
- Live transcode and package on the fly. The incoming feed is transcoded in near-real-time into an adaptive bitrate ladder (240p through 1080p/4K) and packaged into short segmented streaming formats (LL-HLS / LL-DASH) with a continuously-growing manifest.
- Adaptive bitrate playback. A viewer streams the live match; the player automatically switches quality based on bandwidth and buffer health and can also pin a quality. Playback tracks the live edge (newest segment).
- Low latency. Viewers stay within a target lag of the live action (we target <10s glass-to-glass) so the experience matches TV and does not get spoiled.
- Massive concurrency. Support tens of millions of concurrent viewers for a single event, with the audience arriving in a steep ramp and spiking on key moments.
- Rewind / catch-up within the live window. A viewer who joins late or pauses can seek back a few minutes (DVR window) and catch back up to live.
- Graceful degradation. Under extreme load or a viewer’s poor network, drop quality rather than stall; keep the broadcast up for everyone even if some features (chat, 4K) are shed.
Explicitly out of scope (say it so you own the scope):
- VOD / full match replay after the game. Once the match ends, the recording becomes a standard VOD asset served by the pre-encode pipeline. That is a solved, separate system (I built it in the YouTube write-up); I note the hook where the live segments are archived into it and move on.
- Live chat, reactions, polls, fantasy scores. Real engagement features and heavy in their own right, but orthogonal to the video delivery plane. I flag where they attach.
- Ad insertion (SSAI) billing and targeting. I design the mechanism for server-side ad stitching (it matters for the manifest) but not the ad decisioning/auction.
- DRM and content protection. Real and required for premium sports rights, but orthogonal to the streaming architecture; I flag the hook point.
- Recommendations, catalogue, login. Assume auth and entitlement checks exist; I consume a “is this user allowed to watch” decision.
The one decision that drives everything: this is a produce-once-per-few-seconds, read-synchronously-by-everyone, latency-critical system where the expensive work (encode + package) happens continuously in real time and cannot be pre-computed, and where the entire audience contends for the single newest segment at the same instant. Unlike VOD (serve old immutable bytes from a deep catalogue) the hot object is the segment that was created one second ago, requested by 50M clients before the CDN has even finished caching it. That synchronized, single-object, can’t-pre-compute pressure is the whole problem.
Non-Functional Requirements (NFR)
- Scale: A marquee IPL final peaks around 50M concurrent viewers (Hotstar has publicly hit ~59M on a single stream). The audience ramps from a few million to tens of millions in minutes around the toss and the chase. One event, one stream, one country-heavy distribution.
- Latency: Glass-to-glass under ~10s at p95 (camera lens to viewer screen); we will push toward 3-5s where the network allows. Time-to-first-frame on join under ~2s. Manifest refresh every segment duration. This is the headline constraint and it fights directly against buffering, which is the easy way to absorb network jitter.
- Availability: 99.99%+ on the playback path for the duration of the event. A live broadcast that goes down during the final over is a catastrophic, un-redoable failure; there is no “watch it later” consolation in the moment. Ingest redundancy (dual feeds) is mandatory because you cannot re-shoot the match.
- Consistency: No strong consistency needed on the read path. Every viewer getting the same segments in order is all that matters; a viewer being 4s vs 8s behind is fine. The only hard requirement is monotonic, gap-free segment sequence per rendition and a manifest that never points at a segment that is not yet published.
- Durability: The contribution feed is the crown jewel and must be recorded durably in parallel (for VOD, highlights, and compliance) with no single point of loss; but the live delivery path itself is ephemeral (a lost segment is skipped, not recovered, because by the time you recovered it the live edge has moved on).
- The real budget is edge egress and flash-crowd absorption. It is acceptable to be a few seconds behind; it is never acceptable for the newest segment to miss the edge cache and stampede the origin when 50M players request it simultaneously. The whole design pushes a single produced-once segment to the edge and makes 50M reads of it collapse to a trickle of origin fills.
Back-of-the-Envelope Estimation (BoE)
Real numbers with the arithmetic, because they dictate the architecture.
Concurrency and the ramp:
Peak concurrent viewers (CCV): 50,000,000 for one event.
Audience ramp: ~5M at toss climbing to 50M over ~20 min, then spiking.
=> join rate during ramp: (50M - 5M) / (20*60) = ~37,500 new joins/sec sustained,
with bursts far higher at the toss and start of the chase.
Each join = 1 entitlement check + 1 manifest fetch + first segments.
=> the CONTROL plane (auth + playlist) sees tens of thousands of joins/sec;
bursty to maybe 100k+/sec at the sharpest ramp.
Segment request QPS (the firehose):
Use 2s LL-HLS segments (low latency) => each viewer fetches ~1 segment / 2s per rendition.
50M viewers * (1 segment / 2s) = 25,000,000 segment GETs/sec STEADY at peak.
Plus manifest refreshes: each client re-reads the (small) media playlist ~every 2s:
50M / 2s = another 25,000,000 playlist GETs/sec.
=> ~50M HTTP GETs/sec at peak, almost all for the SAME newest segment/playlist.
This is why ~99.9%+ must be served from the CDN edge, and why the newest object
is a brutal hot key: 25M clients want seg_N the instant it is published.
Bandwidth (the actual product cost):
Mixed ABR ladder, India-heavy mobile audience, avg ~2.5 Mbps delivered
(many on 480p/720p mobile, some on 1080p/4K TV).
50M * 2.5 Mbps = 125,000,000 Mbps = 125 Tbps of egress at peak.
Served ~entirely from CDN edges inside India/region. Origin egress must be a
tiny cache-fill fraction. 125 Tbps is a national-internet-scale number;
it only works because every viewer in a PoP shares the same cached segment.
Ingest and transcode compute (continuous, real time):
1 contribution feed (say 1080p50 at ~20-40 Mbps, or a 4K feed higher), dual for redundancy.
Transcode into a ladder of ~6 renditions (240p,360p,480p,720p,1080p, +4K for premium)
x maybe 2 codecs (H.264 for reach, HEVC/AV1 for efficiency on capable devices).
Live transcode MUST run at >= 1x realtime (it cannot fall behind a live feed, ever).
~6 renditions * ~2 codecs = ~12 real-time encodes running continuously for the match.
One feed => a modest, fixed GPU/ASIC encoder cluster PER feed, sized for peak quality,
NOT an autoscaled batch fleet (there is nothing to batch; it is one continuous stream).
The encode fleet scales with NUMBER OF CONCURRENT EVENTS, not number of viewers.
DVR / live window storage:
Keep a rolling DVR window of, say, 30 min for rewind + a full recording for VOD.
All renditions, 2s segments: segments/min = 30. For 12 rendition-codec combos,
DVR buffer is small (minutes of video * ~3 MB/s aggregate) - MBs to low GBs, hot.
Full-match recording for VOD/highlights: a few hours * ladder bitrate, tens of GB,
written to durable object storage in parallel - cheap, not on the hot path.
The spike within the spike (a wicket / a six):
A key moment does NOT change steady segment QPS much (everyone is already watching),
but it triggers correlated behaviour: late joiners rush in, people un-pause,
switch to higher quality, and the CONTROL plane (joins, quality switches, chat)
spikes hard. The real synchronized spike is at EVENT BOUNDARIES
(toss, innings break ending, final over) when millions join in the same minute.
The takeaways: ingest and transcode are a small, fixed, real-time cluster per event (scales with events, not viewers, and can never fall behind live); the read side is a ~50M-GETs/sec firehose where almost every request is for the single newest segment, which lives or dies on CDN edge hit rate and hot-object handling; bandwidth is 125 Tbps of region-local egress that only closes because everyone shares the same cached bytes; and the control plane (joins, auth, manifests) sees sharp synchronized spikes at event boundaries. Those four - real-time ingest/transcode, the newest-segment hot key, the regional CDN, and the flash-crowd control plane - are the interview.
High-Level Design (HLD)
The architecture splits along the live seam: a single real-time production pipeline (ingest, transcode, package - continuous, can’t-fall-behind, one per event) feeds a massive fan-out delivery pipeline (manifest + segment reads - identical bytes, synchronized, served from regional edges to tens of millions). The stadium encoder pushes a contribution feed in; a live transcoder produces the ABR ladder and packages it into tiny segments plus a rolling manifest; those segments are published to an origin fronted by a deep regional CDN; the ABR player chases the live edge, refetching the manifest and pulling the newest segment from the nearest PoP.
PRODUCTION PIPELINE (real time, continuous, one per live event)
┌───────────┐ RTMP/SRT ┌───────────────┐ ┌────────────────────────────┐
│ Stadium │ (dual feeds, │ Ingest / │ │ Live Transcoder │
│ broadcast │ redundant) │ Contribution │──>│ (real-time encode ladder: │
│ encoder │─────────────>│ Endpoint │ │ 240p..4K x H.264/HEVC/AV1) │
└───────────┘ │ (auth, dedup,│ └──────────────┬─────────────┘
│ failover) │ │ renditions
└───────┬───────┘ ▼
│ tee ┌────────────────────────────┐
▼ │ Packager (LL-HLS/LL-DASH: │
┌───────────────┐ │ 2s segments + rolling │
│ Archive writer│ │ media playlists; chunked) │
│ (durable VOD │ └──────────────┬─────────────┘
│ recording) │ │ segments + manifest
└───────────────┘ ┌──────────────▼─────────────┐
│ ORIGIN (live edge store: │
│ rolling DVR window, hot) │
└──────────────┬─────────────┘
──────────────────────────────────────────────────────────────┼──────────────
DELIVERY PIPELINE (fan-out, synchronized, tens of millions) │ cache-fill (rare)
▼
┌──────────┐ 1. join: auth+entitle ┌──────────────┐ ┌──────────────────────┐
│ Viewer │──────────────────────> │ Playback API │ │ Origin Shield │
│ (ABR │ 2. GET media playlist │ (entitlement,│ │ (regional, collapses │
│ player, │ <--- manifest URL │ session, │ │ edge misses) │
│ chasing │ │ token) │ └──────────┬───────────┘
│ live │ 3. GET playlist + seg └──────────────┘ │ hit
│ edge) │<==== from EDGE PoP =========================> ┌───────▼────────┐
└────┬─────┘ (99.9%+ hit; near viewer) │ CDN EDGE PoPs │
│ 4. QoE / heartbeat beacons │ (deep, regional│
▼ │ India-heavy) │
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ └────────────────┘
│ Beacon gate │──>│ Event Stream │──>│ QoE / CCV │
│ (sample,auth)│ │ (Kafka) │ │ Analytics │
└──────────────┘ └──────────────────┘ │ (rollups) │
└──────────────┘
Ingest flow (the match starts):
- The stadium’s broadcast encoder pushes one (ideally two, hot/hot redundant) contribution feed over RTMP or SRT to a geographically-close ingest endpoint. The endpoint authenticates the stream key, picks the healthy feed, and tees the stream: one copy to the transcoder, one copy to the archive writer that durably records the full match for VOD and highlights.
- If the primary feed drops (satellite hiccup, encoder crash), the ingest layer fails over to the backup feed within a segment or two. This is the only place where losing the input is unrecoverable, so it is doubly redundant end to end.
Production flow (continuous, real time):
- The live transcoder decodes the contribution feed and re-encodes it, in parallel and at >= 1x realtime, into the full ABR ladder (240p…1080p, 4K for premium) across the delivery codecs (H.264 for universal reach, HEVC/AV1 for efficient high-res). It must never fall behind the live clock, so it runs on fixed, over-provisioned GPU/ASIC encoders sized for the hardest content.
- The packager cuts each rendition into short (~2s) segments aligned at keyframes, and maintains a rolling media playlist (LL-HLS
.m3u8/ LL-DASH.mpd) that lists the most recent segments and (for low latency) exposes partial segments / chunked transfer so the player can start a segment before it is fully written. - Segments and the ever-updating manifest are pushed to the live origin, which keeps only a rolling DVR window (e.g. last 30 min) hot and lets older segments spill to the VOD archive. The origin is fronted by the CDN.
Playback flow (a viewer joins, online):
- The client calls the Playback API: it checks entitlement (is this user allowed to watch this match in this region), creates a session, and returns the master manifest URL plus a short-lived CDN token. This is the control-plane hot path during the ramp.
- The ABR player fetches the master manifest (ladder), picks a conservative starting rung for fast join, then begins chasing the live edge: every segment duration it re-fetches the small media playlist to learn the newest segment, then fetches that segment from the nearest edge PoP. It steps the ladder up or down per segment on bandwidth and buffer.
- Because the newest segment is identical for everyone, ~99.9% of segment and playlist GETs are served from the edge cache; the rare edge miss collapses onto a regional origin shield so the live origin is touched a handful of times per segment, not 25M times.
- In parallel the player fires QoE/heartbeat beacons (rebuffer events, bitrate, latency-to-live, concurrent-viewer pings) to a beacon gate that lands them on the event stream; a stream job rolls them up into the real-time CCV and QoE dashboards the operations team watches like a hawk during the event.
The key insight: the production side is small and fixed (one real-time pipeline per event, scaling with the number of concurrent matches), while the delivery side is enormous and synchronized (one object, tens of millions of simultaneous reads). Everything on the read path is engineered so that 50M requests for the same 2-second segment collapse into one cache fill - that collapse, not raw bandwidth, is what makes 50M concurrent possible.
Component Deep Dive
The hard parts, naive-first then evolved: (1) low-latency live packaging - how to be <10s behind without the buffer that normally absorbs jitter; (2) the newest-segment hot key - how 50M synchronized requests for one object do not melt the origin; (3) the regional CDN and the flash-crowd ramp - how a deep edge survives the audience doubling in 30 seconds; (4) the control plane under a synchronized join spike.
1. Low-latency live packaging: why classic HLS fails, then LL-HLS with partial segments
Naive approach: reuse standard VOD-style HLS - 6-10 second segments, a media playlist listing them, player keeps a 3-segment buffer before it starts.
Where it breaks:
- Latency is structurally huge. Classic HLS tells the player to buffer ~3 segments before playback. With 6s segments that is ~18-30s of latency baked in before you count encode and network. You are 30s behind live; the neighbour’s TV cheers first; the stream is spoiled. Sports is unforgiving about this in a way a lecture video is not.
- The player can only see a segment once it is fully written. With 6s segments, the newest playable content is always at least one full segment old, and the player polls the playlist on a coarse cadence, adding more lag.
- Shrinking segments naively trades one problem for another. Just dropping to 1s segments cuts latency but multiplies request count (6x the GETs and 6x the playlist churn) and inflates per-segment HTTP overhead - bad when you already face 50M GETs/sec.
Evolution: Low-Latency HLS / LL-DASH with partial segments (CMAF chunks) and blocking playlist reloads. Keep a reasonable segment duration (~2s) for caching efficiency, but split each segment into much smaller parts / CMAF chunks (~200-500ms) that are published and fetchable the instant they are encoded, via HTTP chunked transfer. The player plays partial chunks as they arrive and keeps a tiny buffer.
Classic HLS: [==== seg (6s) ====][==== seg ====] player buffers 3 segs => ~18-30s behind
^ not visible until fully written
LL-HLS: [p|p|p|p] [p|p|p|p] [p|p|... 2s segment = 4 x ~500ms PARTS
^ each ~500ms part is published & playable immediately (chunked transfer)
player holds ~1-2s buffer, tracks the newest part => ~3-6s behind live
Playlist: media playlist advertises parts as they appear; player uses a
BLOCKING reload (long-poll): "give me the playlist once part N+1 exists"
instead of dumb polling => no wasted polls, minimal time-to-next-part.
The moves that make <10s work:
- Parts decouple latency from caching. The part (~500ms) is the latency unit (how fresh the newest playable content is); the segment (~2s) is the caching unit (what the CDN stores as one object). You get low latency from small parts and cache efficiency from fatter segments, instead of being forced to pick one.
- Chunked transfer encoding means the CDN and player receive a segment’s bytes as it is being produced, not after it is complete - the encoder, packager, origin, and edge all stream the same open HTTP response. The segment is “in flight” through the whole chain.
- Blocking playlist reload (long-poll) replaces busy-polling: the player asks for the playlist that contains part N+1 and the server holds the request until that part exists. No wasted playlist fetches, and the player learns about new content the instant it is ready.
- The buffer is the enemy and the safety net. A deeper buffer hides network jitter but adds latency; a shallow buffer is low-latency but rebuffers on a hiccup. LL-HLS runs a deliberately shallow buffer (~1-2s) and leans on fast ABR down-switching to avoid stalls instead of leaning on buffer depth. On a genuinely bad network the player falls further behind live (bigger buffer) rather than stalling - graceful degradation of latency, not of playback.
Trade-off stated plainly: pushing from ~30s to <10s (and toward 3-5s) is not free. Smaller parts and blocking reloads mean more requests and tighter timing, and a shallow buffer is less forgiving of jitter. We accept that complexity because in live sports, latency is the product.
2. The newest-segment hot key: why pull-through caching stampedes, then request collapsing
Naive approach: standard pull-through CDN - each edge, on a cache miss, fetches the object from origin; rely on caching to protect origin.
Where it breaks:
- The newest segment is a cache miss for everyone at once. When the packager publishes
seg_N, it is in no cache yet. Within the same ~200ms, millions of players (who all just reloaded the playlist and sawseg_N) request it. Every edge PoP has a miss simultaneously, and naively every one of those misses - potentially every concurrent request - punches through to origin. This is a thundering herd on a brand-new object, repeating every 2 seconds, forever, for the life of the match. - Caching does not help a never-before-seen object. The thing that makes VOD easy (the object has been cached for hours) is exactly absent here: the hot object is always ~0 seconds old.
- Origin cannot take 25M GETs/sec. No origin fleet serves the raw firehose; if even 0.1% of 25M/sec leaks through that is 25,000 origin RPS for a single object, times every edge, every 2 seconds.
Evolution: request collapsing / coalescing at every tier, plus a tiered shield, plus push pre-warming.
25M concurrent requests for seg_N
-> EDGE PoP: request COLLAPSING. The first request for seg_N triggers ONE
origin-bound fetch; all other concurrent requests for seg_N on that edge
are PARKED and served from that single in-flight fill. 100k requests -> 1 fetch.
-> ORIGIN SHIELD (regional): the many edges' single fetches collapse AGAIN.
All edges in a region share one shield; the shield makes ONE fetch to origin.
-> ORIGIN: sees ~1 request per segment per region. 25M/sec -> single digits/sec.
The levers:
- Request collapsing (single-flight) at the edge. When many clients ask an edge for the same not-yet-cached object, the edge issues one upstream fetch and holds the other requests, then fans the one response out to all of them. This turns a per-PoP herd of 100k into a single upstream request. It is the single most important mechanism for live at scale, and it works precisely because every request is byte-identical.
- A tiered origin shield collapses again. Edges do not hit origin directly; they hit a regional shield layer. Every edge in a region funnels its one request through the shield, which collapses them into one origin fetch per segment per region. Two collapse tiers take 25M/sec down to a handful of origin reads per segment.
- Chunked transfer makes collapsing stream, not block. Because the segment is delivered chunked, the single in-flight origin fetch can stream its chunks out to all parked clients as they arrive - the parked clients are not waiting for the whole segment, they get bytes as the one upstream fetch gets them. Low latency and collapsing coexist.
- Push / pre-publish the newest segment to shields. Rather than wait for a client to pull
seg_Nand trigger a fill, the packager can push each new segment (and playlist update) to the regional shields the instant it is produced, so by the time clients ask, the shield already has it and only the edge fill remains. This converts a pull-stampede into a controlled push fan-out of a few hundred shield fills. - Immutable, versioned segment URLs. Each segment has a unique URL and never changes, so caching is trivially safe; the only “mutable” object is the media playlist, which is tiny and gets a very short TTL (sub-segment) plus collapsing of its own. A short-TTL playlist is the one object you accept re-fetching constantly; keeping it tiny (a few lines) makes that cheap.
The payoff: the thundering herd on the newest segment, which is the thing that actually kills naive live systems, is defeated by collapsing identical requests at two tiers so the origin sees single-digit RPS per segment regardless of whether 5M or 50M are watching. Concurrency scales almost for free once every viewer is asking for literally the same bytes and the edge is smart enough to ask upstream only once.
3. Regional CDN and the flash-crowd ramp: why a thin global CDN fails, then a deep regional edge with pre-provisioning
Naive approach: lean on a general-purpose global CDN with its default capacity, pull-through, autoscaling as traffic grows.
Where it breaks:
- Autoscaling cannot keep up with a 30-second doubling. The audience goes from 20M to 40M in the minutes around the toss or the start of the chase. Edge servers, NICs, and transit links cannot be spun up reactively in seconds; by the time autoscaling reacts, the final over is over. Capacity for a known event must be reserved and warmed in advance, not discovered at runtime.
- 125 Tbps is not generic spare capacity. That is a nation-scale slice of internet egress, concentrated in one country, in one hour. It requires pre-negotiated capacity with the CDN and with ISPs, not whatever the CDN happens to have free.
- Distance kills low latency. Serving an Indian audience from PoPs in Europe or the US adds 150-250ms RTT per fetch - fatal when you are chasing a <10s live edge with 500ms parts. The edge must be inside the audience’s region and ISPs.
Evolution: a deep, regional, pre-provisioned multi-tier CDN with ISP-embedded caches.
Viewer (few ms, same ISP) ─> ISP-EMBEDDED edge cache (inside the telco network)
│ miss
▼
METRO / EDGE PoP (many, inside region)
│ miss (collapsed)
▼
REGIONAL ORIGIN SHIELD (few, per region)
│ miss (collapsed, rare)
▼
LIVE ORIGIN (one logical, DVR window)
The design levers:
- Embed caches inside the ISPs (the deepest tier). For a country-scale event, caches live inside the major telco / ISP networks (open-caching / embedded edge nodes). Most Indian viewers are on a handful of large mobile carriers; a cache inside the carrier means segment bytes never even cross the ISP’s peering boundary, cutting both latency and the carrier’s transit cost. This is how 125 Tbps becomes physically deliverable - it is served from inside the access networks, not shipped to them.
- Pre-provision and pre-warm for a known event. The match time is known days ahead. Reserve edge and transit capacity, pre-position the manifest and the first segments, pre-establish the push paths to shields, and run dry-run load tests at target concurrency before the event. Nothing about the peak is a surprise; the system is scaled for 50M before the first viewer joins.
- Shed load gracefully under the ramp. If a region approaches capacity, the system caps the top of the ABR ladder (temporarily disable 4K/1080p, push everyone to 720p and below) to cut per-viewer bitrate. Dropping average bitrate from 2.5 to 1.5 Mbps cuts 40% of egress instantly. Better that everyone watches 720p than that 10% get 4K and the rest buffer. This is a deliberate, central quality-governor lever the ops team can pull.
- Keep the control plane regional and cached too. Entitlement checks and session creation are regionally deployed and lean (a token the edge can validate locally), so the join spike does not cross regions.
- Multi-CDN for headroom and failover. No single CDN has clean 125 Tbps regional headroom; traffic is spread across multiple CDNs (and the ISP caches) with a steering layer that shifts load away from a degrading CDN in real time. A CDN brown-out during the final over reroutes rather than fails.
The payoff ties straight to the NFR budget: a deep regional edge embedded in the ISPs keeps latency at single-digit-ms access-network RTT and serves 125 Tbps from inside the carriers; pre-provisioning means the 30-second doubling hits already-warm reserved capacity instead of a cold autoscaler; and the quality governor is the release valve that trades resolution for stability when a region is genuinely saturated. The CDN here is not a cache you bolt on - it is a pre-built, region-shaped delivery grid sized for one night.
4. The control plane under a synchronized join spike: why synchronous auth fails, then tokenized edge validation
Naive approach: every join and every manifest refresh hits a central Playback API that does a database lookup for entitlement and session state.
Where it breaks:
- Joins are synchronized, not smooth. At the toss, millions join in the same minute: ~100k+ joins/sec, each doing an auth + entitlement DB read. A synchronous central check on every join overwhelms the API tier and the entitlement DB exactly when it matters most.
- Per-segment manifest fetches would multiply it. If every 2-second playlist refresh also hit the central API for a fresh auth check, that is 25M auth checks/sec - absurd. Authorization must happen once at join, not per segment.
- A hot event is a hot row / hot key in the entitlement store. Everyone checking “am I entitled to the IPL final” hammers the same entitlement record.
Evolution: authorize once at join, issue a signed short-lived token the edge validates statelessly, and cache entitlement aggressively.
Join (once per viewer):
Playback API: validate user + entitlement (cached) -> issue SIGNED CDN TOKEN
token = { user_id, event_id, region, exp (minutes), allowed_renditions }, HMAC-signed
return manifest URL with the token.
Steady state (every 2s, 25M/sec):
player fetches playlist + segments from EDGE with the token.
EDGE validates the token's signature + expiry LOCALLY (no call home).
No central auth on the hot path. Token expires -> player silently refreshes it
via a cheap token-refresh endpoint (rate-limited), not a full re-auth.
The moves:
- Stateless signed tokens move auth off the hot path. The edge checks an HMAC signature and expiry with no network call. A short expiry (a few minutes) bounds abuse; refresh is cheap and infrequent. The 25M/sec steady segment traffic does zero central auth work.
- Cache entitlement, do not query it per join. Entitlement for “this event, this region, this subscription tier” is highly cacheable; the join path reads a cache, not the system-of-record DB, so the synchronized join spike hits an in-memory cache that scales horizontally, with the DB behind it as a cold backstop.
- Admission control / waiting room for the extreme ramp. If joins exceed provisioned control-plane capacity, a virtual waiting room / token-bucket admission meters new joins (already-watching viewers are never evicted) so the control plane degrades gracefully instead of collapsing - the classic flash-sale admission pattern applied to a flash crowd of viewers.
- Beacons are fire-and-forget and sampled. QoE and CCV beacons go to a separate ingest gate on the event stream, sampled under extreme load (you do not need all 50M heartbeats to know CCV and rebuffer rate; a 1% sample is statistically plenty), so telemetry never contends with delivery.
The result: the control plane does real work once per viewer at join and nearly nothing per segment thereafter; a signed token turns 25M segment requests/sec into pure stateless edge reads; and admission control plus cached entitlement keep the synchronized join spike from taking down the one thing that lets people in.
API Design & Data Schema
Most traffic is playlist refreshes + segment GETs (served by the CDN, not these APIs) plus QoE beacons; the APIs below handle the join handshake, token refresh, and ops controls.
REST / RPC + manifest endpoints
# --- Join handshake (control plane; once per viewer) ---
POST /api/v1/live/{event_id}/join
Headers: Authorization: <user session>
Body: { "device":"android", "region":"IN-MH", "drm":"widevine" }
-> checks entitlement (cached), creates session, issues signed CDN token.
Response 200: { "event_id":"ipl_final_2026", "status":"live",
"master_manifest":"https://cdn/live/ipl_final_2026/master.m3u8?t=<token>",
"cdn_token":"<hmac-signed>", "token_exp": 1793000000,
"latency_target_s": 6, "dvr_window_s": 1800 }
# status may be "waiting_room" with { retry_after_s } under extreme admission load.
POST /api/v1/live/{event_id}/token/refresh
Body: { "cdn_token":"<expiring token>" }
-> re-signs without a full re-auth (rate-limited).
Response 200: { "cdn_token":"<new>", "token_exp": 1793000300 }
# --- Manifests + segments (served by CDN, token-validated at edge) ---
GET https://cdn/live/ipl_final_2026/master.m3u8?t=<token>
-> master playlist: the ABR ladder (variant playlists per rendition). Short TTL.
GET https://cdn/live/ipl_final_2026/720p/media.m3u8?t=<token>
-> rolling MEDIA playlist: recent segments + PARTS, live edge, DVR window.
Supports LL-HLS blocking reload: ?_HLS_msn=820&_HLS_part=3 (hold until it exists).
Cache-Control: max-age=1 (tiny, constantly changing). The hot playlist.
GET https://cdn/live/ipl_final_2026/720p/seg_00820.m4s?t=<token> # a SEGMENT
-> immutable once complete; chunked-transfer while being produced.
Cache-Control: public, max-age=60, immutable. Request-collapsed at edge+shield.
GET https://cdn/live/ipl_final_2026/720p/seg_00820.3.m4s?t=<token> # a PART (~500ms)
-> the low-latency unit; published the instant it is encoded.
# --- QoE / concurrency telemetry (fire-and-forget, sampled) ---
POST /api/v1/live/{event_id}/beacon
Body: { "session_id":"s_91", "bitrate_kbps":2500, "latency_to_live_s":5.2,
"rebuffer_ms":0, "state":"playing", "ts": 1793000012 }
-> 202 Accepted; lands on the event stream. No synchronous write.
# --- Ops / broadcast control (internal) ---
POST /internal/live/{event_id}/quality-governor
Body: { "max_rendition":"720p", "region":"IN" } # shed load: cap the ladder
POST /internal/live/{event_id}/failover
Body: { "ingest_feed":"backup" } # switch contribution feed
Data store schemas
Live is mostly not a database problem - the hot data is ephemeral segments in blob/edge storage, not rows. The stores that matter are small: event/session metadata, entitlement, and analytics rollups.
1. Event & Session metadata - SQL / NewSQL, keyed by event_id / session_id. Small, structured, read-heavy at join. The event record is read by every join, so it is cached hard.
Table: live_events
event_id STRING PRIMARY KEY
title TEXT
status ENUM(scheduled, live, paused, ended)
ingest_primary TEXT -- contribution endpoint / stream key ref
ingest_backup TEXT
origin_base_url TEXT -- live origin / CDN path
dvr_window_s INT
latency_target_s INT
ladder JSON -- [{res, codec, bitrate_kbps}, ...] (current, governable)
start_at TIMESTAMP
region_policy JSON -- allowed regions / blackout rules
Table: sessions -- ephemeral, TTL'd, often in Redis not SQL
session_id STRING PRIMARY KEY
user_id STRING INDEX
event_id STRING INDEX
region STRING
device STRING
created_at TIMESTAMP
TTL -- expires after event + grace; sessions are disposable
2. Entitlement store - cached KV / SQL, keyed by (user_id,event_id) or by subscription tier. The join path reads this through an aggressive cache; it is the system-of-record for “allowed to watch”.
Key: entitlement:{user_id}:{event_id} -> { allowed: true, tier:"premium",
allowed_renditions:["<=4k"], region:"IN" }
Cached in Redis with minutes TTL; backed by the subscriptions DB.
Hot key "is anyone entitled to ipl_final" is avoided by keying per user,
and by caching the TIER rule (most users share a few tier->allowed mappings).
3. Live segment / DVR store - object storage + edge, immutable, keyed by path. The actual video. Not a database; a rolling window on fast storage fronted by the CDN, with completed segments also teed to the durable VOD archive.
Key: live/ipl_final_2026/720p/seg_00820.m4s -> bytes (immutable segment)
Key: live/ipl_final_2026/720p/media.m3u8 -> rolling playlist (short TTL)
Hot DVR window (last ~30 min) kept on fast storage/edge;
full match teed to vod/ipl_final_2026/... in durable object storage (11 nines)
for post-match replay and highlights (handed to the VOD pipeline).
4. QoE / CCV analytics - time-series / columnar (Druid / ClickHouse / Bigtable), rollups by event + region + time bucket. Written by the stream processor from sampled beacons; read by the real-time ops dashboard.
Table: ccv_rollups -- concurrent viewers & QoE per (event, region, bucket)
event_id STRING PARTITION KEY
region STRING CLUSTERING KEY
bucket STRING CLUSTERING KEY -- 2026-11-10T20:31 (per 10s)
concurrent BIGINT -- estimated CCV (from sampled pings * 1/rate)
avg_bitrate INT
rebuffer_ratio DOUBLE
avg_latency_s DOUBLE
joins, leaves BIGINT
Why these choices: event and session metadata is small and relational with a clean read at join, so SQL/NewSQL fronted by a cache (and Redis for disposable sessions). Entitlement is a cached KV lookup keyed per user with tier-rule caching to kill the hot key. Segments are immutable, ephemeral, streamed - object storage + edge, where the path is the identity and a database would be catastrophic. QoE/CCV is append-heavy, aggregate-read, approximation-tolerant - a time-series/OLAP store fed by sampled rollups, never per-beacon rows. Each layer gets exactly the consistency and cost model its access pattern needs; the heavy lifting is in the delivery grid, not in any database.
Bottlenecks & Scaling
Where it breaks first, in order, and the fix for each:
1. The newest-segment thundering herd (breaks first, every 2 seconds). The instant seg_N is published, 25M clients request an object that is in no cache.
Fix: request collapsing (single-flight) at the edge and again at a regional shield, so per-PoP herds of 100k become one upstream fetch and all edges in a region collapse to one origin fetch per segment. Push/pre-publish new segments to shields so they are warm before clients ask. Chunked transfer lets the single in-flight fetch stream to all parked clients. This takes 25M/sec to single-digit origin RPS per segment and is the mechanism that makes concurrency scale almost for free.
2. CDN / ISP egress capacity (125 Tbps, region-concentrated). Fix: a deep regional CDN with caches embedded inside the major ISPs (open caching), so bytes are served from inside the access networks; pre-provision and pre-warm reserved capacity for the known event; spread across multiple CDNs with real-time steering. The quality governor caps the ABR ladder (force 720p) to shed egress when a region saturates - trading resolution for stability on purpose.
3. Live transcode falling behind real time. The encoder cannot ever lag the live feed; a slow encode compounds forever. Fix: fixed, over-provisioned GPU/ASIC encoders sized for the hardest content (not autoscaled - there is nothing to batch), with a hot-standby transcoder ready to take over. Scale is by number of concurrent events, not viewers, so this cluster is small and predictable. Prioritize producing the common rungs (360p/720p) first; 4K can lag or be shed.
4. Ingest / contribution feed loss. The one input you cannot recreate. Fix: dual hot/hot contribution feeds (independent paths, e.g. fibre + satellite) with automatic failover within a segment or two, plus the archive tee recording durably in parallel. This is the only truly unrecoverable point, so it is the most redundant.
5. Synchronized join spike on the control plane (toss, start of chase). ~100k+ joins/sec of auth + entitlement. Fix: authorize once at join, issue a signed short-lived token the edge validates statelessly so per-segment traffic does zero central auth; cache entitlement (and tier rules) so the join spike hits memory not the DB; admission control / waiting room meters new joins under extreme load without ever evicting current viewers.
6. The media playlist as a hot mutable object. Unlike immutable segments, the playlist changes every part and everyone re-reads it. Fix: keep it tiny (a few lines), give it a sub-second TTL, and collapse its requests at the edge like segments; use blocking reload so clients fetch it exactly when there is new content rather than busy-polling. A small, collapsed, short-TTL object is cheap to serve 25M times/sec.
7. Hot event = hot entitlement/CCV key.
Fix on entitlement: key per user_id and cache the shared tier->allowed mapping so “is anyone entitled” is never one hot row. Fix on analytics: sample beacons (1% is plenty for CCV/QoE), partition the event stream by event_id and sub-partition the single hot event across shards, summing shard counts - the classic hot-key split. HyperLogLog-style estimation for unique viewers.
8. Shard / partition keys, stated plainly. Event & session metadata -> event_id / session_id (read at join, cached). Entitlement -> user_id (never the event, to avoid a hot row). Segment store -> content path live/{event}/{rendition}/seg_n (the path is placement; immutability makes it collapsible and cacheable). Event stream + CCV store -> event_id, sub-partitioned for the one hot event (sharding beacons by user_id would scatter one event’s CCV across every partition and make aggregation a cross-shard join). The segment path is the unit of delivery identity; the event is the unit of analytics; the user is the unit of entitlement.
9. Single points of failure. Ingest is dual-fed with failover. Transcoder/packager run with a hot standby. The origin is replicated and only holds a rolling window (loss of an old segment is irrelevant; the live edge has moved on). CDN is inherently redundant (thousands of PoPs + ISP caches + multi-CDN steering; a dead edge routes to the next). Playback API/token services are stateless and horizontally scaled behind load balancers. No single box whose loss drops the broadcast for everyone.
10. Multi-region and blackouts. The delivery grid is region-shaped: each region serves from its own embedded edges and shield, so a region’s load and failures are contained. The production pipeline runs close to the stadium with cross-region distribution of the produced segments. Blackout/geo rules (rights are sold per territory) are enforced at the entitlement check and encoded in the token, so an out-of-region request is rejected at join and at the edge. Latency is always served from the viewer’s own region; only the produced stream crosses regions, once, into each region’s shield.
Wrap-Up
The trade-offs that define this design:
- Latency is the product, so the buffer is the enemy. Classic HLS’s comfortable 3-segment buffer (~30s behind) is rejected outright; LL-HLS with ~500ms parts, chunked transfer, and blocking playlist reloads gets us under 10s and toward 3-5s, at the cost of more requests, tighter timing, and a shallow buffer that degrades latency rather than playback under jitter. In a different domain you would buy smoothness with buffer; in live sports you cannot.
- Concurrency is free only if every viewer asks for identical bytes - so make the edge ask upstream once. The defining mechanism is request collapsing at the edge and a regional shield, which turns 25M synchronized requests for the brand-new segment into single-digit origin RPS. Naive pull-through caching was rejected because the hot object is always ~0 seconds old and would stampede the origin every 2 seconds.
- Produce once in real time; fan out enormously. The production side is a small, fixed, over-provisioned real-time pipeline per event (scales with concurrent matches, never falls behind live, dual-fed ingest); the delivery side is a pre-provisioned, ISP-embedded regional CDN sized for 50M before anyone joins. Reactive autoscaling was rejected because a 30-second audience doubling outruns any autoscaler - capacity for a known event is reserved and warmed in advance.
- Shed quality, never playback. When a region saturates, the quality governor caps the ABR ladder (everyone to 720p) to cut egress 40% instantly, because everyone watching 720p beats 10% getting 4K while the rest buffer. Graceful degradation is a deliberate central lever, not an accident.
- Authorize once, trust a token forever after. A signed short-lived token validated statelessly at the edge keeps 25M segment requests/sec doing zero central auth; cached entitlement and admission control keep the synchronized join spike from collapsing the one thing that lets people in.
One-line summary: a live sports platform that ingests a dual-redundant stadium feed, transcodes it in real time into a low-latency ABR ladder packaged as ~500ms parts for sub-10s glass-to-glass latency, and fans the single newest segment out to 50M concurrent viewers through a pre-provisioned, ISP-embedded regional CDN where request collapsing at the edge and a regional shield turn a 25M-requests-per-second thundering herd into single-digit origin reads - so the expensive work is produced once per few seconds in real time and the read path survives the IPL-final flash crowd by making every viewer ask for literally the same bytes.
Comments