HotShard

Design a Video Streaming Service

A creator uploads one video. Later, millions of viewers around the world want to watch it, each on their own screen, each on their own network, whenever they choose. Build the system that takes the uploaded video, gets it ready to play, and streams it to every one of those viewers smoothly — starting fast, never stalling, and adjusting to whatever connection each viewer happens to have. This is video on demand: upload once, watch later, watch anywhere, watch a lot.

Predict ~1.5 h · Read ~50 min

The problem i

A video service looks like a storage problem and turns out to be a delivery problem. The instinct is to worry about where all that video lives, because video files are big and there are a lot of them. Hold that instinct, because it is pointing at the wrong number. Everything hard about this system turns on two facts, and neither one is about how much disk you buy.

Here is the first hard fact. Storing the library is the easy part. Do the arithmetic and it comes out small. A region's whole catalog is a few thousand titles. Each title, encoded and ready to serve, is a handful of gigabytes. Multiply it out and one region's ready-to-play library is tens of terabytes — a few racks, not a data center. Even when you generously account for many languages, subtitle tracks, and a global footprint, it still lands well under a petabyte. That is a real number, but it is a small number, and no single decision in this design is forced by it. Storing the videos is never the wall.

Here is the second hard fact, and it is where the wall actually is. The same video is watched an enormous number of times, and every one of those views is bytes leaving your system. Watching dwarfs uploading. On television screens alone, in one country, viewers watch more than a billion hours a day, while creators upload something like 500 hours a minute — about 720k hours a day. That is watching outrunning uploading by roughly a thousand to one. Now turn those watch-hours into bytes on the wire. Every viewer is pulling a steady stream of video — a few megabits every second, continuously, for as long as they watch. Add up a large audience all watching at once and you are pushing terabits per second out of your system. No single server can push bytes that fast. A server's link to the internet tops out orders of magnitude below what a large regional audience demands. So the wall is not disk and it is not requests per second. It is outbound bandwidth, or egress: the sheer rate of bytes leaving the building. Reconciling that firehose with the physical limit of any one machine is the force that shapes the whole design.

Those two facts flip the problem. The scary-sounding part, storage, is the small number. The boring-sounding part, serving the bytes, is the wall. Getting one video ready to play is the easy half. Serving it to millions of people at once, over connections you do not control, is the whole game.

There is a third strand, quieter but real, on the way in. Getting a video ready to play is itself work, and it cannot happen the instant an upload lands. One source video has to be turned into several versions at different qualities so a viewer on a weak connection and a viewer on a strong one can each get a watchable stream. That conversion is heavy, the uploads arrive fast, and a viewer who just hit publish should not sit and wait for it. So the upload path has its own shape to work out. But it is the read path — the bytes going out — that carries the wall, and that is where the design starts once one server stops being enough.

Two timings are worth pinning now, because decisions later turn on them. A durable write to disk, the kind that has to survive a crash, takes about 10 ms. Fetching a few seconds of video from a cache sitting close to the viewer is a few milliseconds — one local read. The gap between close to the viewer and back at the origin is the gap the whole read path is built around.

Functional requirements what it must do

  • Accept a creator's uploaded video and get it ready to play.
  • Play a video for a viewer, starting fast and without stalling.
  • Adapt the quality to each viewer's connection, without them managing it.
  • Serve the same popular video to a huge audience at once.
  • Store every ready-to-serve video durably.

Non-functional requirements what it must be

  • Sustain terabits per second of outbound video, with no single machine as the bottleneck.
  • Keep starting playback fast wherever the viewer is.
  • Do not make a creator wait for heavy processing.
  • Keep serving during a partial failure.

Out of scope left out on purpose

  • The encoding algorithm itself
  • Live streaming
  • Content protection
  • What to recommend and what to show next
  • Advertising and monetization

How the design works

The finished design, explained one piece at a time: first the contract the system serves, then the smallest version that works, then the version that survives scale — walked use case by use case, one deep dive at a time, with the finished diagram at the end.

part 1 The single-server version

Start with the smallest thing that works: one server that holds an uploaded video and streams it back.

Start with the vocabulary. A creator hands the system an upload — one source video, a large file. A viewer later wants to watch it, using a player — the app or the browser tab on their screen. The player does not download the whole file and then start; it pulls the video in segments, short pieces fetched one after another over HTTP, so playback can begin after the first piece arrives. And the whole collection of videos the service offers is the catalog. Those words — an upload, a source video, a viewer, a player, a segment, the catalog — carry the whole problem.

The day-one design is direct. One server, call it the Origin, does everything. When a creator uploads, the origin stores the source video on its disk. When a viewer wants to watch, the origin reads that video off its disk and streams it back to the player, segment by segment, over HTTP. One box, one copy of each video, both jobs. For a single creator and a handful of viewers on the same continent, this is genuinely fine, and nothing clever is missing.

CreatorViewer · playerOriginstores + streamsone uplink · one qualityencodes inline
The whole system on day one.
Following a request
A creator's upload lands
  1. a creator uploads a source video; the Origin stores it on disk and, on one box, would process it inline before it could be watched~10 ms
A viewer watches
  1. a viewer watches; the Origin reads the video off its disk and streams it back, segment by segment, over its own single uplink~10 ms

One hop dominates every path: the client's request crossing the public internet to reach the datacenter. That hop is tens of milliseconds — call it about 30 — and it is set by physical distance, not by anything we build. Everything after it happens inside a single datacenter, where the network between two machines is well under a millisecond — so those numbers are the actual work, not the wait: a durable write to disk about 10 ms, an indexed read about 5 ms. A hop between two datacenters would cost 10–100× the in-datacenter number, which is why the whole path stays in one region.

It just does not survive being pointed at a real audience, and it fails in three separate ways — each of which forces a piece of the real design. The first failure is the one from the brief, and it is the big one. The origin can only push bytes as fast as its link to the internet allows, and that link is small compared to what an audience demands. Picture a video going viral: a hundred thousand people, then a million, all press play within the same hour. Every one of them is pulling a steady stream of a few megabits a second from that one server, continuously. Add them up and the demand is terabits per second. The origin's uplink is orders of magnitude below that. It does not slow down gracefully; it simply cannot move the bytes, and every viewer past the first few thousand gets a stream that stalls or never starts. One machine physically cannot be the source of a large audience's bytes.

The second failure is that the single stored video is the wrong thing to serve. The origin holds one copy at whatever quality the creator uploaded, but viewers are not all on the same connection. There is a word for how heavy a copy is: bitrate, meaning how much data one second of video costs. A higher bitrate looks sharper and takes more of the connection to pull. A viewer on fiber can take a high-bitrate copy. A viewer on a phone in a train tunnel cannot, and sending it to them anyway makes their player stall constantly. One stored quality cannot serve every connection. The third failure is the upload path itself: the origin accepts the whole gigabyte source in one shot, uploads drop halfway, and the single stored quality is not even what a viewer should be served, so real processing has to happen between the creator uploading and the viewer watching — and on one box that processing happens inline, while the creator waits and while it competes with serving. Those three failures point the same way: the bytes have to be served from many places close to the viewers, the single stored quality has to become several qualities with a way to pick between them, and the upload has to be accepted quickly and processed off to the side, durably. The rest of this design does exactly that, one forced decision at a time — and it starts with the wall.

The API

The whole product is these endpoints. Every box in the design below exists to serve one of them.

POST /videosA creator uploads a source video in chunks and gets back success once the bytes are safely stored, not once the video is watchable. Rare compared to playback; never on the hot read path.
contract ▾
# A creator begins a resumable upload, then sends the file in chunks.
 POST /videos { title: "…", sizeBytes: 2_100_000_000 }
 201 Created { uploadUrl: "/videos/v_82/chunks" }
 PATCH /videos/v_82/chunks   (chunk, resumes from the last received byte)
 201 Created                 # success = bytes are DURABLE, not = encoded
GET playA viewer's player asks for a video and gets back a small description of the available quality levels and where to fetch the stream's pieces — not the video itself.
contract ▾
# The player asks what exists before it fetches any bytes.
 GET /videos/v_82/manifest
 200 OK {
    levels: [ { bitrateKbps: 5800, … }, { bitrateKbps: 2350, … }, … ],
    segmentSeconds: 6
  }                              # a small manifest, not the video
READ segmentThe player then repeatedly fetches short pieces of the video, picking the quality level it can currently keep up with. This is the operation that happens billions of times a day.
contract ▾
# The player pulls the stream piece by piece, picking its own quality level.
 (from the nearest edge cache) GET /seg/v_82/q3/00042.ts
 200 OK  <~6 s of video>       # a cache hit, a few ms away — no per-viewer server decision

part 2 The version that survives scale

This section walks the design step by step, in the order you actually have to decide things. First, face the wall head-on: the outbound bytes cannot come from one origin, and fixing that is the heart of the whole design. Then work out what the serving tier actually caches and how a viewer on a variable connection gets a watchable stream — and, along the way, discover that storing the library was never the hard part. Then turn to the upload path: accept a creator's video fast, process it off to the side, and never lose it. And finally, see what happens when a whole region's control plane disappears, and why the serving tier keeps playing anyway. The finished diagram waits at the end.

Two ways through this section

The four decisions below are the heart of this design, and there are two ways to take them. In the interview room you make each one yourself and the board reacts to your pick. The write-ups after this point cover the same decisions in full — read them instead, or afterwards as reference.

use case 1 A million viewers at once, and the bytes cannot leave one box

On day one, every viewer's stream left through a single origin's uplink. That holds right up until one title gets popular — and then, as the brief warned, that one uplink becomes the wall the entire design has to get around.

deep dive 1 A million viewers at once, and the bytes cannot leave one box
The question

The origin works until an audience shows up. A popular video is being watched by a huge audience at once, spread across cities and countries. Every viewer is pulling a steady multi-megabit stream, continuously. Together they demand terabits per second of outbound bytes. One origin's link to the internet is orders of magnitude too small to push that. It does not slow down gracefully; past the first few thousand viewers, it simply cannot move the bytes. Where do the bytes come from?

Follow the obvious answer first, because it is what most people reach for: give the origin a bigger pipe. Put it on a fatter uplink, or add a few more origin servers behind a load balancer. It is the design you already have, scaled up, and the attraction is real. But the arithmetic kills it. ~500k people watching at once, each pulling ~3 Mbps, is ~1.5 Tbps from one region. A single high-end serving box tops out around ~100 Gbps. That regional demand is well over ~16 times what one box can push, on one mid-size region, on one popular title. A bigger pipe is not that much bigger. It is an order of magnitude short, and the gap widens with the audience. It also does nothing about distance: those viewers are spread across a continent, and serving them from one central location means every byte crosses the whole way, slow to start and expensive to carry.

A second instinct sounds sophisticated: put a cache in front of the origin. But a plain key-value cache is built to answer lookups faster. It remembers an answer so you do not recompute it. The origin's problem is not slow lookups. It is that bytes cannot leave the building fast enough, and a cache next to the origin still pushes every one of those bytes out the same regional uplink. It caches the wrong thing. The ceiling here is bandwidth, not lookup speed, and no amount of remembering answers moves more bytes through a fixed pipe. Neither a bigger pipe nor a cache in one place works. The fix is to spread serving across many appliances, each close to its viewers, so the aggregate rate is carried by the whole fleet and no single uplink is the ceiling.

A tier of caches close to the viewer — the CDN edge tier — with the origin serving only fills and misses. Stop serving the bytes from one place. Put the video on a tier of caches spread across the world, each one sitting close to where viewers actually are. A network of caches placed near viewers like this is called a content delivery network, or CDN, and this one is the CDN edge tier — edge because it sits at the outer edge of the network, nearest the people. Each location in it is called a PoP, short for point of presence, which is just a rack of cache machines parked somewhere close to viewers, very often inside the viewer's own internet provider. Those machines are called appliances: ordinary servers built to do one job, hold video and push it out fast. A viewer's player does not fetch segments from a distant origin. It fetches them from the nearest PoP, a short hop away. Here is the move that beats the arithmetic. The terabits of demand are no longer one number against one box. They are spread across hundreds of PoPs, each serving the viewers near it. That ~1.5 Tbps for one region becomes ~16 appliances working in parallel, each pushing its own ~100 Gbps to the viewers on its doorstep. Not one impossible origin pipe. Many small ones, each close to home.

CreatorViewer · playerOriginfills the edgeanswers missesCDN edgetier · PoPsPoP caches near viewers~90–100 Gbps/boxpopular titles from RAM
The board after this deep dive — “A viewer watches” traced, hop by hop. The numbered steps below walk the same path.

The origin does not disappear, but its job changes completely. It stops sitting in the viewer's path. Instead it fills the edge, pushing copies of the videos out to the PoPs ahead of time during off-peak hours, and it answers the rare miss: fetching a missing segment once, serving it, and keeping a copy for next time. A PoP does not need the whole library, just the popular slice its region actually watches, a few hundred terabytes, with the most-watched tenth to fifth of that held in memory and served straight from RAM. The rarely-watched rest is not kept there at all, and a request for one of those falls through to the origin as a miss. This fleet is real: Netflix alone runs more than 8k of these appliances, part of why Netflix and YouTube together already account for roughly 28% of all the traffic flowing down to homes worldwide. The failure story is gentle too. If an appliance dies, its viewers are sent to a nearby PoP instead, and a miss just costs one fetch; the edge only ever holds copies of bytes the origin still has, so losing an appliance costs a warm cache, never a video. Outbound bandwidth does not scale up. It scales out, across many appliances, and outward, toward the viewer, until the bytes are served from inside the viewer's own network.

A viewer watches
  1. the player pulls the next segment from the nearest PoP — a short hop away, a cache hit in a few milliseconds~3 ms
  2. only on a miss: the PoP fetches that one segment from the origin once, serves it, and keeps a copy for the next viewertens of ms
  3. separately and off-peak, the origin proactively fills the PoPs with copies of the videos, so in steady state the bytes never touch the origin~1 ms
use case 2 What does the edge cache, and how does a shaky-network viewer stream it?

Now two answers the edge tier deferred come due, and they turn out to be one answer. What does the edge actually store, and what does the player actually request? And how does someone on a flaky, shifting connection still get smooth playback? Both fall out of a single choice about how a video is encoded and sliced — which, along the way, makes the storage bill far smaller than anyone expects.

deep dive 2 What does the edge cache, and how does a shaky-network viewer stream it?
The question

The edge serves bytes close to the viewer. But two questions the last dive set aside now have to be answered together. What exactly are those bytes — what unit does the edge cache and the player fetch? And how does a viewer on a weak, changing connection get a stream that stays smooth, when the single-server version could only offer one stored quality? A connection is strong at home, weak on a phone, and it changes minute to minute on the move. The service has one job: keep the video playing smoothly the whole time, sharp when the connection is good and never frozen when it is bad, without the viewer touching a quality setting.

Start with what breaks if you serve one fixed quality. You lose either way. Pick a high bitrate and the phone viewer stalls constantly, because their connection cannot pull the bytes fast enough. Pick a low bitrate and the fiber viewer gets a blurry stream on a connection that could have carried far better. And a viewer whose connection changes mid-video cannot be served correctly by any single fixed choice, because the right answer changed while they were watching. So the video has to exist at several qualities, and something has to get each viewer the right one, which a ladder the player climbs itself does without any server-side per-viewer decision.

That leaves the choice of the ladder itself: how many levels, at what bitrates, and cut how. The baseline is a fixed ladder, one set of bitrates applied to every title, the same levels whether it is a dark, slow drama or a bright, fast sports clip. It is simple and it works, and it was the industry norm for years, with a top level around 5.8k kilobits a second for 1080p and middle levels at 2.4k and 1.8k kilobits below it. But it spends bits badly. Some titles are easy to compress — a still, dark scene barely changes from frame to frame — and an easy title does not need the top level's full bitrate to look identical. A fixed ladder has no way to know that. The better default is to pick the ladder per title: look at what is actually in the video and choose that title's levels for its own content, so an easy title gets a lower top-level bitrate at the same perceived quality. On a real title that trimmed the top level by about 20% for equal quality, and that saves bytes on every level, on every segment, for every one of the millions of views. At this scale of outbound traffic, a fifth off the bitrate is enormous.

A ladder of quality levels chosen per title, cut into ~6-second segments, with the player picking its own level. One quality is not enough. The video has to exist at several. So the source video gets encoded more than once, into several complete copies of itself at different qualities. Call each copy a quality level. The top level is the sharpest picture at the heaviest bitrate, for the strongest connections. A few levels sit in the middle. The bottom level runs at a few hundred kilobits a second, for the worst connections there are. Stack those levels from lowest to highest and the picture you get is a ladder, which is what everyone calls it. Two trade names are worth knowing, because you will hear them and they mean exactly what you just read: the whole idea is called adaptive bitrate streaming, shortened to ABR, so this stack of levels is the ABR ladder, and one individual copy is a rendition. Level and rendition are the same thing said two ways, and this design says level. Every level is then cut into short segments, a few seconds each, and the current authoring guidance puts a segment at about six seconds. What actually gets stored and served is all of that: every level, cut into segments, plus one small file listing which levels exist and where their segments live.

Now the half that kills the most common myth. The instinct is that the server watches your connection and decides what quality to push you. That is not how it works. The player pulls; the server does not push. When playback starts, the player first fetches that small listing file — it is called the manifest — which tells it every level that exists and where each level's segments live. Then the player fetches the video one segment at a time, and before each segment it picks a level. It looks at how fast the last few segments actually arrived, and at how much video it has already buffered ahead, and it asks for the next six-second segment at the highest level that arrival speed can keep up with. Connection drops on that walk outside? The next segment the player asks for is a lower level, and playback stays smooth at reduced quality instead of freezing. Connection recovers? The player steps back up. The server has no per-viewer quality decision to make. It just serves whatever segment is asked for, which is exactly why those fetches cache so well at the edge.

Here is where the surprise from the brief pays off, and it is the most important number on the board. Now that you know what is stored, the whole ladder cut into segments, size the library from the bytes up. One source hour at the old top level is about 2.6 GB. A full ladder runs several more levels below that, so budget very roughly twice the top level for the whole set: call it 5 GB per source hour. A region's catalog is about 8k titles at a blended runtime of maybe 1.5 hours each. Multiply it out and it is about 60 TB, and even generously multiplying by ten for extra audio languages, subtitle tracks, and a global footprint, the whole thing is still well under a petabyte. The exact same bytes, served to a huge audience, were ~1.5 Tbps of bytes going out in the last dive, the wall, while the bytes at rest are tens of terabytes. The scary number was always the small one. All of that, every level, every segment, needs somewhere to be found from: a small metadata catalog holds the map, kilobytes of pointers per title rather than the gigabytes of the segments themselves. It is read on every single playback start and almost never written, so it is answered from read replicas — extra copies of the catalog kept in step with it and used only for reading, so a flood of lookups never lands on the one copy taking the writes. Its failure story is gentle. A replica that has fallen a little behind still hands the player a slightly stale but working list of levels, and if one specific level is momentarily unavailable at the edge, the player just requests a lower one it can get, since stepping down is already normal behavior, not an error path.

table quality_levels (the manifest's rows) — the data behind this part of the design
video_ididwhich source video this level belongs to
levelint0 is the lowest bitrate; climbs toward the top level
bitrate_kbpsintthis level's bitrate — what the player compares against its own download speed
resolutionstringe.g. 1080p, 720p — paired one-to-one with the level's bitrate
segment_prefixstringwhere this level's segments live at the edge, so the player knows what to request
CreatorViewer · playerOriginfills the edgeanswers missesquality levels, in segmentsCDN edgetier · PoPsPoP caches near viewers~90–100 Gbps/boxpopular titles from RAMMetadata catalog· Postgresread-mostlymanifests + levelsread replicas
The board after this deep dive — “A viewer watches” traced, hop by hop. The numbered steps below walk the same path.
A viewer watches
  1. the player first reads the manifest from the metadata catalog — the small file listing every quality level and where its segments live~5 ms
  2. the catalog returns the list of levels, served from a read replica~2 ms
  3. then the player pulls the next segment from the nearest PoP, at the highest level its own download speed can keep up with — an edge cache hit~3 ms
  4. only on a miss: the PoP fetches that one segment from the origin once, serves it, and keeps a copy for the next viewertens of ms
  5. separately and off-peak, the origin proactively fills the PoPs with copies of the videos, so in steady state the bytes never touch the origin~1 ms
use case 3 Do not block the upload, and do not lose it

Playback is fully handled now. But the ladder those players climb has to be produced by something, and the creator's original file — often a full gigabyte — has to arrive intact before any of that can start. On one box, done inline, this small pipeline snaps in two separate places once real creators show up.

deep dive 3 Do not block the upload, and do not lose it
The question

The read path is built: the edge serves bytes near the viewer, and the player climbs a ladder of quality levels. But that ladder does not appear by magic. Something has to turn one creator's source video into every level on it, and something has to get the gigabyte source file in safely in the first place. Creators upload constantly — hundreds of hours of new video every minute across the service. Each source video has to be encoded into its whole ladder before anyone can watch it, and encoding is heavy. Two things cannot happen: the creator cannot wait for the encoding to finish before their upload is accepted, and a gigabyte upload cannot be lost to a dropped connection.

The arrival rate itself is the argument for the shape. Encoding on upload is the instinct, and it dies on a number: about ~500 source-hours a minute, each turned into a ladder of ~6 quality levels, is roughly ~3k hours of finished video to produce a minute. No fixed set of machines bolted to the upload path could clear that while the creator waits, and every creator would be left watching a progress bar until the whole encode finished. Work that arrives far faster than it can be done, must not be lost, and can be done slightly later, is the textbook case for a durable job queue and a pool of workers: the upload adds a job and returns, and the fleet works through the queue as fast as its worker count allows.

The point of a durable job queue is that the job lives in the queue, not in the worker. A worker takes a chunk-encode job on loan — it is marked as claimed for a while, rather than removed — does the work, and marks it done. If the worker dies partway through, that claim runs out and the job goes back on the queue for another worker to pick up. The creator already got their success and the source video is safe in the blob store, so a lost worker only delays that video's levels slightly. It never loses the video. This self-correcting shape works for any pipeline whose jobs do not depend on each other, which is exactly the case here: one video's encode does not care what any other video is doing, so the jobs spread across the queue and the worker pool cleanly.

Accept the file in chunks that can resume after a drop, return as soon as it is stored, and encode the ladder afterwards through a job queue and a worker fleet. Take the encoding first, because a number settles it. The instinct is to encode on upload: the creator uploads, the server converts the video into every quality level right then, and returns success when they are all ready. That conversion has a name — transcoding, turning one video file into other formats and qualities — and it is the expensive step here. At one upload an hour, doing it inline is fine. At the real rate it is impossible. Roughly ~500 hours of source video arrive every minute, and each of those hours has to be encoded into a ladder of about ~6 levels. That is around ~3k hours of finished video to produce every minute — the useful way to count the work, because one hour of source encoded at six levels is six hours of output, not one. No step that runs while the creator waits can swallow that. Work that arrives far faster than it can be done, must not be lost, and can be done slightly later, is exactly what a durable job queue with a pool of workers is for: a list of pending jobs that survives a crash, plus a set of machines pulling jobs off it. In practice that queue is an SQS- or Kafka-class durable log. So the upload does not trigger an encode. It puts an encode job on the queue.

The only thing the upload path has to finish while the creator waits is getting the source bytes stored safely and handing back a success: the video is safe, it will be ready to watch shortly. The upload service that does this keeps nothing of its own between requests except the parts of a file still arriving, so you can run as many copies of it as you need behind a load balancer, and none of them holds anything worth losing. The bytes themselves land in the store, not on the upload service. Behind the queue, a fleet of workers pulls jobs and encodes them: more encoding to do just means more workers pulling from the same queue. One more move makes it fast, not merely unblocked. A single source video does not have to be one long encode on one machine. It is split into many short chunks, each encoded at the same time by a different worker, then joined back into the finished levels. Netflix's own pipeline splits one upload into roughly ~31 chunks this way, running the steps in an order that respects which ones depend on which. The encoding itself — turning a chunk of pixels into one level's bitrate — is treated as a sealed job the worker runs. This design sizes and schedules that work; it does not open up the compression scheme inside.

Now the way the file actually arrives, which is the second failure. A source video is gigabytes, and a plain single upload that drops at ninety percent has to start over from zero, which at gigabyte sizes is brutal and common. The fix is to send the file in pieces and let it pick up where it left off: the file goes over HTTP in chunks, the server tracks how far it got, and a dropped connection resumes from the last byte that arrived instead of restarting. This is a solved, standardized thing. The tus protocol is the open standard behind several major services' resumable uploads, so the design adopts it and moves on. The upload service holds the chunks as they come in, and the moment the full source video is safely stored it returns success and puts an encode job on the queue. Success means your bytes are safe, not your video is encoded. Once the pipeline writes the finished levels back, that store — a blob store, meaning storage built to hold very large files cheaply and keep them safe — becomes the master copy of every source video and every level — the thing the edge fills from, and the thing everything else can be rebuilt from. Lose part of it and those videos are simply gone, so it is copied across several machines and backed up. Unlike the edge or the catalog, nothing can rebuild it. The pipeline writes the video levels before it writes the catalog's manifest entry, on purpose, so a viewer's manifest never lists a level that is not actually there yet; a worker dying between those two writes just delays the manifest entry until the retry, and never breaks it.

CreatorViewer · playerBlob store ·object storesources + every levelreplicatedfills the edgeCDN edgetier · PoPsPoP caches near viewers~90–100 Gbps/boxpopular titles from RAMMetadata catalog· Postgresread-mostlymanifests + levelsread replicasUploadservice · tusresumable chunksreturns on durable storeTranscode pipeline· queue + workersjob queue + workers~31 chunks/upload
The board after this deep dive — “A creator's upload lands” traced, hop by hop. The numbered steps below walk the same path.
A creator's upload lands
  1. the creator sends the source video in chunks, picking up from the last byte that arrived if the connection drops~5 ms
  2. the moment the full source is safely stored in the blob store, the upload service returns success — the creator is unblocked here~10 ms
  3. separately, the upload service puts an encode job on the transcode pipeline's durable queue~1 ms
  4. workers pull the job, split the video into ~31 chunks encoded at the same time, produce every quality level, and write them back to the blob store~1 ms
  5. and record each level's manifest entry in the metadata catalog, so the player can find it~1 ms
use case 4 When the region goes dark

For the happy path, the whole thing is finished. This last one starts already in failure, and the shock is where the failure is. Not in anything carrying video bytes, but in a quiet management layer to one side, which still manages to pull streaming down — exactly the outage the edge tier was supposed to rule out.

deep dive 4 When the region goes dark
The question

Step back and look at what is built. It is complete for the happy path. The edge serves bytes near every viewer, the player climbs a ladder that fits its connection, the catalog hands out manifests, and uploads are accepted fast and encoded off to the side. Now the premise arrives already broken, the way a real incident does. On one Christmas Eve, one of the biggest streaming services in the world went dark for about seven hours across the United States, Canada, and Latin America. The cause was not a flood of viewers and not a failure of any serving box. An internal maintenance process ran by mistake against production state for a slice of a cloud provider's load-balancing fleet, the control plane that directs traffic to servers, and it deleted that state. The video bytes were fine, and so were the stored copies of every quality level. But the layer that routes a viewer's request to a server was gone in that region, so requests could not be steered to where the bytes lived, and televisions across three continents showed an error instead of a movie.

The instinct is to read this as bad luck with one cloud provider, or an argument for more redundancy in that provider's load balancers. Both miss the lesson, and the lesson is the whole reason the edge tier is shaped the way it is. A more redundant control plane is still a control plane playback depends on in real time. Make it about as reliable as anything gets, what engineers call five-nines uptime, and a bad-enough maintenance slip still takes it, and every stream with it. The engineering answer is not to make the central dependency stronger; it is to take the dependency off the path every viewer's request takes. The bytes a viewer needs are already at the edge, a short hop away, so playback can ride the nearest cache instead of the farthest control plane.

The honest counterpoint belongs in the lesson, because it is the other half of what this incident teaches. The edge is what lets playback stand on its own, but the incident happened because a single region's control plane was something every stream could not do without. The safety net only helps if the design actually leans on it, if the edge is allowed to serve without checking in, and if the control plane itself is not a single regional point of failure that a maintenance slip can delete wholesale. During a control-plane failure, a PoP that already holds a title keeps serving it outright; a PoP that does not cannot fetch it from a dead region, so that one obscure title stops working, and that is where the failure lands — at the rare edges, not across the whole service. The popular titles the region is actually watching are already cached, often in RAM, so the overwhelming majority of playback continues while the rarely-watched rest waits for the control plane to come back. Breaking the rare miss while protecting the common hit is exactly the shape graceful should take.

The edge is the safety net — playback should not depend on the control plane, and a stale answer beats no answer. Ask why a deleted control plane could stop playback at all. It is because playback in that moment still depended, right then and there, on a central regional control plane to connect viewers to bytes. That dependency is exactly what the edge tier exists to cut. The bytes a viewer needs are already sitting in a cache inside their own internet provider, a short hop away. If playback does not have to reach the origin or the central control plane at all — if the edge can keep serving the segments it already holds without asking permission on every request — then a regional control plane can disappear and viewers already watching keep watching, because their bytes never needed the control plane in the first place.

This is where the signature dive pays off a second time, reframed. The edge tier was introduced to beat the outbound-bandwidth wall, to spread the bytes across appliances near the viewer. Its second, quieter job is exactly this. Because the bytes live at the edge, close to the viewer and independent of any one central control plane, the edge is what keeps playback alive when the center fails. Degrading gracefully here means the edge keeps serving the segments it already has, even if the catalog is briefly unreachable and the manifest a player holds is stale — out of date by a few minutes, which for a list of quality levels changes almost nothing — rather than failing every stream the instant the control plane stumbles. A viewer mid-movie should not care that a management layer three hops away just went down. The lesson is bigger than this board. A system that serves bytes to the world should push the thing viewers depend on, the bytes, as close to them and as far from a single central dependency as it can, then let the far management layers fail without taking the near bytes with them.

CreatorViewer · playerBlob store ·object storesources + every levelreplicatedfills the edgeCDN edgetier · PoPsPoP caches near viewers~90–100 Gbps/boxpopular titles from RAMkeeps serving if staleserves w/o control planeMetadata catalog· Postgresread-mostlymanifests + levelsread replicasUploadservice · tusresumable chunksreturns on durable storeTranscode pipeline· queue + workersjob queue + workers~31 chunks/upload
The board after this deep dive — “A viewer watches” traced, hop by hop. The numbered steps below walk the same path.
A viewer watches
  1. the player first reads the manifest from the metadata catalog — the small file listing every quality level and where its segments live~5 ms
  2. the catalog returns the list of levels, served from a read replica~2 ms
  3. the player pulls the next segment from the nearest PoP — and when the catalog and central control plane are briefly unreachable, the PoP keeps serving the segments it already holds against a slightly out-of-date manifest, so playback does not go dark~3 ms
  4. only on a miss: the PoP fetches that one segment from the origin once, serves it, and keeps a copy for the next viewertens of ms
  5. separately and off-peak, the origin proactively fills the PoPs with copies of the videos, so in steady state the bytes never touch the origin~1 ms
Where this design sits on consistency (and CAP)

This design makes opposite choices about safety for its two kinds of data, on purpose, because they are worth completely different things. The playback side — the CDN edge tier, the metadata catalog, and the segments a viewer pulls — would rather answer now, and answer fast, than be perfectly up to date. The edge serves the segments it already holds, close to the viewer; the catalog is read far more than it is written, and answered from whatever copy is near; a segment that drops is simply requested again; and a manifest that is a few minutes out of date still hands the player a working list of quality levels. None of this waits around to be exactly correct, because a stream needs to be smooth and available, not accounting-grade.

The Blob store makes the other choice. The encoded video is the product — the thing the edge fills from and the thing everything else rebuilds from — so losing part of it is real, permanent harm, not something the next request repairs. So the Blob store is where the design pays for safety: it lives in an object store that keeps several copies and is backed up, and it is the one place the system's work is truly safe. That is exactly why the edge and the catalog can afford to be cheap and throwaway — the edge re-fills from the store, and the catalog's manifests can be worked out from it again. Durability is bought where it matters and declined where it does not.

There is a name for the underlying choice. When a network fault splits a system in two — that split is the P, for partition — each operation has exactly two honest options: answer anyway from what this half knows and risk being wrong, which is the A, for availability, or refuse until the halves agree, which is the C, for consistency. A partition is not something you choose; it is weather. The useful way to apply the CAP theorem is per operation, by asking what a wrong answer would cost. Here a slightly out-of-date manifest or a re-requested segment costs almost nothing, while a lost archive of encoded video costs everything, so the design takes the fast answer everywhere on the playback side and pays for durability only on the Blob store. The Christmas Eve outage is the same lesson in the field: playback should ride the nearby edge, not a far central control plane.

CAP theorem — the partition trade-off, per operation
The finished board

That is the whole machine, two paths that meet at the durable store, and every box on it was placed by one of the deep dives above. On the read path a viewer's player reads the manifest from the metadata catalog, then pulls six-second segments from the nearest edge PoP, climbing and descending the ladder of quality levels to match its own connection; the origin — now the blob store — is off the viewer's path entirely, filling the edge off-peak and answering only the rare miss. On the write path an upload service accepts the source in chunks that resume after a drop and returns the moment the bytes are safe, then a transcode pipeline splits the video into chunks encoded at the same time, produces every quality level, writes them to the blob store, and records their manifests in the catalog. The edge serves the terabits, spread across appliances near the viewers; the blob store holds the tens of terabytes durably; the catalog holds the small map; and when a region's control plane fails, the edge keeps serving what it holds. If you carry away one sentence, make it the one the whole design turned on: the scary-sounding resource is often not the wall — do the arithmetic before you design.

CreatorViewer · playerBlob store ·object storesources + every levelreplicatedfills the edgeCDN edgetier · PoPsPoP caches near viewers~90–100 Gbps/boxpopular titles from RAMkeeps serving if staleserves w/o control planeMetadata catalog· Postgresread-mostlymanifests + levelsread replicasUploadservice · tusresumable chunksreturns on durable storeTranscode pipeline· queue + workersjob queue + workers~31 chunks/upload
The finished design — every box placed by one of the deep dives above.
What's on the diagram
Creator
Creator. The person who uploads a source video, handed to the write path once per video. Nothing about it is on the path a viewer waits on — the upload is accepted in chunks that resume after a drop, and acknowledged the moment the bytes are safely stored, with everything heavy happening off to the side after that.
Viewer · player
Viewer · player. The app or browser tab that plays a video. The player pulls: it reads the manifest from the metadata catalog first, then fetches the stream one segment at a time from the nearest edge PoP, choosing before each segment the highest quality level its own download speed can keep up with — so quality follows the connection with no server-side per-viewer decision.
Blob store · object store
Blob store · object store. The master copy — an object store holding every original upload and every encoded quality level, tens of terabytes a region and low petabytes globally, copied across machines and backed up. It is off the everyday playback path: it fills the edge off-peak and answers only misses. It is the one box whose loss cannot be undone, because the edge re-fills from it and the catalog's manifests can be worked out from it again, but nothing can rebuild the store itself. It was born as the all-purpose Origin and relabeled once what it held became the finished, durable video.
CDN edge tier · PoPs
CDN edge tier · PoPs. The signature: a tier of cache appliances installed deep in the network, close to the viewer and often inside their own internet provider, each pushing out roughly 90-100 gigabits a second, with the most-watched titles served straight from RAM. It carries the terabits of outbound video, spread across the fleet, so no single origin uplink is the ceiling; the origin only fills it off-peak and answers the rare miss. Because the bytes live here near the viewer, it is also the safety net — when a region's control plane is unreachable, it keeps serving the segments it already holds and breaks only on the rare title it is not holding, rather than failing every stream.
Metadata catalog · Postgres
Metadata catalog · Postgres. The small index the player reads first — each title's list of quality levels and the manifest that points at their segments, kilobytes per title, not the gigabytes of video. It is read enormously (one manifest read starts every playback) and written only when a new level is published, so it is tuned for reads and answered from read replicas: extra copies kept in step with it and used only for reading. It holds the map; the edge holds the bytes; an out-of-date manifest is tolerable, and losing a replica just moves reads elsewhere without touching playback.
Upload service · tus
Upload service · tus. Intake that accepts the source video in chunks over HTTP, picking up from the last byte that arrived when a connection drops, and returns success the moment the source is safely stored — not when it is encoded. It keeps nothing of its own between requests except the parts of a file still arriving, so a dead upload worker loses only a partial chunk that the client re-sends, and you can run as many copies of it as you need behind a load balancer. The moment the source is stored it puts an encode job on the transcode pipeline.
Transcode pipeline · queue + workers
Transcode pipeline · queue + workers. A durable job queue and a fleet of workers that turns one source video into its whole ladder of quality levels, off the path the creator waits on. It splits each upload into roughly 31 chunks encoded at the same time, writes the finished levels back to the blob store, and records each level's manifest entry in the catalog. The queue owns the work, so a dead worker's job simply goes back on the queue — the creator already has their success. It is sized by how much finished video it has to produce per minute; the compression itself is treated as a sealed job it just runs.
What this design never covered — raise it yourself
The encoding algorithm itself

A strong follow-up is how the compression itself works — how a codec (the scheme that shrinks raw video into a streamable file: H.264, HEVC, AV1) decides how many bits each frame and each scene gets. The honest answer names it as the sealed job this board sized but never opened: the pipeline decides how much to encode and when, but what happens inside one encode is its own deep problem.

Live streaming

The sharpest what-about is broadcasting an event as it happens — the video has to arrive and go straight back out within a second, there is no chance to encode it ahead of time, and cutting it into pieces costs something different. The strong answer names it as a distinct problem with its own forcing numbers, not a variation on this video-on-demand board.

Content protection

A follow-up on stopping a paid video from being copied points at license servers and key exchange. Name it as a rights-and-security layer bolted onto playback that no systems number here forces, rather than folding it into the delivery spine.

What to recommend and what to autoplay

The deepest what-is-this-for is which video a viewer sees next — the home screen, the ranking. Name it as a separate personalization and machine-learning system that decides which video to request; this board plays whatever video is asked for.

Cache-fill strategy and the long tail

A sharp interviewer may ask how the edge decides which titles to load in advance and which to leave for a miss, and what it costs when someone plays a title no PoP is holding. Name it as a real cache-strategy question — push the popular titles out off-peak, and let the rarely-watched ones come through on a miss — that this board treated as the edge's fill policy rather than a sourced design number.

Go deeper — the primitives this design leaned on
  • Durable job queue with a worker pooljobs that do not depend on each other are added to a durable list that survives a crash, and a group of workers pulls them off and runs them side by side, each tracking how far through its own share it has got; it is exactly the transcode pipeline, uploads adding encode jobs a fleet of workers gets through in parallel

Feedback on this problem →