Design a video streaming platform: upload, parallel transcoding, adaptive bitrate delivery with HLS and DASH, CDN, storage tiers, thumbnails, cost and failures.
What you'll learn: By the end of this lesson you will be able to (1) design an upload and parallel transcoding pipeline, (2) explain adaptive bitrate streaming and why video is cut into small segments, (3) size bandwidth, storage and compute for a video service, and (4) reason about cost levers and failure modes.
Imagine a shipping company that must move one enormous crate to many customers, each with a different truck. Some trucks are huge and fast, some are small and slow. The company cuts the crate into small boxes, repackages the contents into several grades of packing (premium, standard, light), and lets each truck pick up boxes of the grade it can carry, switching grade mid-route if the road gets bad.
Video streaming is that logistics problem. A raw upload is far too big and too varied to send directly. We re-encode it into a ladder of qualities, cut each quality into short segments, and let the player choose segment by segment what its network can handle. Almost every byte then travels from a CDN edge close to the viewer.
Video is a write-once, read-many, bandwidth-bound workload. Upload is rare and compute heavy: we transcode once. Playback is constant and bandwidth heavy: we serve the result a very large number of times. So the design splits into an asynchronous, parallel processing pipeline that produces many renditions, and a delivery path that is essentially "static files on a CDN" plus a small control plane for metadata, authorisation and recommendations.
Requirements and scope
Functional: upload a video, watch it (with fast start and no long buffering), seek, thumbnails and preview images, video metadata and search, resume where you left off.
Out of scope: live streaming (a related but different problem, with latency measured in seconds and no time to transcode offline), recommendations, comments, ads, DRM details beyond a mention.
Non-functional (assumptions):
Playback starts quickly (for illustration, within about two seconds) and rebuffering is rare.
Upload processing finishes within minutes for typical videos, not seconds.
Durability of originals: a creator's upload must not be lost.
Availability: playback should survive origin trouble for popular content, because the CDN holds it.
Cost matters more than in most systems, because egress and storage are the bills.
Back-of-envelope estimate
All numbers are illustrative assumptions.
Quantity
Assumption
Result
Videos uploaded per day
1 million
about 12 uploads per second average
Average duration
5 minutes
5 million video minutes per day
Average original size
200 MB (about 5 Mbps)
200 TB per day of originals
Rendition ladder
6 renditions whose bitrates sum to about 8 Mbps
about 300 MB per video, about 300 TB per day
Views per day
100 million
Average watch time
5 minutes at 3 Mbps
about 112 MB per view
Egress per day
100 million x 112 MB
about 11 PB per day
Average concurrent streams
100M x 300 s / 86,400 s
about 350,000
Peak concurrent streams
3x
about 1 million, about 3 Tbps
Transcode compute
6 renditions, each about real-time on one core (assumption)
5M minutes x 6 = 30M core-minutes, about 20,000 cores busy around the clock
The shape of the result is the lesson. Uploads per second are tiny. Egress dwarfs everything: terabits per second at peak. That is why the CDN is the central component and why per-gigabyte egress price drives business decisions. Storage grows by roughly 180 PB per year at these assumptions (about 500 TB per day), so tiering matters too. Compute for transcoding is large but flexible, because it is queued work that can use cheap, interruptible machines.
API design
POST /v1/videos -> 201 { "videoId": "v_77", "uploadId": "...", "partUrls": [...] }
PUT <partUrl> (client uploads parts directly to object storage)
POST /v1/videos/v_77/complete -> 202 { "status": "processing" }
GET /v1/videos/v_77 -> 200 { "status": "ready", "title": "...", "manifestUrl": "https://cdn.example/v_77/master.m3u8" }
GET /v1/videos/v_77/progress -> 200 { "positionSeconds": 142 }
PUT /v1/videos/v_77/progress -> 204
As in the photo sharing case study, uploads go directly to object storage using presigned multipart URLs. Multipart matters even more here because files are large and mobile connections drop. The manifest URL is what a player needs: a small text file listing the available renditions and segments.
Data model
Metadata:videos(video_id, owner_id, title, status, duration, created_at, ...) and renditions(video_id, profile, manifest_key, bitrate) in a relational or document store. Small, read-heavy, cacheable.
Object storage: originals under orig/<id>, segments and manifests under v/<id>/<profile>/seg_00042.m4s and v/<id>/master.m3u8. Immutable objects.
Watch progress: a key-value store keyed by (user_id, video_id), written often and read rarely. Losing a few seconds of progress is acceptable, so it tolerates loose durability.
erDiagram
USERS ||--o{ VIDEOS : uploads
VIDEOS ||--o{ RENDITIONS : has
VIDEOS ||--o{ THUMBNAILS : has
USERS ||--o{ WATCH_PROGRESS : tracks
VIDEOS ||--o{ WATCH_PROGRESS : tracked_by
VIDEOS {
string video_id PK
string owner_id FK
string status
int duration_s
}
RENDITIONS {
string video_id FK
string profile
int bitrate_kbps
string manifest_key
}
THUMBNAILS {
string video_id FK
string object_key
}
WATCH_PROGRESS {
string user_id FK
string video_id FK
int position_s
}
How to read it: the segments themselves are not rows. They are objects named by convention, and RENDITIONS only points at each manifest.
High-level design
Playback is entirely separate from processing. When the video is ready, the API returns a manifest URL and the player talks to the CDN.
Reference deployment topology
Deployed, the system is three mostly independent planes: a control plane for metadata, a batch plane for transcoding, and a delivery plane for bytes.
How to read it: a failure in the batch plane delays new videos but does not interrupt playback of videos already published, and the delivery plane needs no application servers to serve segments (the player only calls the control plane to get the manifest URL).
Deep dive 1: the transcoding pipeline
Transcoding converts the original (any codec, resolution and frame rate) into a standard set of renditions, for example from a small mobile-friendly size up to high definition, each at a target bitrate. A single long video transcoded on one machine is slow and fragile, so the work is parallelised and checkpointed.
Pipeline as a DAG. A directed acyclic graph of tasks, run by an orchestrator that records state for each task:
Inspect and validate: probe the file, reject corrupt or unsupported input, extract metadata.
Split: cut at keyframe boundaries (a keyframe decodes without earlier frames, so each chunk is independently decodable), in chunks of a few to tens of seconds.
Transcode in parallel: one task per chunk and rendition. A ten-minute video with 6 renditions and 10-second chunks produces 360 small tasks.
Package: assemble each rendition's segments, write the manifests, and optionally encrypt segments.
Side tasks in parallel: thumbnails, audio extraction, moderation, captions, loudness normalisation.
Publish: write metadata, set status ready, notify the creator.
Transcoding pipeline as a DAGOriginal to Probe and validate; Probe and validate to Split at keyframes; Split at keyframes to Chunk 1 renditions; Split at keyframes to Chunk 2 renditions; Split at keyframes to Chunk N renditions; Split at keyframes to Thumbnails; Chunk 1 renditions to Package and manifest; Chunk 2 renditions to Package and manifest; Chunk N renditions to Package and manifest; Package and manifest to Ready; Thumbnails to ReadyOriginalOriginalProbe and validateProbe andvalidateSplit at keyframesSplit atkeyframesThumbnailsThumbnailsChunk 1 renditionsChunk 1renditionsChunk 2 renditionsChunk 2renditionsChunk N renditionsChunk NrenditionsPackage and manifestPackage andmanifestReadyReady
Why chunks make it robust. If a worker dies, only its chunk is retried, not the whole video. Tasks are idempotent because output keys are deterministic: chunk 17 at rendition 720p always writes the same key. Spot or preemptible machines, which are cheaper but can disappear, become practical. The orchestrator can be a workflow engine, because it needs durable state, retries, timeouts and visibility into stuck jobs (see distributed transactions for the saga mindset, and the Temporal article for workflow engines).
Prioritise. Processing a creator's first video, or a video from a partner with a scheduled release, should not wait behind a bulk catalogue re-encode. Use priority queues or separate worker pools, and cap the cost of the low-priority pool.
Quality versus cost. More compute spent on encoding produces smaller files at the same visual quality, and the saving repeats on every view. Popular videos justify an expensive second pass with a more efficient codec, while the long tail does not. A common policy is to encode cheaply first so the video is available fast, then re-encode only what turns out to be popular.
flowchart TD
U["Upload finished"] --> E1["Encode standard ladder, publish"]
E1 --> W["Collect views for a trial window"]
W --> A{"Views above the re-encode threshold?"}
A -->|no| T["Tier renditions down over time"]
A -->|yes| B{"Spare off-peak compute?"}
B -->|yes| R["Re-encode with efficient codec"]
B -->|no| Q["Queue for the next window"]
R --> S["Swap manifest, keep old files briefly"]
How to read it: every video gets the cheap path, and only the popular few earn the expensive pass, which pays back through lower egress on each later view.
Pipeline choice
Use when
Cost
Alternative
Whole-file transcode on one worker
Prototype, short clips
Slow, restarts from scratch on failure
Chunked parallel transcode
Chunked parallel transcode
Long videos, many renditions
Orchestration and stitching complexity
Per-rendition parallelism only
Encode everything with the best codec
Premium, small catalogue
Very high compute
Two-pass: cheap first, upgrade popular
Fixed ladder for every title
Simple, predictable
Wastes bits on easy content
Per-title ladders chosen from content analysis
Deep dive 2: adaptive bitrate streaming and delivery
The problem. Viewers have different screens and constantly changing bandwidth. A single fixed-quality file stalls on slow links and wastes bits on fast ones.
The solution: adaptive bitrate (ABR) over HTTP. Each rendition is cut into short segments (commonly a few seconds each). A manifest lists the renditions and their segment URLs. HLS (HTTP Live Streaming) uses playlist files, typically .m3u8, and DASH (Dynamic Adaptive Streaming over HTTP) uses an XML manifest, typically .mpd. They differ in format and ecosystem but share the idea. Many services publish both or use a common segment format so one set of media files can serve both.
The player downloads the manifest, picks a starting rendition, and fetches segments over plain HTTP. After each segment it measures throughput and buffer level and chooses the next segment's rendition: step up when the buffer is healthy and bandwidth is high, step down quickly when the buffer is draining. Switching works because every rendition's segments are aligned in time, so any segment boundary is a valid switch point.
sequenceDiagram
participant P as Player
participant A as Video API
participant C as CDN
participant O as Origin storage
P->>A: get video metadata
A-->>P: manifest URL
P->>C: GET master manifest
C->>O: fetch on miss
O-->>C: manifest
C-->>P: manifest with renditions
P->>C: GET segment 1 at medium quality
C-->>P: segment
P->>C: GET segment 2 at higher quality
C-->>P: segment
Why plain HTTP and a CDN? Segments are small, immutable static files. Any ordinary web cache can store them, and no special streaming server is needed per viewer. Popular segments are served from edge caches near viewers. Because each segment is a distinct URL, the cache key is simple and the hit ratio is high for popular titles. A shield or mid-tier cache cuts origin load further. The first viewer after upload suffers a cold cache, so for highly anticipated releases you can pre-warm edges by requesting the first segments in advance.
Fast start. Begin at a low or medium rendition to get the first frame quickly, then ramp up. Keep the first segments short and put the manifest and initial segments in the cache of the viewer's region.
Long tail versus head. A small fraction of videos account for most views. Edge caches have finite space, so unpopular videos mostly miss the edge and are served from the origin or a regional tier. That is acceptable if the origin is sized for the tail, and it is the reason storage tiers exist.
Deep dive 3: storage tiers and cost
Cost has three big drivers: egress, storage and encoding compute.
Egress. Usually the largest. Levers: efficient codecs, per-title ladders that avoid over-provisioned bitrates, a high CDN hit ratio, negotiated CDN pricing, and (in very large deployments) caches inside network providers' data centres. Capping the top bitrate on small screens saves bandwidth with little visible loss.
Storage. Keep originals (so you can re-encode with future codecs) in a cooler tier, and hot renditions in the standard tier. Move rarely watched titles' high-bitrate renditions to colder storage and regenerate from the original if they become popular again, using lifecycle rules. Archival retrieval can be slow and carries fees, so check your provider's documented behaviour before relying on it for playback.
Compute. Use interruptible machines for the bulk pool, schedule low-priority re-encodes off-peak, and skip renditions no device would pick.
Tier
Holds
Access pattern
Notes
CDN edge
Popular segments and manifests
Constant
Highest speed, limited space
Standard object storage
Current renditions
Frequent for the head, sporadic for the tail
Origin for CDN misses
Infrequent access or archive
Originals, old high bitrates
Rare
Cheaper storage, retrieval cost or delay
Thumbnails and metadata
Generate several thumbnail candidates at evenly spaced times, then choose one by a simple heuristic (avoid black frames, prefer sharp ones) or let the creator choose. Generate a sprite sheet of tiny frames and a small index so the player can show hover previews while seeking without fetching video. These are small images served through the CDN like any other asset. Metadata, titles and counters live in the database. View counters should not be incremented per request in the main database: buffer them in a stream or in memory and aggregate periodically, accepting that counts are approximate for a while.
NOTE
Content protection (DRM) usually works by encrypting segments and handing keys only to authorised players through a licence service. It adds a licence server dependency to the playback start path, so it needs its own availability design. We only mention it here.
Bottlenecks and scaling
CDN capacity and egress. The biggest lever is hit ratio. Monitor it per title class and per region.
Origin storage reads on cache misses. A new viral video misses on many edges at once. Use a shield tier and request coalescing so one origin read serves many edge requests.
Transcoding backlog. Scale workers on queue age and use priorities. Reserve capacity for fast-turnaround uploads.
Metadata database. Read-heavy and small: cache and replicate. It is rarely the bottleneck.
Upload bandwidth. Direct-to-storage uploads spread across the object store's capacity, not your servers'.
Failure modes
Failure
Effect
Mitigation
Transcode worker dies or is preempted
Task lost
Retry the chunk, deterministic output keys, orchestrator tracks state
Corrupt or malicious upload
Pipeline errors
Validate at the probe step, sandbox decoders, mark failed with a reason
One rendition missing
Gaps in the ladder
Package only complete renditions, or block ready until the minimum ladder exists
CDN regional problem
Slow or failing playback in a region
Multi-CDN, or player failover to another host listed in the manifest
Origin overload from a viral miss storm
Slow startup
Shield tier, request coalescing, pre-warming
Manifest points to missing segment
Playback error
Write segments first, manifest last, and verify before publishing
Orchestrator state lost
Jobs stuck
Durable workflow state, timeouts, sweeper for stuck jobs
Cost runaway from a bug
Surprise bill
Budgets, alerts on egress and compute, per-job limits
The rule "segments first, manifest last" is the video version of publishing atomically: the manifest is the commit point.
Trade-offs worth stating
Decision
Option A
Option B
How to choose
Segment length
Short (about 2 to 4 seconds)
Long (about 6 to 10 seconds)
Short adapts faster and starts sooner but has more requests and overhead. Long is more efficient
Ladder
Fixed for all
Per title
Per title saves egress on easy content at the cost of analysis compute
Codec
Widely supported
More efficient but newer
Offer both: efficient for capable devices, compatible as the fallback
Original retention
Keep forever
Delete after encoding
Keep, in cold storage, so you can re-encode later
CDN
One provider
Several
Several for resilience and price leverage, with the cost of added complexity
Interview cheat sheet
Step
What to say
Key numbers (illustrative)
Requirements
Upload, watch, seek, thumbnails. Fast start, rare rebuffering
100M views per day
Estimate
Egress dominates. Transcode compute is large but schedulable
About 11 PB per day, about 3 Tbps peak, about 20k cores
API
Multipart presigned upload, manifest URL
Direct to storage
Data
Metadata DB, immutable segments in object storage, derived index
A realistic scenario, not a case history. This is a composite example built from common production patterns. It does not describe any specific company, and every number in it is an illustrative assumption.
Assume a team of fourteen runs the control and batch planes on Kubernetes, with PostgreSQL for metadata, Redis for caching, a workflow engine such as Temporal for the pipeline, an S3-compatible object store with lifecycle tiers, FFmpeg-based encoders, one or two CDN vendors, and Prometheus and Grafana for observability.
SLOs and dashboards
SLI
SLO (assumed)
Dashboard panels
Playback start
95% of starts under 2 s
Time to first frame, manifest latency, first segment latency
Rebuffering
Under 1% of watch time
Rebuffer ratio by device, region and CDN
Time to ready
90% of videos ready within 10 minutes
Queue age, per-task duration, failed tasks by type
CDN efficiency
Hit ratio above an agreed target per class
Hit ratio by title popularity, origin egress, shield hits
Spend
Egress and compute within budget
Daily egress, core-hours, storage by tier, cost per watch hour
Client-side metrics matter most here, so the player must report startup time and rebuffering. Server logs alone cannot see what a viewer experienced.
Capacity-review cadence
Weekly: transcode queue age by priority, spot interruption rate, hit ratio on new releases, spend against budget.
Monthly: storage by tier, share of views from the top 1% of titles, re-encode backlog and payback, CDN contracts versus actual egress.
Quarterly: a failover drill between CDNs, a chunk-level retry test under forced worker kills, an estimate refresh against the roughly 11 PB per day egress assumption.
Before scheduled releases: pre-warm the first segments and reserve worker capacity.
Re-hydrate or re-encode from the original, serve a lower rung meanwhile
Decision record: cheap-first encoding with upgrade for popular titles
Field
Entry
Context
Encoding with the most efficient codec for every title is expensive, but egress dominates the bill and efficient files lower it on every view.
Decision
Encode a standard ladder immediately, then re-encode only titles that pass a view threshold in a trial window, using off-peak interruptible capacity.
Consequences (good)
Fast availability, compute spent where it pays back, a long tail that stays cheap.
Consequences (bad)
Two encodes for popular titles, manifest swaps to manage, and a threshold to tune.
Revisit when
Measured egress saved per re-encode no longer covers its compute cost, or the share of views from untouched titles grows.
Cost view
Egress comes first by a wide margin at these assumptions, then storage, then compute.
Cost driver
Lever
Egress
Hit ratio, efficient codecs, per-title ladders, capping top bitrates, CDN negotiation
Storage
Cold tier for originals, lifecycle rules for old renditions, delete failed partial outputs
Transcoding
Interruptible workers, off-peak scheduling, skipping rungs no device selects
Operations
Fewer vendors where resilience allows, budgets with alerts per job type
Architect's notes
Instrument the player before the backend. Startup time and rebuffering are the user's truth. Backend dashboards can be green while viewers suffer.
Estimate egress per watch hour, then track it. It is a number both finance and engineering can act on.
Ask what happens to originals. Retention decisions are cheap on day one and impossible to reverse after deletion.
The orchestrator is a critical dependency. Give it the same availability thinking as the database, including backups of workflow state.
Multi-CDN is a business decision as much as a technical one. It adds routing logic, extra monitoring and contract complexity. Defer it until a single-vendor incident or price leverage justifies it.
Retrieval fees are a hidden trap. Cold tiers save on storage, but a viral archived title can cost more than it saved. Model the worst week.
Common mistakes
Serving video from application servers. They cannot supply the bandwidth and tie up connections. Serve segments from a CDN.
Transcoding whole files on one worker. One failure restarts hours of work. Chunk, parallelise and checkpoint.
One quality for everyone. Slow viewers stall and fast viewers waste bits. Use an adaptive ladder.
Ignoring egress cost in the estimate. It is usually the largest bill. Estimate it first.
Publishing a manifest before all segments exist. Players hit missing files. Write segments first and the manifest last.
Deleting originals after encoding. You lose the ability to re-encode for new codecs and devices. Archive them cheaply instead.
Counting views with a database write per view. The database becomes the bottleneck. Aggregate through a stream or buffered counters.
Key takeaways
Video is bandwidth-bound. Egress at the CDN dominates both load and cost.
Process once, serve many times. Spend compute at upload to save bandwidth on every view.
Chunk the work. Parallel, idempotent chunk tasks make transcoding fast and resilient.
Adaptive bitrate is segment-based HTTP. HLS and DASH manifests let players switch quality at segment boundaries.
Segments are static files. Immutable, uniquely named objects are ideal for CDN caching.
Tier your storage. Keep originals cold, hot renditions warm, and regenerate tail content on demand.
The manifest is the commit point. Publish it only when everything it references exists.
Cost is a first-class requirement. Track egress, storage and compute with budgets and alerts.
Practice exercises
Exercise 1 — Starter (30 min): Using the illustrative assumptions, recompute egress per day if the average bitrate drops from 3 Mbps to 2 Mbps with the same watch time, and if average watch time rises to 8 minutes. Express the result in PB per day and as peak Tbps assuming a 3x peak factor.
Exercise 2 — Intermediate (2 hours): Design the DAG for a 30-minute upload with a six-rung ladder and 10-second chunks. Count the tasks, identify which can run in parallel, define the retry and timeout policy per task type, and describe how the orchestrator decides the video is ready when only the highest rung fails repeatedly.
Exercise 3 — Advanced (half day): Propose a storage and encoding policy that saves money for a catalogue where 1% of videos get 80% of views. Define the initial encode, the trigger for a better re-encode, the lifecycle rules for each tier, and the metric you would use to prove the policy is paying for itself. Include what happens when an archived video suddenly goes viral.
Lesson 13 of 14 · Advanced System Design and Case Studies
Design a video streaming platform: upload, parallel transcoding, adaptive bitrate delivery with HLS and DASH, CDN, storage tiers, thumbnails, cost and failures.
In the rest of this lesson
Reference deployment topology
Deep dive 1: the transcoding pipeline
Deep dive 2: adaptive bitrate streaming and delivery
Deep dive 3: storage tiers and cost
Thumbnails and metadata
Bottlenecks and scaling
Enrolling is free. It unlocks every lesson in Advanced System Design and Case Studies, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.
Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.
Get new articles by email
Free. We email a confirmation link first, and you can unsubscribe from any email.