Build a video platform: creators upload large video files, the system processes them into multiple qualities, and viewers around the world watch instantly at whatever quality their connection can handle.
Transcoding is converting a video from one format into others: different resolutions (1080p, 720p, 480p, 360p), different compression settings, sometimes different codecs. We cannot serve the original upload for three reasons. First, the source is usually enormous - a camera or editing tool exports at high bitrate, often several GB per hour. Serving that to a phone on cellular data would buffer forever and burn the viewer's data plan for quality their screen cannot even show. Second, viewers' conditions vary wildly - fiber at home, spotty cellular on a train - and one file has exactly one quality. To adapt, we need the same video available at several quality levels, which is exactly what transcoding produces: a ladder of renditions. Third, devices disagree about formats. A codec a desktop browser plays natively may not decode on an older phone or a TV. Transcoding normalizes every upload into the formats the whole device ecosystem can play. So the deal is: the creator uploads one file, and the system spends compute once - up front - to turn it into many files, so that billions of playbacks later are cheap. That trade defines this whole design.
Startup time is the gap between clicking play and seeing the first frame. It is dominated by a few round trips: fetch the manifest (the video's table of contents), then fetch the first segments of video. This is why players deliberately start at a conservative, lower quality - small first segments arrive fast, the video starts, and quality steps up once the player has measured your real bandwidth. That soft-looking first few seconds of a video is not a bug; it is the startup optimization working. Rebuffering is the mid-playback stall - the spinner - which happens when the player runs out of downloaded video before the network delivers more. Viewers forgive almost nothing less: measured watch time drops sharply with every stall, which is why "rebuffer ratio" is the metric streaming teams obsess over. Both metrics push the same direction: keep the video bytes as close to the viewer as possible (the CDN), chop video into small independently fetchable segments (so there is always a nearby next-chunk to grab), and let the client - which alone knows its own bandwidth and buffer - make the quality decisions. The server does not decide your quality; your player does. That division of labor is the heart of adaptive streaming.
Three conclusions fall straight out of the numbers. First, the bytes-versus-metadata split from Design Dropbox, pushed to its extreme. Video bytes (exabytes, served at terabits per second) live in object storage and are delivered almost entirely by the CDN - the estimates show ~65Tbps average egress, which no database or app-server fleet could touch. Metadata (a few KB per video: title, status, counts) lives in a database and is cached hard. The two never mix, and the system is really two systems: a control plane that decides what plays, and a byte-delivery hierarchy that plays it. Second, popularity skew makes the CDN work. A tiny fraction of videos gets the overwhelming share of watches, so edge caches with a small fraction of total storage absorb 95%+ of requests. The long tail lives in cheaper, colder object storage where a slower first byte is acceptable - nobody notices 300 extra milliseconds on a video with twelve lifetime views. Tiering by popularity is how exabytes stay affordable. Third, the write path's cost is compute, not disk. ~150K cores of continuous transcoding dwarfs the cost of storing the output - which justifies the defining trade: transcode once, up front, into every quality we will ever serve, so that each of billions of playbacks is a dumb, cheap file read. The inverse choice (transcode on demand per viewer) would replace a one-time cost with a per-view cost - exactly backwards for a 1,400:1 read-to-write ratio. Where does that leave databases? Small and comfortable: video metadata is point-lookups by video_id (a document/KV store scales this trivially), comments follow the partition-by-video pattern from Design a Newsfeed, and view counts use the same buffered-counter trick as newsfeed likes. Nothing about the database tier here is exotic - all the exotic parts are in the pipeline and the CDN.
UPLOAD + PROCESS - the write path (compute-heavy, runs once)
[Creator] --init--> [Upload Service] --> [Videos DB: status=uploading]
| presigned chunk URLs (resumable - the Design Dropbox flow)
v
[Object Storage: source file]
| upload complete -> enqueue
v
[Processing Queue] --> [Transcoding Fleet (elastic)]
| fan-out: one job per rendition
| 1080p | 720p | 480p | 360p + thumbnails
v
[Object Storage: renditions, chopped into
4-10s segments] --> write manifest
|
v
[Videos DB: status=ready] (low rendition publishes
first; ladder fills in)
WATCH - the read path (runs billions of times, must be dumb-cheap)
[Viewer] --> [Playback Service] --> [Videos DB] visibility check
| returns signed manifest URL
v
[CDN edge] --(miss)--> [Origin shield] --(miss)--> [Object Storage]
|
| player: reads manifest -> starts at conservative quality ->
| measures bandwidth + buffer each segment -> steps up/down
v
[View events] --> [Event stream] --> [fraud filter] -->
[buffered counters] --> [Videos DB counts]Every processed video is chopped into small segments - 4 to 10 seconds of video each - and each rendition (1080p, 720p, 480p...) is its own parallel set of segments. The manifest (an HLS or DASH playlist file) is the table of contents: it lists the available renditions with their bitrates, and where every segment of each rendition lives. Playback is then a loop the player runs entirely on its own: fetch the manifest, start with a conservative rendition so the first frame arrives fast, and then, before each next segment, look at two numbers - how fast recent segments downloaded, and how many seconds of video are sitting in the buffer. Bandwidth comfortably above the next rendition's bitrate and a healthy buffer? Step up. Downloads falling behind playback? Step down before the buffer runs dry. Because all renditions are cut at the same time boundaries, swapping quality between segments is invisible - no gap, no restart. Seeking works the same way: jumping to minute 43 means fetching the segment covering minute 43, not anything before it. Notice who is in charge: the server never picks your quality. Only your player knows your bandwidth and buffer this second, so the intelligence lives at the edge of the network, in the client - and the server side stays a dumb, cacheable file store, which is exactly what lets a CDN serve it at planetary scale.
Design Dropbox goes to great lengths to store identical bytes exactly once - block-level dedup, content hashes, reference counts. This design does the opposite on purpose: every video is stored as five-plus renditions, multiplying the bytes several-fold. Both are right, and the difference is which resource dominates each system's cost. For Dropbox, storage is the product: files sit for years and are rarely read, so bytes-at-rest dominate cost and dedup pays directly. For YouTube, serving is the product: the 1,400:1 watch-to-upload ratio means each video's bytes are read astronomically more often than written, so the cost that matters is per-playback. Pre-computing every rendition makes each playback a dumb file read from a cache. The alternative - transcoding on demand for each viewer's bandwidth - would attach a compute cost to every single view, which at billions of views a day is the most expensive possible place to spend. Same logic tiered the other direction: the long tail (most videos, few views) can afford a shorter ladder - some platforms skip generating the top renditions until a video shows traction, spending transcode compute only where playbacks will amortize it. The interview-worthy sentence: dedup and deliberate duplication are the same decision - minimize the dominant cost - made in systems whose dominant costs are opposites.
Storage layout & schemas
- Videos (NoSQL document store, keyed by video_id)
{
"video_id": "v_8f3e2d1c",
"channel_id": "ch_42",
"title": "...", "description": "...",
"visibility": "public", -- public | unlisted | private
"status": "ready", -- uploading | processing | ready | failed
"duration_seconds": 1260,
"thumbnail_ref": "thumbs/v_8f3e2d1c/1.jpg",
"manifest_key": "manifests/v_8f3e2d1c/master.m3u8",
"renditions": [ -- fills in progressively
{ "quality": "360p", "bitrate_kbps": 500, "ready": true },
{ "quality": "720p", "bitrate_kbps": 1800, "ready": true },
{ "quality": "1080p", "bitrate_kbps": 3500, "ready": false }
],
"view_count": 182304, -- denormalized, buffered-flush updated
"like_count": 8921,
"comment_count": 412,
"uploaded_at": "2026-07-05T18:04:11Z"
}
-- access pattern: point lookup by video_id (every watch), list by channel
- Object Storage layout (the bytes; tiered hot -> cold by popularity)
/source/{video_id}/original.mp4 -- the upload, never deleted
/renditions/{video_id}/{quality}/seg_00001.ts -- 4-10s segments
/manifests/{video_id}/master.m3u8 -- the table of contents
/thumbs/{video_id}/{n}.jpg
- Processing Jobs (durable queue + status)
job_id, video_id, rendition ('720p'), time_range (for split long videos),
status (queued|running|done|failed), attempts, last_error
-- one job per rendition (x time-range); retries are per-job;
-- repeated failures land in a dead-letter queue
- Comments (NoSQL, the Newsfeed pattern)
PK: video_id, SK: ts#comment_id
Attributes: user_id, text, like_count
-- flat by design; nesting was de-scoped for the same reasons as Newsfeed
- Likes (NoSQL)
PK: video_id, SK: user_id -- one row = idempotent like
GSI: user_id -> videos this user liked
Redis / stream keys
- View-count buffers (the Newsfeed counter pattern, hotter)
"views:{video_id}" -> pending increments, flushed every few seconds
- Hot metadata cache
"video:{video_id}" -> rendered metadata JSON, TTL ~60s
- View event stream (Kafka-style, short retention)
topic: view-events, partitioned by video_id
consumers: fraud filter -> counter aggregator -> analytics sink{
"title": "Building a Cabin in 30 Days",
"description": "Full timelapse and walkthrough",
"file_size": 8589934592,
"content_type": "video/mp4",
"visibility": "public"
}{
"video_id": "v_8f3e2d1c",
"upload_session_id": "up_7a2b8f4e",
"chunk_size": 10485760,
"chunk_count": 819,
"upload_urls": [
{ "chunk_number": 1,
"presigned_url": "https://storage.example.com/source/...(signed)",
"expires_at": "2026-07-05T20:04:11Z" }
],
"parallel_uploads": 4
}
// Resumable: chunks upload in parallel, straight to object storage; a
// dropped connection re-requests URLs only for missing chunks - the
// Design Dropbox upload flow, minus the dedup (every video's bytes are
// effectively unique, so content-hash dedup would find nothing).{
"video_id": "v_8f3e2d1c",
"status": "processing",
"watchable": true,
"renditions": [
{ "quality": "360p", "ready": true },
{ "quality": "720p", "ready": true },
{ "quality": "1080p", "ready": false, "eta_minutes": 12 }
],
"view_count": 3,
"uploaded_at": "2026-07-05T18:04:11Z"
}
// Progressive publishing: the video went live the moment 360p finished.
// Higher qualities join the manifest as their transcode jobs complete -
// which is why fresh uploads sometimes max out at 360p for a few minutes.{
"video_id": "v_8f3e2d1c",
"manifest_url": "https://cdn.example.com/manifests/v_8f3e2d1c/master.m3u8?sig=...&exp=1751911451",
"duration_seconds": 1260,
"thumbnail_url": "https://cdn.example.com/thumbs/v_8f3e2d1c/1.jpg"
}
// The only control-plane call in a playback. The manifest URL is signed
// and short-lived; the CDN validates the signature on every segment
// request, so private/unlisted videos stay protected even at the edge.
// From here on, it is entirely player <-> CDN.#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=640x360 360p/playlist.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=1280x720 720p/playlist.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=3500000,RESOLUTION=1920x1080 1080p/playlist.m3u8 // The table of contents: one line per rendition with its bitrate. The // player starts low (fast first frame), measures how quickly segments // arrive, and steps up or down the ladder between segments. The server // never chooses the quality - the client does.
{
"event": "progress",
"session_id": "ps_9d4f1a2b",
"position_seconds": 300,
"watched_seconds": 290
}{
"ok": true
}
// Beacons feed the stream -> fraud filter -> buffered counters, never
// the database directly. Displayed counts are approximate in real time
// and reconciled in batch - the Newsfeed like-counter pattern at 6B
// events/day. Fraud filtering runs BEFORE counting: view counts
// eventually feed monetization, so inflation is a financial attack.Rendition ladder: the ordered set of qualities a video is available in (360p → 720p → 1080p...), each rung a complete, independently playable copy at a different bitrate. Built once at upload; every playback climbs and descends it freely. Never taller than the source - upscaling spends compute to add zero information. Segment alignment: all renditions are cut at the same time boundaries, so segment 47 covers the same seconds of video in every quality. That is the property that makes quality switching seamless - the player finishes segment 47 at 720p and starts segment 48 at 480p with no gap, no overlap, no decoder restart. Without alignment, adaptive streaming would stutter at every switch. Origin shield: a cache tier between the CDN edges and object storage. When a video goes viral, thousands of edges miss the same new segment at once; the shield absorbs those misses, and request coalescing turns the thousand identical fetches into one trip to storage. Edges protect the shield, the shield protects storage - the same "each layer absorbs its multiplier" logic as the whole read path.