Appearance
11.9 — Video Platform
Someone uploads a two-hour film. Ten minutes later a person on a train, on a phone, with a connection that swings between four megabits and four hundred kilobits, presses play and sees a picture within two seconds — and keeps seeing one as the train enters a cutting.
Three separate systems have to be true for that to happen. The upload has to survive a flaky connection, which 11.2 already solved. The two-hour source has to become a dozen different encoded versions in ten minutes, which is impossible serially and routine in parallel. And the bytes have to reach a phone in another country without travelling from your servers, because your servers cannot possibly carry them.
That last point is the one that shapes everything. This is the first study in this Part where the honest answer to "how do you scale it" is mostly "you do not serve the bytes yourself", and where the dominant number on the whole design is a network bill.
1. Requirements
Functional. Upload video up to 10 GB. Transcode into several resolutions and bitrates. Stream adaptively to any device. Thumbnails and preview scrubbing. View counts. Search by title.
Non-functional, with numbers.
- Playback starts within 2 seconds.
- Rebuffering ratio under 0.5% — the fraction of watch time spent staring at a spinner. This is the single metric a video product lives on.
- Upload to watchable within about 10 minutes for a typical video.
- 99.95% availability for playback. Being unable to upload for an hour is a bad day; being unable to watch is the product being down.
Out of scope today: recommendations (11.20), digital rights management specifics, comments, and live streaming — although section 8 says plainly why live is a different pipeline rather than a setting on this one.
The clarifying questions, and what each answer changes
"How much is watched versus how much is uploaded?" This ratio decides where the money goes. If a video is watched a million times, the encoding cost is irrelevant and the delivery cost is everything. If most uploads are watched twice, encoding five renditions of each is waste and you should encode lazily on first view.
"What is the longest video, and the largest single file?" Ten minutes of phone footage and a three-hour feature need the same pipeline but very different chunking, and the storage arithmetic changes by an order of magnitude.
"Which devices must play it?" This decides the codec question, which decides the bandwidth bill. A codec is the compression scheme the video is encoded with; newer ones make smaller files but only play on newer devices, so supporting old devices means storing an extra copy in an older format.
"Is a video private, unlisted or public?" Public content caches beautifully at the edge because everyone gets the same bytes. Private content needs signed access on every request, which is compatible with edge caching only if you are careful, and section 6.5 covers how.
"Can a video be watchable before it is fully processed?" If yes, publishing the lowest quality first turns a ten-minute wait into a one-minute one, and that is a real product win that costs one extra field in the data model.
2. Estimation
Storage. 500 hours uploaded per minute × 60 × 24 = 720,000 hours a day. At roughly 1 GB an hour for the source, plus about 2.5 times that for all the encoded versions together, that is ~2.5 PB a day. What that forces: object storage with lifecycle tiering, and a real decision about how long to keep the original source file after encoding — which is a surprisingly large lever, because the source is often the single biggest object and is only needed for re-encoding.
Egress, which is the number that dominates every other consideration. 1 billion watch-hours a day at an average 3 megabits a second: 1e9 hours × 3,600 seconds × 3 Mbps = 1.35 exabytes a day, which is about 125 terabits a second sustained. What that forces: no origin infrastructure serves 125 Tbps. More than 95% of bytes must come from content delivery network edge caches, and the design's central engineering job is maximising that percentage. Every other decision on this page — immutable segment URLs, tiered caching, codec choice — exists to serve that one number.
Transcoding capacity. 720,000 source-hours a day × 5 renditions = 3.6 million encode-hours a day. If a modern encoder runs at roughly real time per rendition, that is 3.6M ÷ 24 = 150,000 concurrent encoder cores. What that forces: a fleet sized by a queue rather than provisioned for peak, and — because that is an enormous compute bill — a strong reason to make every encoding task independently retryable so the fleet can run on interruptible capacity at a fraction of the price.
Cost balance, which decides the trade-offs. Storage at 2.5 PB a day is expensive. Egress at 1.35 EB a day is roughly five hundred times more bytes moved than stored. What that forces: whenever you can spend storage to save egress, do it. This is why storing an extra copy in a more efficient codec is obviously right, and why storing a redundant rendition nobody watches is only mildly wrong.
Latency budget for playback start. Two seconds covers: resolving the manifest (~100 ms), fetching the first segment from an edge (~200 ms if cached, ~800 ms if it has to come from origin), and decoding enough to render (~300 ms). What that forces: the first segment must be small and must be cached. Starting at the lowest rendition deliberately is not a compromise, it is how the budget is met.
3. API
http
POST /videos
{ "title": "Tunnel timelapse", "visibility": "public", "sizeBytes": 4294967296 }
→ 201 Created
{ "videoId": "v_7431", "uploadId": "up_01J…", "parts": [ … ] } # the 11.2 flowhttp
POST /videos/v_7431/publish
→ 202 Accepted
{ "videoId": "v_7431", "status": "processing" }http
GET /videos/v_7431
→ 200 OK
{ "videoId": "v_7431", "status": "partially_ready", "duration": 7241,
"renditions": [
{ "profile": "240p", "status": "ready" },
{ "profile": "480p", "status": "ready" },
{ "profile": "1080p", "status": "encoding", "percent": 62 },
{ "profile": "2160p", "status": "queued" } ],
"manifestUrl": "https://cdn.example/v/7431/master.m3u8",
"playable": true }http
GET /v/7431/master.m3u8 # the list of available qualities
GET /v/7431/720p/seg-00412.m4s # one 4-second chunk of video
POST /videos/v_7431/views # → 204, fire-and-forget, batched by the clientStatus is a first-class field, not an afterthought. The gap between "upload finished" and "anyone can watch this" is minutes, so this is a long-running operation exposed as a status resource rather than a request somebody holds open (9.6.1). The states are uploading → processing → partially_ready → ready, with failed reachable from any of them.
playable: true alongside status: partially_ready is the product win. The video can be watched at 480p while 4K is still encoding. Expressing that requires per-rendition status, which is why section 4 tracks renditions as rows rather than as a column on the video.
Segment URLs contain no query string, no token and no user identifier. That is deliberate and it is worth stating as a rule: anything that varies per user in a segment URL splits one cached object into millions of cache entries and destroys the hit ratio the whole design depends on. Section 6.5 explains how access control is done without putting anything user-specific in the path.
4. Data model
videos
video_id UUID PRIMARY KEY
owner_id UUID NOT NULL
title TEXT, description TEXT
duration_s INT
status SMALLINT NOT NULL -- uploading | processing | partially_ready | ready | failed
source_key TEXT -- object storage key of the original
visibility SMALLINT NOT NULL
created_at TIMESTAMPTZ
renditions
video_id UUID, profile TEXT -- '240p', '480p', … or 'av1-1080p'
codec TEXT, width INT, height INT, bitrate_kbps INT
segment_prefix TEXT -- where its segments live
status SMALLINT NOT NULL -- queued | encoding | ready | failed
PRIMARY KEY (video_id, profile)
encode_tasks -- the workflow's unit of work
task_id UUID PRIMARY KEY
video_id UUID, profile TEXT, chunk_index INT
status SMALLINT, attempts SMALLINT
output_key TEXT
views -- append-only, time-partitioned
video_id, bucket_minute, count -- produced by aggregation, never incremented per viewAccess patterns:
| Query | Frequency | Returns |
|---|---|---|
| Fetch a segment | ~10M/s, 95%+ at the edge | one file |
| Fetch a manifest | ~50,000/s | one small file |
| Read video metadata for a page | ~50,000/s | one row plus renditions |
| Claim the next encode task | ~2,000/s | one row |
| Update a task's status | ~4,000/s | one row |
Metadata is small and relational; media is object storage. This is the same split as 11.2, and it is worth noticing that it appears again for the same reason — the two things differ by many orders of magnitude in size and by everything in access pattern.
Renditions are separate rows because they finish at different times. A single status column on the video cannot express "watchable at 480p, still encoding 4K", and that state is the difference between a creator waiting one minute and waiting ten.
Views are aggregated, never incremented. A row-per-view counter on a popular video is millions of writes to one row, which no partitioning scheme fixes. Client batches events, they land on a stream, a consumer rolls them into per-minute buckets, and the displayed number lags by seconds and is approximate at the top end (11.19).
5. Architecture
The caching structure is worth its own panel, because the hit ratio is the budget.
6. Deep dives
6.1 Chunked parallel transcoding
Encoding a two-hour video serially takes hours, and there are five renditions to produce. The fix is to stop treating the video as one job.
Split the source at safe cut points. Video compression stores most frames as differences from earlier frames, so you cannot cut anywhere — you can only cut at the frames that are encoded independently, which occur every couple of seconds. Those are the boundaries, and finding them is what the probe step at the start of the pipeline does.
Encode every (chunk × rendition) pair as an independent task. A two-hour video split into 30-second chunks is 240 chunks; times five renditions is 1,200 independent tasks. With a thousand workers free, wall-clock time is one task's duration plus queue wait — minutes, not hours. Then concatenate per rendition, which is fast because the pieces are already encoded, and package into segments and a manifest.
Three properties this depends on, and the second one is the subtle one.
Determinism and idempotency. The same chunk with the same settings produces the same bytes, so a task can be retried anywhere with no coordination. That is what allows the fleet to run on interruptible machines — capacity that can be taken away at any moment, sold at a fraction of the normal price — which turns the largest compute cost in the system into the cheapest kind of capacity.
Consistent rate control across chunks. If each chunk's encoder independently decides how many bits to spend, the quality visibly pulses at chunk boundaries — every 30 seconds the picture gets slightly better or worse, and viewers notice even if they cannot name it. The fix is to constrain rate control: either a fixed quality target rather than a fixed bitrate, or a first pass that analyses the whole video and distributes its statistics to the chunk workers. This detail is what separates a design that works from one that looks broken, and it almost never comes up unprompted.
Orchestration that survives failure. Twelve hundred tasks with retries, partial completion and a final assembly step is a durable workflow (11.18), not a script. It must resume from where it stopped rather than restarting, or a failure at task 1,199 costs you the whole video.
And the product refinement: encode the lowest rendition first and publish it. The video becomes watchable in about a minute while the rest continues, which is what per-rendition status exists for.
6.2 Adaptive streaming, and why the client decides
The video exists as segments — short chunks of 2 to 6 seconds — at each quality level, plus a manifest, which is a small text file listing the available qualities and where their segments are.
The segments are time-aligned across qualities: segment 412 covers the same four seconds of the film in every rendition. That alignment is what makes switching mid-playback seamless, because the player can fetch segment 412 at 480p after fetching 411 at 1080p and the picture continues without a gap.
The player runs the loop. It downloads a segment, measures the throughput it actually achieved (bytes divided by elapsed time) and looks at how much buffered video it is holding, then chooses the quality for the next segment. Throughput dropping or buffer draining means step down. Both healthy for a while means step up, cautiously.
Why the client and not the server. Only the client knows its real conditions. The server sees one connection and cannot distinguish congested home Wi-Fi from a slow last mile from a phone that just handed over to a cell tower. A server-side decision would be a guess made with worse information and a round trip of lag.
The segment-length trade. Shorter segments start faster and adapt more quickly, and they compress slightly worse and cost more requests. Longer segments compress better and adapt sluggishly. Four seconds is the usual compromise; two seconds for cases where startup latency matters more than efficiency.
Why this is cheap to serve. Segments are immutable: segment 412 of the 720p rendition never changes. That means an infinite cache lifetime and no invalidation protocol at all — the ideal caching case, and the reason a video platform's serving cost is dominated by a network bill rather than by origin infrastructure.
6.3 Cache hit ratio is the budget
At 125 terabits a second, the hit ratio is not a performance metric, it is the finance model. Going from 92% to 98% is a four-fold reduction in origin egress, because the miss rate went from 8% to 2%.
The levers, in order of how much they move the number.
Immutable URLs with long lifetimes. Nothing about a segment ever changes, so nothing ever needs invalidating. A re-encode produces new URLs and a new manifest rather than replacing bytes at an existing address.
Tiered caching, as in Figure 2. Without a shield, a popular video that is cold at forty edges produces forty identical origin requests. With one, it produces one.
Pre-warming. A release everyone will watch at 9pm is pushed to edges at 5pm. This converts a predictable miss storm into background traffic.
Nothing user-specific in the URL. A token, a session identifier or a query parameter in a segment URL splits one cached object into as many entries as there are users, and the hit ratio collapses to zero for that content. This is the single most common self-inflicted cache disaster in this domain.
Accepting the long tail. Rarely watched videos will miss, and that is fine — by definition they are a small share of total bytes. Trying to keep everything hot is spending money to improve a number that does not matter.
6.4 Codecs and the bandwidth bill
A codec is the compression scheme. Newer ones produce the same visual quality in substantially fewer bits — commonly 30–50% fewer than the older scheme most devices support — at the cost of far more CPU to encode and support only on newer devices.
That produces an unusual-looking trade: to send fewer bytes you store more copies, because you keep the old format as a fallback for older devices. Given that this system moves roughly five hundred times more bytes than it stores, that trade is strongly favourable, and it is worth stating as arithmetic rather than as a preference.
And it only applies where the bytes are. Encoding the long tail in an expensive modern codec spends a great deal of CPU to save bandwidth nobody is using. Encode the popular decile efficiently and leave the rest alone — which requires knowing which decile is popular, which the view pipeline already tells you.
6.5 Private video, without destroying the cache
Access control and edge caching pull against each other: caching wants every user to request the same URL, and access control wants every request to prove who it is.
The resolution is to keep the authorisation in a signature rather than in the path. The player fetches the manifest through your API, which checks permission and returns manifest URLs carrying a short-lived signed token as a separate credential — a cookie scoped to the content path, or a signature the edge validates — while the segment paths themselves stay identical for every viewer. The cache key is the path; the authorisation is the signature; the edge validates the signature on every request even when the object is already cached.
Get this wrong in the obvious way — putting the token in the query string and letting it form part of the cache key — and every viewer creates their own copy of every segment in every edge. Get it wrong in the other direction — caching without validating the signature — and private videos become public.
6.6 View counts, honestly
Never increment a row per view. On a video getting a million views an hour that is 280 writes a second to one row, and no sharding fixes it because it is one video.
The pipeline is: the client batches view events and sends them occasionally, they land on a stream, a consumer aggregates them into per-minute buckets, and the displayed number is the sum of buckets. It lags by seconds and is approximate at the top end, and both of those are fine for a view count.
What matters is saying so. The alternative — an exact, synchronous, real-time count — is a hot-row write problem with no good solution at this scale, and a candidate who promises it has not done the arithmetic.
7. Decision Ledger
| Decision | Alternatives | Why this | What it costs |
|---|---|---|---|
| Chunked parallel transcoding | encode each rendition serially | minutes rather than hours; every task is retryable anywhere | must cut at safe points; rate control must be constrained or quality pulses |
| Interruptible machines for encoding | reserved capacity | the largest compute cost becomes the cheapest capacity class | tasks must be idempotent and the orchestrator must resume |
| Quality chosen by the client | server-side selection | only the client knows its real bandwidth and screen | a more complex player; the server cannot optimise what it does not decide |
| Immutable segment URLs | mutable URLs with invalidation | unlimited cache lifetime and no invalidation protocol at all | a re-encode creates new URLs and a new manifest |
| Tiered caching with a regional shield | edges pull straight from origin | origin load falls by roughly the number of edges per region | one more layer to operate and reason about |
| Per-rendition status | publish all or nothing | watchable at 480p in one minute instead of ten | more states in the model and in the interface |
| Modern codec for popular content only | one codec everywhere | 30–50% fewer bytes where the bytes actually are | more storage, more encode CPU, a fallback copy to maintain |
| Approximate, lagging view counts | exact synchronous counters | a per-view row increment is an unsolvable hot-row problem | the number is a few seconds behind and approximate at the top |
8. Scale and failure
The transcode backlog is the common incident. An upload spike or an encoder regression grows the queue, and videos take hours to publish instead of minutes. Three defences: priority tiers, so a paying creator's upload is not behind a bulk re-encode of the back catalogue; autoscaling on queue age rather than on queue depth; and encoding the lowest rendition first, so that even a backlogged system makes videos watchable quickly.
At 10× the answer is not more origin. It is more edge capacity, a better hit ratio, and a more efficient codec on the popular content. The origin is deliberately small and should stay that way; if origin capacity is your scaling lever, the caching design has already failed.
| What breaks | Blast radius | How you find out | What keeps it running | Recovery |
|---|---|---|---|---|
| Transcode backlog | new videos publish late | age of the oldest queued task, not depth | priority tiers; lowest rendition first | autoscale; drain with age as the exit condition |
| Encoder worker lost | one chunk | task retry count | chunks are idempotent, so retry anywhere | the chunk re-encodes; the workflow resumes |
| Corrupt or unsupported source | one upload | probe-stage failure rate | validate at probe, before 1,200 tasks are created | fail fast with a clear creator-facing message |
Video stuck in processing | one creator, badly | tasks with no terminal state past a deadline | a workflow timeout that forces failed | never leave it ambiguous — a stuck video is worse than a failed one |
| One edge fails | that region's viewers | edge health; origin pull volume | traffic shifts to neighbouring edges | the shield absorbs the miss storm, if it is sized for it |
| Shield undersized for edge loss | origin, suddenly | origin egress spike | size shields for a fraction of edge loss, not steady state | shed long-tail content first |
| Cache key fragmented by a bad URL | hit ratio collapses; the bill does not | hit ratio by content class | no tokens or query strings in segment paths | fix the URL scheme; the cache refills |
| View aggregation stalled | counts freeze; playback unaffected | consumer lag | events buffer durably in the stream | reprocess; counts catch up |
The row worth dwelling on is "stuck in processing". It is the worst failure in this system because it is indistinguishable from slowness: the creator refreshes, sees "processing", and waits — for hours, then gives up. Every workflow needs a deadline after which it declares failure with a reason, because an honest failed with "we could not read this file, here is what to try" is a far better outcome than an optimistic status that never resolves.
Live streaming, honestly. It is a different pipeline and saying so is the correct answer. Encoding happens in real time, so there is no chunk parallelism available — you cannot encode the future. Latency targets are seconds rather than minutes. There is no opportunity to re-encode a chunk that failed, because the moment has passed. And the caching story changes, because the newest segment is by definition not yet in any cache. Reusing this architecture for live is a common interview mistake; the right move is to name the four differences and propose a separate ingest and packaging path.
What the interviewer will push on
"How do you transcode a two-hour video in ten minutes?" They want the chunking insight and then the detail underneath it. Split at the frames that can be decoded independently, dispatch every chunk-and-quality pair as a task — 1,200 of them for a two-hour video at five qualities — and let wall-clock time become one task plus queue wait. Then volunteer the rate-control problem: if each chunk decides its own bitrate independently, quality pulses visibly at every boundary, so rate control has to be constrained or informed by a global analysis pass. That detail is the tell.
"Why does the client pick the quality rather than the server?" Because only the client can measure its own conditions. The server sees one connection and cannot tell congested Wi-Fi from a slow last mile from a handover to a cell tower. Add the two inputs the player uses — achieved throughput and buffer level — because candidates who only say "bandwidth" have not thought about why a player steps down before it runs dry.
"Your cache hit ratio is 92%. Is that good?" No, and the arithmetic is the answer: moving to 98% is a four-fold reduction in origin traffic, because what matters is the miss rate, not the hit rate. Then name what usually causes a low ratio — tokens or query strings in segment URLs splitting one object into millions of entries, missing tiered caching so every edge pulls independently, and lifetimes shorter than necessary on content that is immutable by construction.
"How do you serve private videos from a shared cache?" The tell is separating the cache key from the authorisation. Segment paths stay identical for every viewer so there is one cached object; the credential travels separately as a short-lived signature the edge validates on every request, cached or not. The two ways to get this wrong are worth naming: putting the token in the path destroys the hit ratio, and skipping validation on cached objects makes private videos public.
"A video has been 'processing' for four hours. What do you tell the creator?" They are testing whether you have thought about the worst failure rather than the most common one. The answer is that this should be impossible: every workflow carries a deadline after which it declares failure with a reason. An honest failure with a next step beats an optimistic status that never resolves, because the second one costs the creator hours and gives them nothing to act on.
"Would you reuse this pipeline for live streaming?" No, and listing why is the whole answer: real-time encoding forecloses chunk parallelism, latency targets are seconds not minutes, a failed chunk cannot be redone because the moment has gone, and the newest segment is by definition not cached anywhere. Proposing a separate ingest path shows you understand what the parallelism was buying.
Volunteer this, because nobody asks: in a system where the client makes the quality decision, the client holds the diagnostic data. Server-side metrics can tell you that segments were delivered quickly; only player telemetry can tell you that the buffer emptied, which rendition was playing when it did, and what throughput the player had actually measured. A video platform without rich player telemetry is blind to its own primary quality metric, and no amount of server-side monitoring substitutes for it.
Next: 11.10 — from bytes at rest to objects in motion. A video's location never changes; a driver's changes every four seconds, and the query "who is near me right now" turns out to need a completely different way of indexing space.
Recall
- Egress dominates everything. ~125 Tbps means more than 95% of bytes must come from edge caches, and the design's real job is maximising the hit ratio. 92% → 98% is a four-fold cut in origin traffic.
- What makes segments perfectly cacheable: they are immutable, so unlimited lifetime and no invalidation protocol. Nothing user-specific may appear in a segment URL, or one object becomes millions of cache entries.
- Chunked parallel transcoding: split at independently-decodable frames, encode every chunk × quality pair as its own task (1,200 for a two-hour video at five qualities), concatenate, package. Completion time = one task + queue wait, not video length.
- Because chunks are idempotent, the fleet runs on interruptible machines — the biggest compute cost becomes the cheapest capacity. The catch nobody mentions: rate control must be constrained across chunks, or quality visibly pulses at every boundary.
- Adaptive streaming is client-driven. Time-aligned 2–6 second segments plus a manifest; the player picks the next segment's quality from achieved throughput and buffer level. The server offers a menu and never decides.
- Per-rendition status makes a video watchable at 480p in about a minute while 4K continues. Status is a first-class field:
uploading → processing → partially_ready → ready | failed. - Tiered caching (edge → regional shield → origin) collapses origin load by roughly the number of edges per region, and it is what makes an edge failure survivable.
- View counts are batched, streamed and aggregated — approximate and lagging by construction, because a per-view row increment is an unsolvable hot-row problem.
- Live streaming is a different pipeline: no chunk parallelism, seconds not minutes, no second chance at a failed chunk, and the newest segment is never cached.
Self-test: Why must 95% of bytes come from the edge, and what property makes that possible? Give the latency formula for chunked transcoding and the quality bug it introduces. What two inputs drive the player's choice? How do you serve private video from a shared cache? Why is "stuck in processing" the worst failure state?
Quiz Bank
FoundationalExplain adaptive bitrate streaming end to end.
The encoding side. The video is encoded into several renditions — 240p, 480p, 720p, 1080p, 4K, each at a target bitrate — and every rendition is cut into segments of 2 to 6 seconds. The segments are time-aligned: segment 412 covers the same few seconds of the video in every rendition. That alignment is the whole trick, because it is what lets a player fetch segment 411 at 1080p and segment 412 at 480p and have the picture continue without a seam.
The manifest is a small text file listing the available renditions with their bitrates and resolutions, and where each one's segments live. It is the menu.
The player drives the decision. It downloads a segment, measures the throughput it actually achieved — bytes divided by elapsed time, not an advertised connection speed — and checks how many seconds of video it currently holds buffered. From those two numbers it picks the quality for the next segment: step down when throughput falls or the buffer is draining, step up cautiously when both have been healthy for a while. Startup deliberately begins at a low rendition so the first frame appears fast, then ramps.
Why the client and not the server. Only the client knows its real conditions. The server sees a single connection and cannot distinguish congested home Wi-Fi from a slow last mile from a phone that has just switched cell towers. A server-side choice would be a guess made with worse information, arriving a round trip late.
The trade-off to name. Shorter segments mean faster startup and quicker adaptation, at the cost of slightly worse compression and more requests. Longer segments compress better and adapt sluggishly. Four seconds is the common compromise.
And why this is cheap to serve: segments never change. Immutability means unlimited cache lifetime and no invalidation protocol at all, which is why the byte-serving cost of a video platform lands on the content delivery network rather than on origin infrastructure — and why the origin can be surprisingly small.
InterviewHow would you transcode a two-hour 4K video in under ten minutes?
Not serially. A serial encode of one rendition of a two-hour source runs for hours, and there are five renditions.
Chunk it. Probe the source to find the frames that can be decoded independently — you cannot cut in the middle of a run of frames that are stored as differences from each other — and split into chunks of roughly 10 to 60 seconds. Dispatch every (chunk, rendition) pair as an independent task. A two-hour video at 30-second chunks is 240 chunks × 5 renditions = 1,200 independent tasks. With 1,200 workers available, wall-clock time becomes one task's duration plus queue wait: minutes rather than hours. Then concatenate per rendition — fast, because the pieces are already encoded compatibly — and package into segments and manifests.
Three properties the design depends on.
Determinism and idempotency. The same chunk with the same settings produces the same output, so a task can be retried on any worker with no coordination. That is precisely what allows the fleet to run on interruptible capacity at a fraction of the normal price, which turns the largest compute cost in the entire system into its cheapest capacity class.
Consistent rate control across chunks. This is the detail that separates a working design from one that looks broken. If each chunk's encoder independently decides how many bits to spend, quality visibly pulses at every chunk boundary — the picture subtly improves and degrades every thirty seconds. The fix is either a fixed quality target rather than a fixed bitrate, or a first analysis pass over the whole video whose statistics are distributed to the chunk workers.
Orchestration that survives failure. Twelve hundred tasks with retries, partial completion and a final assembly step is a durable workflow (11.18), not a shell script. It must resume from where it stopped, or a failure near the end costs the entire video.
The product refinement worth adding unprompted: encode the lowest rendition first and publish it. The video becomes watchable within about a minute while the higher renditions continue in the background, which turns a ten-minute wait into a one-minute one. Per-rendition status exists exactly to make that expressible.
InterviewHow do you serve private videos from a shared edge cache without destroying the hit ratio?
The tension. Caching wants every viewer to request the identical URL so there is one cached object. Access control wants every request to prove who is making it. Naively satisfying both puts the credential in the URL, which makes every viewer's URL unique and gives you one cache entry per viewer per segment — a hit ratio of effectively zero on exactly the content you are paying most to serve.
The resolution is to separate the cache key from the authorisation. The path is identical for every viewer: /v/7431/720p/seg-00412.m4s. That path is the cache key, so there is one cached object serving everyone. The credential travels separately — as a short-lived signed cookie scoped to the content path, or as a signature the edge validates — and it is not part of the key.
The flow. The player asks your API for the manifest. The API checks that this viewer may watch this video, and returns the manifest together with a signed credential valid for a few minutes and scoped to this video's path. The player then fetches segments directly from the edge, presenting the credential. The edge validates it on every request, including when the object is already in cache, and serves the bytes.
The two ways to get this wrong, both of which have shipped. Putting the token in the query string so it becomes part of the cache key: the hit ratio collapses and the bill quadruples, with no functional symptom at all, which is why it survives so long. Or caching without validating the signature on hits: the first authorised viewer warms the object and every subsequent request is served from cache without a check, which makes private videos publicly readable to anyone who guesses a path.
One more detail worth volunteering: the credential must expire, and it must be scoped to the content rather than to the viewer, because a viewer-scoped credential is one more thing that varies per user. Short expiry is what limits the damage from a shared link, and the manifest request — which does go through your API — is where revocation can actually be enforced.
StaffYour delivery bill is 70% of infrastructure spend and growing faster than revenue. Find the levers.
Measure first, and refuse to act before you have. Break bytes down by content, by rendition, by geography, by device class, and — most importantly — by cache hit ratio per class. Every lever below is worthless without that breakdown, and in practice one of them is usually responsible for a disproportionate share.
Lever one: encoding efficiency, the biggest structural win. Modern codecs deliver comparable quality at 30–50% fewer bits than the older scheme most devices support. The cost is far more encode CPU and the need to keep the older format as a fallback, so you store more to send fewer. Because this system moves roughly five hundred times more bytes than it stores, that trade is strongly favourable. And it applies only to the content that actually generates bytes — encode the popular decile efficiently and leave the long tail alone.
Lever two: per-title encoding ladders. A fixed bitrate ladder wastes bits on simple content. A static talking head at 1080p needs far fewer bits than an action sequence at the same resolution. Analysing each title and assigning a custom ladder is a well-documented double-digit percentage saving and costs only encoder analysis time.
Lever three: device and viewport policy. Serving 1080p to a five-inch screen where nobody can see the difference is pure waste. Cap by viewport and device class, default conservatively, and let adaptive selection step up only when both the screen and the conditions justify it. This is usually the fastest lever to pull and the most immediately measurable.
Lever four: cache hit ratio. Every miss costs origin egress on top of edge delivery. Audit for the fragmenting mistakes: tokens or query strings in segment URLs, inconsistent request-varying headers, cache lifetimes shorter than necessary on content that is immutable by construction, and missing tiered caching so that every edge independently pulls from origin. Moving from 92% to 98% is a four-fold reduction in origin egress, which is why this number deserves its own dashboard.
Lever five: commercial. Multiple providers with traffic steered by cost and measured quality, committed-volume agreements, and direct interconnection in the heaviest markets. At this spend, negotiation is engineering work.
Lever six: the honest product question. Autoplay, aggressive preloading and background playback generate bytes nobody watches. Measuring "bytes delivered but not viewed" frequently finds a startling number, and reducing it costs nothing but a product decision.
The framing for leadership: delivery cost is (bits per second of quality) × (seconds delivered) × (price per byte), and there are levers on all three. Codec and per-title encoding attack the first. Device policy and waste reduction attack the second. Hit ratio and contracts attack the third. Attacking only the third is the common mistake, because it is the only one that looks like a cost problem rather than an engineering one — and it is also the smallest of the three.
Flashcards
FlashThe cost model
Egress dominates: ~125 Tbps, roughly 500× more bytes moved than stored. So spend storage to save bandwidth. More than 95% of bytes must come from edge caches; 92% → 98% hit ratio is a four-fold cut in origin traffic.
FlashChunked transcoding, and its hidden bug
Split at independently-decodable frames → encode every chunk × rendition in parallel → concatenate → package. Time = one task + queue. Idempotent chunks allow interruptible machines. Constrain rate control, or quality pulses at every chunk boundary.
FlashAdaptive streaming
Time-aligned 2–6 second segments per rendition, plus a manifest. The player picks the next segment's quality from achieved throughput and buffer level. The server offers a menu and never decides, because only the client can measure its own conditions.
FlashWhat makes segments cacheable
They are immutable, so unlimited lifetime and zero invalidation. Nothing user-specific in the path — a token in the URL splits one object into one entry per viewer and silently quadruples the bill.
FlashPer-rendition status
Watchable at 480p in a minute while 4K encodes. Statuses: uploading → processing → partially_ready → ready | failed. And every workflow needs a deadline, because "stuck in processing" is worse than "failed".
FlashLive is a different pipeline
Real-time encoding means no chunk parallelism, seconds rather than minutes of latency budget, no second chance at a failed chunk, and the newest segment is by definition not cached anywhere.
Scenario Drill
DrillRebuffering has risen to 3% against a 0.5% target — but only in one country, only on mobile, and only in the evening. Investigate.
The three-way correlation is the diagnostic engine, because each dimension eliminates whole classes of cause before you look at anything.
Only one country rules out the encoding pipeline, the manifests and the player build, all of which are global. It points at delivery in that geography.
Only mobile points at last-mile bandwidth, or at a device-specific rendition or codec path.
Only evening points at congestion — either the local network operator's peak-hour saturation, or your own edge capacity in that region.
Hypothesis one: edge capacity or interconnection saturated at peak. Check that region's per-location metrics: cache hit ratio, origin pull volume, and delivered throughput during the affected hours. A hit ratio that collapses at peak means the edge is evicting under load and pulling from origin across a congested path, which produces exactly this pattern. Fixes: more edge capacity, a regional shield, or pre-warming popular content before the peak.
Hypothesis two: network-operator congestion that is not yours to fix directly. It shows up as reduced achieved throughput per session across all content and all providers in that market. Fixes: direct interconnection or an embedded cache inside the operator's network, which major platforms do, plus a more conservative quality ladder for that market so the player starts lower and steps up rather than overshooting into a rebuffer.
Hypothesis three, and the one most often missed: the ladder does not go low enough. If the lowest rendition is 480p at 1.5 megabits and typical evening mobile throughput there is 1 megabit, the player has nowhere to step down to and must rebuffer. Adding a 240p rung fixes it outright and is the cheapest possible fix. This is missed because the ladder was designed for a different market's conditions and nobody revisited it.
Hypothesis four: a codec fallback path. If devices in that market disproportionately lack hardware decoding for your efficient codec, they fall back to the older, larger format — consuming more bandwidth exactly where there is least. Check rebuffering segmented by the codec actually served.
The measurement to demand before acting on any of them: player-side telemetry carrying achieved throughput, buffer level at the moment of each rebuffer, the rendition and codec being played, the serving location, and the network operator. Every hypothesis above is confirmable from that single event schema, and none of them is confirmable from server-side metrics alone (10.10).
The general point, which is worth putting in the design document: in a system where the client makes the quality decision, the client holds the diagnostic data. A video platform without rich player telemetry is blind to its own primary quality metric, and no amount of server-side monitoring substitutes for it — the servers will report that every segment was delivered promptly while users watch a spinner.