Design a Video Streaming Platform
Design a YouTube/Netflix-style platform covering resumable uploads, a parallel transcoding pipeline, adaptive bitrate streaming with HLS/DASH, and CDN-based delivery at global scale.
Requirements
Functional
- Creators upload videos (up to several GB) from web and mobile.
- Uploaded videos are processed into multiple resolutions and become watchable.
- Viewers stream videos on any device with smooth playback, seeking, and resume.
- Video metadata: title, description, thumbnails, view counts; basic search and recommendations exist but are out of scope here.
Non-functional
- Smooth playback: start-up under ~2 s, rebuffering rare, quality adapts to the network.
- Massive read skew: views dwarf uploads, and a small fraction of videos gets most views.
- Durability: an uploaded original must never be lost.
- Processing latency: a typical video playable within minutes of upload (low resolutions first).
- Cost efficiency: bandwidth and storage are the dominant costs, so every design choice is weighed against them.
Split the problem early into two almost independent systems: the upload and processing pipeline (write path, batch-like, latency in minutes) and the playback path (read path, real-time, served almost entirely by the CDN). Interviewers like hearing that split stated explicitly.
Capacity Estimates
Assume 200 M daily viewers each watching 5 videos of ~5 minutes, 500 K uploads per day averaging 300 MB, and an average delivered bitrate of 3 Mbps.
| Metric | Math | Value |
|---|---|---|
| Views per day | 200 M × 5 | 1 B |
| Watch time | 1 B × 5 min | 5 B minutes/day |
| Average concurrent streams | 5 B min / 1,440 min | ~3.5 M |
| Egress bandwidth | 3.5 M × 3 Mbps | ~10 Tbps (peaks 2-3× higher) |
| Upload ingest | 500 K × 300 MB | 150 TB/day |
| Upload rate | 150 TB / 86,400 s | ~14 Gbps |
| Encoded renditions | ~1.5× original across all bitrates | ~225 TB/day |
| Storage per year | (150 + 225) TB × 365 | ~135 PB before replication |
Ten-plus terabits per second of egress cannot come from a few data centres; it must be pushed to the edge. Storage grows by petabytes per month, which motivates tiered storage for old, rarely watched videos.
API Design
POST /api/v1/videos // create upload session
Body: { "title": "...", "sizeBytes": 314572800, "contentType": "video/mp4" }
201: { "videoId": "v_8812", "uploadId": "up_31", "partSize": 8388608,
"partUrls": ["https://upload.example.com/...part=1&sig=...", ...] }
PUT <partUrl> // client uploads each 8 MB part directly to object storage
POST /api/v1/videos/{videoId}/complete // { "uploadId": "up_31", "parts": [{ "n": 1, "etag": "..." }] }
GET /api/v1/videos/{videoId} // metadata + status: UPLOADING | PROCESSING | READY | FAILED
GET /api/v1/videos/{videoId}/play // { "manifestUrl": "https://cdn.example.com/v_8812/master.m3u8?token=..." }
POST /api/v1/videos/{videoId}/progress // { "positionSec": 312 } (resume watching, analytics)
Uploads go straight to object storage through pre-signed, chunked (multipart) URLs, so API servers never proxy gigabytes of video and a failed chunk is retried on its own.
Data Model
| Store | Contents | Key |
|---|---|---|
| Video metadata (sharded SQL) | video_id, owner, title, status, duration, renditions, created_at | video_id |
| Raw object store | original uploads | raw/{video_id} |
| Encoded object store | segments + manifests per rendition | enc/{video_id}/{rendition}/seg_{n} |
| Job store | transcoding DAG, task status, retries | job_id |
| View counters / watch history | aggregated counts, per-user positions | video_id, user_id |
CREATE TABLE videos ( video_id BIGINT PRIMARY KEY, owner_id BIGINT NOT NULL, title VARCHAR(200), status VARCHAR(16) NOT NULL, -- UPLOADING, PROCESSING, READY, FAILED duration_sec INT, manifest_key VARCHAR(255), created_at TIMESTAMP NOT NULL ); CREATE TABLE renditions ( video_id BIGINT, codec VARCHAR(16), -- h264, vp9, av1 height INT, -- 240, 480, 720, 1080, 2160 bitrate_kb INT, PRIMARY KEY (video_id, codec, height) );
High-Level Design
flowchart LR CR[Creator] -->|"multipart upload"| RAW[(Raw Object Store)] CR --> API[Video API] API --> META[(Metadata DB)] RAW -->|"upload complete event"| Q[[Job Queue]] Q --> ORC[Pipeline Orchestrator] ORC --> W[Transcode Workers] W --> ENC[(Encoded Segment Store)] ORC --> META ENC --> CDN[CDN Edge Caches] V[Viewer Player] --> API V -->|"manifest and segments"| CDN V --> AN[[Playback Analytics]]
Upload path: create session → upload parts directly to the raw bucket → complete → an event triggers the processing pipeline → status moves to READY when the first renditions are done.
Playback path: the player asks the API for a signed manifest URL, then fetches the manifest and video segments from the nearest CDN edge. The origin is only touched on cache misses.
Deep Dives
Upload pipeline
- Resumable multipart upload: the file is split into fixed parts (5-16 MB). The client tracks which parts succeeded, so a dropped mobile connection resumes instead of restarting.
- Validation first: after completion, a lightweight job probes the container (format, codec, duration, corruption) and runs content-safety and copyright checks before spending compute on transcoding.
- Durability: the original is stored with high redundancy (multi-AZ object storage) and kept as the "mezzanine" source for future re-encodes with better codecs.
- Status updates flow back via the metadata DB, and the creator is notified when the video is ready.
Transcoding as a parallel DAG
Encoding a 1-hour 4K video serially can take hours. Instead, the orchestrator builds a directed acyclic graph of tasks:
- Split the source into chunks of a few seconds, aligned to keyframes (GOP boundaries).
- Encode each chunk for each target rendition in parallel across a worker fleet (e.g. 240p to 4K, H.264 for compatibility, VP9/AV1 for efficiency).
- Assemble/package the encoded chunks into streaming segments and write the manifests.
- Side tasks in parallel: extract audio tracks, generate thumbnails, produce captions, compute a perceptual quality score.
- Workers pull tasks from a queue; failed tasks are retried individually, and idempotent outputs (deterministic object keys) make retries safe.
- Prioritize: encode a low resolution first so the video becomes playable quickly, then add higher renditions. Fresh uploads from popular creators can jump the queue.
- Spot/preemptible instances cut cost since chunk tasks are short and restartable.
Chunk-level parallelism turns transcoding latency from "proportional to video length" into "roughly the time to encode one chunk plus scheduling overhead", and it isolates failures: a crashed worker costs one chunk, not the whole job.
Per-title encoding: a static cartoon looks fine at a much lower bitrate than a sports match. Analysing complexity and picking a custom bitrate ladder per video saves significant bandwidth at equal perceived quality, which matters when egress is the largest bill.
Adaptive bitrate streaming (HLS / DASH)
- Each rendition is cut into segments of ~2-6 seconds. A master manifest (
.m3u8for HLS,.mpdfor DASH) lists all renditions and their bandwidths; each rendition has a media playlist listing its segments. - The player measures throughput and buffer level, and before each segment picks the best rendition it can sustain. It switches up or down at segment boundaries without interrupting playback.
- Start with a modest rendition for fast start-up, then climb. Buffer-based algorithms prevent oscillation better than throughput-only ones.
- Everything is plain HTTP GETs of static files, which is exactly why CDNs can cache it.
- CMAF packaging lets HLS and DASH share the same fragmented-MP4 segments, halving storage for dual-format delivery.
| Segment length | Pros | Cons |
|---|---|---|
| Short (~2 s) | Faster adaptation, lower live latency | More requests and manifest churn |
| Long (~6-10 s) | Better compression, fewer requests | Slower to react to bandwidth drops |
CDN and content placement
- Pull CDN for the long tail: an edge fetches a segment from origin on first request, then caches it. Simple and storage-efficient.
- Push / pre-positioning for predictable hits: popular titles (new episode releases) are copied to edge caches during off-peak hours before demand arrives. Netflix's Open Connect appliances, installed inside ISPs, are the canonical example.
- Tiered caching: edge → regional mid-tier → origin shield, so a miss at hundreds of edges results in one request to origin.
- Signed URLs/tokens with short expiry protect content; DRM (Widevine, FairPlay, PlayReady) is applied at packaging time for premium catalogues.
- Multi-CDN with client-side or DNS steering improves availability and lets you route by price and performance.
Do not stream video from your application servers or directly from the origin bucket. Without a CDN, egress costs and latency explode, and a single viral video can saturate your origin.
Scaling & Reliability
- Metadata DB: shard by
video_id, cache hot metadata, and use read replicas; it is small compared with video bytes. - Storage tiering: move renditions of rarely watched videos to cold storage; keep only a few renditions (or re-encode on demand) for the long tail.
- View counts are aggregated through a stream (Kafka → stream processor) rather than incremented per view in the DB.
- Pipeline resilience: the orchestrator persists DAG state so it can resume after a crash; dead-letter queues capture poison videos for inspection.
- Back pressure: during upload spikes, queue depth grows rather than overwhelming workers; autoscale the encoder fleet on queue lag.
- Playback QoE monitoring: collect start-up time, rebuffer ratio and average bitrate from players to detect bad CDN nodes or encoding regressions.
- Live streaming (stretch): same ABR model but segments are produced in real time with short segments or low-latency HLS, and the encoder ladder is fixed rather than per-title.
Trade-offs
| Decision | Option A | Option B | Typical choice |
|---|---|---|---|
| Upload | Through API servers | Direct pre-signed multipart | Direct multipart |
| Transcode granularity | Whole file per worker | Keyframe-aligned chunks | Chunks (parallel DAG) |
| Codec | H.264 (plays everywhere) | VP9/AV1 (30-50% smaller) | Both; pick per device |
| Bitrate ladder | Fixed for all | Per-title | Per-title for popular content |
| CDN model | Pull (simple) | Push/pre-position (predictable hits) | Pull + push for hot titles |
| Long-tail storage | All renditions hot | Cold tier / on-demand encode | Tiered |
Interview Tips
- State the read/write split and size egress bandwidth first; it justifies the CDN-centric design.
- Show direct-to-storage multipart uploads and explain resumability.
- Describe transcoding as a DAG of chunk tasks and mention "low resolution first" for fast availability.
- Explain ABR concretely: segments, manifests, and a player that chooses renditions per segment.
- Distinguish pull and push CDN strategies and mention ISP-embedded caches.
- Mention cost levers (per-title encoding, modern codecs, cold storage); interviewers value cost awareness here.