Step 21 of 22 · optional
Design a video streaming platform
Design YouTube-style video: resumable uploads, a transcoding pipeline, adaptive bitrate streaming with HLS, CDN delivery for the long tail, and view counts.
Video is the heaviest thing most systems ever serve: it dominates internet traffic, and a platform's biggest bill is usually bandwidth. Designing a video platform is less about clever databases and more about moving enormous files efficiently: processing each upload once into many formats, then delivering it from as close to the viewer as possible. This lesson designs a YouTube-style service for user-uploaded video on demand, and ends with how live streaming differs.
Requirements
Functional:
- Users upload videos (up to 4 hours, up to 50 GB) from web and mobile.
- Videos play on any device and any connection, from a phone on a train to a 4K TV.
- Viewers can seek anywhere in a video instantly.
- Show titles, thumbnails, view counts and recommendations (recommendation algorithms are out of scope).
Non-functional:
- Playback starts in under 2 seconds and rarely pauses to buffer ("rebuffering" is the metric viewers feel most).
- Uploads survive flaky connections.
- An uploaded video is playable within minutes; full processing can take longer.
- Cost efficiency: bandwidth and storage dominate the budget.
Numbers
daily active users: 200 million, watching 40 minutes/day each
watch time: 8 billion minutes/day
average bitrate: ~3 Mbit/s across devices → 8e9 min × 60 s × 3 Mbit/s ≈ 1.4e12 Mbit ≈ 180 PB/day egress
peak egress: ~2× average ≈ 35 Tbit/s
uploads: 500,000 videos/day, averaging 10 minutes
storage per video: original + ~8 renditions ≈ 3× the original size → roughly 1 PB of new storage per day35 terabits per second can't come from a few data centres; it has to come from a CDN with servers inside or next to internet service providers. Storage grows by about a petabyte a day, which means object storage, with old and unpopular videos moved to cheaper storage tiers. Those two numbers shape everything else.
Uploads: resumable and direct to storage
A 5 GB upload from a phone will be interrupted. Uploads must be resumable: the client uploads in parts and, after a failure, asks which parts arrived and continues from there.
- The client asks the upload service to start: it gets a video ID and presigned URLs for a multipart upload directly to object storage, so the application servers never proxy video bytes.
- The client uploads parts (say 16 MB each), several in parallel, retrying failed parts individually.
- When all parts are in, the client calls "complete". The storage service assembles the original, and the upload service publishes a
video.uploadedevent to the processing queue.
The original file is kept: when a better codec appears in five years, you'll want to re-encode from the source, not from an already-compressed rendition.
The processing pipeline
A raw upload can't be streamed as-is: it might be a 60 Mbit/s 4K file in an unusual format. The pipeline turns it into something every device can play:
- Validate and inspect: check it's a real video, read its duration, resolution and codecs.
- Transcode into a bitrate ladder: the same video at several resolutions and bitrates, for example 240p at 0.4 Mbit/s up to 4K at 16 Mbit/s, in widely supported codecs (H.264 everywhere; newer, more efficient codecs such as AV1 for devices that support them).
- Segment: cut each rendition into short segments of 2 to 6 seconds, and write a manifest listing them.
- Extract thumbnails, generate captions with speech recognition, and run content moderation (copyright matching, policy classifiers).
- Publish: store everything in object storage and mark the video playable.
Transcoding a 10-minute video into 8 renditions takes far longer than real time on one machine. Two things make it fast:
- Split the work: cut the source into chunks of, say, 10 seconds at keyframes, transcode every chunk and rendition in parallel across a worker fleet, then stitch the results. This is a DAG of tasks: inspect, then N × M transcode tasks, then stitch, then publish.
- Prioritise: produce the most-used renditions (360p and 720p) first and mark the video playable, then fill in 4K and the newer codecs afterwards.
The pipeline runs on queues of independent tasks, so a worker crash only retries one chunk. Make every task idempotent: its output path is derived from its inputs (video/123/720p/chunk-0042.mp4), so a retry overwrites the same file instead of creating duplicates. Transcoding is CPU- or GPU-heavy and bursty, so it suits spot or preemptible machines, which are much cheaper and can be interrupted, which retries handle.
Adaptive bitrate streaming
Players don't download "the video". They use adaptive bitrate (ABR) streaming over plain HTTP, with formats such as HLS or MPEG-DASH. The player first downloads a master manifest listing the renditions:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p/index.m3u8Each rendition's manifest lists its segments. The player fetches segments one by one, measures how fast they download and how much video it has buffered, and picks the rendition for the next segment accordingly. On a fast connection it climbs to 1080p; when the train enters a tunnel it drops to 360p for a few segments instead of stopping to buffer.
Segmenting also makes seeking cheap: jumping to 47:12 means requesting the segment that covers that time, not downloading 47 minutes first. And because every segment is an ordinary, immutable HTTP file, any CDN can cache it.
Starting at a low rendition gets the first frame on screen quickly; the player steps up once it has measured bandwidth. Short first segments help too.
Delivery: the CDN and the long tail
Video popularity follows a steep curve: a small fraction of videos gets most of the views, and a huge long tail is watched rarely.
- Popular videos are cached at CDN edges everywhere; the origin barely sees them. Large platforms place cache servers inside ISPs' networks, so traffic never crosses the wider internet.
- The long tail misses at the edge. Without care, every miss goes to the origin, from every edge. An origin shield (a mid-tier cache per region that all edges in the region ask first) collapses those misses so each segment is fetched from object storage at most once per region.
- Pre-warming: when a video from a channel with 10 million subscribers is published, push its first segments to edges before the notification goes out, so the first wave of viewers doesn't hit cold caches at once (cache stampedes).
- Storage tiers: renditions of videos nobody has watched for a year move to cheaper, slower storage. Some platforms delete rarely used renditions entirely and regenerate them from the original on demand.
Access control rides on the URLs: the API returns manifest and segment URLs signed with an expiry (or a signed cookie), so the CDN can check access without calling your servers. Paid content adds DRM, which encrypts segments and hands decryption keys only to licensed players.
Metadata and view counts
Video metadata (title, owner, status, renditions, thumbnails) is a modest dataset: hundreds of millions of rows, read-heavy. A sharded relational database with aggressive caching works; the watch page reads from cache almost always.
View counts are a classic trap. Incrementing a counter row on every view sends a viral video's 50,000 views per second at one database row, a hot key. Instead:
- Players send view events to an ingestion endpoint, which writes them to a log such as Kafka.
- A stream processor validates views (filtering bots and repeated refreshes), aggregates them per video per minute, and adds the totals to the stored count.
- The displayed count lags by seconds to minutes, which nobody minds.
Watch-time analytics, recommendations and creator dashboards consume the same event stream.
Live streaming, briefly
Live streaming reuses most of this design with one difference: there's no time to process the whole video first.
- The broadcaster sends a continuous stream (usually RTMP or SRT) to an ingest server near them.
- It's transcoded into the bitrate ladder in real time, segment by segment, and segments are published to the CDN as soon as each is ready; the manifest is updated continuously.
- Latency is the new trade-off: segments of 6 seconds plus buffering give 20–30 seconds of delay. Low-latency HLS and DASH use shorter, partial segments to get to a few seconds, at the cost of more requests and less buffering against network hiccups.
Practice
Two questions from the business:
- Storage costs are growing faster than revenue. 60% of stored videos had no views in the last year. What would you change?
- A creator with 30 million subscribers will premiere a 2-hour film at a fixed time, with 5 million people expected to start watching in the same minute. What do you prepare?
Show solution
1. Storage costs.
- Tier by popularity: move renditions and originals of videos without views for 90 days to infrequent-access storage, and after a year to archive tiers. Keep the manifest and a low rendition (360p) in the standard tier so playback can always start, and restore higher renditions when the video is watched again.
- Drop rarely used renditions for cold videos (for example keep 360p, 720p and the original, delete the rest) and regenerate on demand from the original if views return. Re-encoding a few videos that come back is cheaper than storing every rendition of millions that don't.
- Better codecs for popular videos: re-encoding the most-watched videos in a more efficient codec cuts both storage and the much larger bandwidth bill; prioritise by watch hours, not by count.
- Measure first: the bill per tier and the access pattern per video age tell you where the cut is.
2. A scheduled premiere.
- Pre-warm the CDN: push the manifest and at least the first several minutes of every common rendition to all edges and origin shields ahead of time. The video is fully processed in advance, so this is just a matter of priming caches.
- Protect the API: 5 million watch-page loads in a minute hit metadata, comments and the manifest URL signing. Serve the watch page metadata from cache with request coalescing, pre-generate signed URLs with a long enough expiry, and load-test the path beforehand.
- Stagger the start: players can add a small random delay (a few seconds) before requesting the first segment, smoothing the spike. A countdown page with a live chat absorbs early arrivals without touching video delivery.
- View counting: the event pipeline must absorb a burst of start events; partitioning the stream by video would put the whole premiere on one partition, so partition by viewer instead and aggregate per video downstream.
Key takeaways
- Video is a bandwidth problem first: plan egress from the start, and deliver almost everything from a CDN close to viewers.
- Upload with resumable multipart transfers straight to object storage, and keep the original for future re-encoding.
- Transcode into a bitrate ladder with a parallel, idempotent pipeline, publishing the common renditions first.
- Adaptive bitrate streaming splits video into short segments over plain HTTP so players switch quality per segment, seek instantly and use ordinary caches.
- Count views through an event stream with aggregation, never by incrementing one row per view.
Check yourself
Quiz
0/4 correct · 4 left
1.Why are videos cut into short segments for streaming?
2.What does an origin shield solve for long-tail videos?
3.A viral video receives 50,000 views per second. How should view counts be updated?
4.Why does the transcoding pipeline split a video into chunks and process them in parallel?
Finished reading?
Mark it complete to fill in your roadmap.