Object storage and media pipelines
In one line
Large files (images, video, documents, backups) belong in object storage such as S3, uploaded directly by clients with pre-signed URLs, processed asynchronously into the sizes and formats you need, and served through a CDN, while the database keeps only metadata and keys.
What it is
Object storage (Amazon S3, Google Cloud Storage, Azure Blob Storage, Cloudflare R2, MinIO self-hosted) stores immutable blobs addressed by a bucket and key, such as s3://media/u/42/p/9f3a.jpg. It has a flat namespace, an HTTP API (PUT, GET, DELETE, multipart upload), effectively unlimited capacity, and very high durability: S3 is designed for 99.999999999% (eleven nines) durability by storing data redundantly across multiple availability zones.
It is cheap and scales without limit, but it is not a file system or a database: you cannot update a byte range in place (you rewrite the object), listing is slow for huge prefixes, and per-request latency is tens of milliseconds. Since December 2020, S3 gives strong read-after-write consistency for PUTs and LISTs; older material saying it is eventually consistent is out of date.
A media pipeline is everything around the blob: the client uploads the original straight to object storage, an event triggers workers that validate, scan, transcode and generate thumbnails, results are written back to object storage, the database records metadata and status, and a CDN serves the outputs to users close to them.
Why interviewers ask
Instagram, YouTube, WhatsApp, Google Drive and Dropbox designs all hinge on this. Interviewers check that you never store blobs in the database or stream gigabytes through your API servers, that you use pre-signed URLs and multipart uploads, that processing is asynchronous and idempotent, and that you put a CDN in front. Storage cost and egress cost are also common follow-ups.
How it works
- 1Client asks the API for an upload: POST /uploads with file size and type. The API checks auth, quota and type, creates a metadata row (status UPLOADING) and returns a pre-signed PUT URL valid for, say, 15 minutes.
- 2Client uploads directly to object storage. Files over about 100 MB use multipart upload: split into parts (for example 8 to 64 MB), upload parts in parallel, retry only failed parts, then complete. On weak mobile networks, smaller parts and resumable uploads matter more than raw speed.
- 3Object storage emits an event (S3 Event Notification to SQS, SNS or EventBridge) when the object is created. A worker picks it up; the queue absorbs bursts.
- 4Processing workers validate the file (real type by magic bytes, size limits), virus scan documents, strip EXIF location data from photos, and produce derivatives: image thumbnails in several widths and WebP or AVIF; video transcoded with FFmpeg into an HLS or DASH ladder (for example 240p to 1080p) in 2 to 6 second segments.
- 5Writes are idempotent: output keys are derived from the input key and preset (p/9f3a/720.webp), so a retried job overwrites the same objects rather than creating duplicates.
- 6The worker updates the metadata row (status READY, dimensions, duration, keys of derivatives) and emits an event so feeds and notifications can show the item.
- 7Serving: clients fetch through a CDN (CloudFront, Akamai, Cloudflare). Objects are immutable with content-hash or versioned keys, so they can be cached for a year. Private content uses signed CDN URLs or cookies with short expiry.
- 8Lifecycle and cost: move originals to infrequent-access or archive tiers (S3 Standard-IA, Glacier) after 30 to 90 days, delete abandoned multipart uploads, and watch egress, which is often the biggest bill. A CDN in front cuts egress from the origin.
Worked example · Photo upload for a social app with 20M daily uploads
- Volume: 20M photos a day / 86,400 = about 230 uploads per second, peak about 1,000 per second on festival mornings.
- Original size about 3 MB: 20M x 3 MB = 60 TB per day of originals. Derivatives (4 widths in WebP) add about 0.6 MB per photo = 12 TB per day.
- Upload: app calls POST /uploads, gets a pre-signed URL for key orig/2026/10/25/9f3a.jpg, PUTs 3 MB directly to S3. API servers handle only a 1 KB request.
- S3 event -> SQS -> image workers. Each image takes about 300 ms of CPU to resize to 4 widths, so 1,000 per second at peak needs about 300 CPU cores; autoscale workers on queue depth.
- Metadata row in Postgres: photo_id, user_id, original key, derivative keys, width, height, status READY. About 500 bytes x 20M = 10 GB per day.
- Feeds request the 640-px WebP through the CDN. With a 95% CDN hit rate, the origin serves only 5% of reads.
- Cost lever: after 60 days, originals move to an infrequent-access tier, since users almost always view derivatives, cutting storage cost for originals by roughly 40 to 50%.
Picture it
Object storage is a huge, cheap warehouse with numbered shelves: you hand in a sealed box and get a receipt number, and you can fetch the box later, but you cannot rearrange what is inside without bringing a whole new box. The CDN is a network of small local shops stocking copies of the most popular boxes, so most customers never travel to the warehouse.
Reference code
# API: authorise and hand back a pre-signed URL; the bytes never pass through the API
import boto3, uuid
s3 = boto3.client("s3")
def create_upload(user_id: int, content_type: str, size: int) -> dict:
if content_type not in {"image/jpeg", "image/png"} or size > 20 * 1024 * 1024:
raise ValueError("unsupported file")
key = f"orig/{user_id}/{uuid.uuid4().hex}"
db.insert_photo(user_id=user_id, key=key, status="UPLOADING")
url = s3.generate_presigned_url(
"put_object",
Params={"Bucket": "media", "Key": key, "ContentType": content_type},
ExpiresIn=900, # 15 minutes
)
return {"upload_url": url, "key": key}Key patterns
Pre-signed direct upload
When: Any user-generated file
How: API authorises and returns a short-lived signed URL; client PUTs directly to the bucket; API never touches the bytes.
Event-driven processing
When: Files need validation, scanning, thumbnails or transcoding
How: Object-created event -> queue -> idempotent workers -> outputs to deterministic keys -> metadata status update.
Immutable keys + long CDN caching
When: Serving media
How: Never overwrite an object in place; use a new key (content hash or version) so CDN caches can keep objects for a year without invalidation.
Tiered storage
When: Data that is hot at first and rarely read later
How: Lifecycle rules move objects to cheaper tiers after N days; keep derivatives hot and archive originals.
Real-world use
Amazon S3
The reference object store, holding hundreds of trillions of objects. Strongly consistent since December 2020, with storage classes from Standard to Glacier Deep Archive and lifecycle rules to move data between them automatically.
Netflix encoding pipeline
Netflix encodes each title into many bitrate and resolution versions, choosing encoding settings per title and even per shot to save bandwidth, stores them in S3, and serves them from its Open Connect appliances inside ISP networks.
Facebook Haystack and f4
Facebook built Haystack to store billions of photos with one disk seek per read by packing many photos into large files, and later f4 for 'warm' older photos using erasure coding to cut storage overhead.
Dropbox Magic Pocket
Dropbox moved most user files from S3 to its own exabyte-scale storage system, Magic Pocket, to control cost at its scale. It shows that at very large scale, owning storage can beat cloud object storage on price.
JioHotstar live cricket
Live streams are packaged into short HLS segments with an adaptive bitrate ladder so viewers on weak mobile networks can drop to low resolutions; segments are served through multiple CDNs to handle tens of millions of concurrent viewers during major matches.
In a project
An ed-tech app lets teachers upload lecture videos. Uploads go through the API servers, which buffer the whole file and then copy it to S3. Large uploads from small towns fail after 20 minutes, API pods run out of memory, and students on 3G connections see constant buffering because only the 1080p original exists.
- Switch to pre-signed multipart uploads directly to S3 with 8 MB parts and resume support, so a dropped connection only re-sends the current part.
- Trigger processing from S3 events into a queue; transcode with FFmpeg workers (or a managed service such as AWS Elemental MediaConvert) into an HLS ladder: 240p, 360p, 480p, 720p, 1080p with 4-second segments.
- Store the outputs under lectures/{id}/hls/ and the metadata (status, duration, renditions) in Postgres; show 'Processing' to the teacher until status is READY.
- Serve through a CDN with signed URLs that expire after a few hours, because the content is for paying students only.
- Add lifecycle rules: delete incomplete multipart uploads after 7 days and move originals to an archive tier after 30 days.
- Make the player start at 360p and adapt upward, which suits users on patchy mobile data.
Impact: Upload failures fall sharply because retries resend only a single part, API memory spikes disappear since no video bytes pass through the API, and buffering complaints from low-bandwidth students drop because the player can switch to 240p or 360p.
Trade-offs
Use it when
- Images, video, audio, documents, backups, logs, ML datasets and model files
- Data written once and read many times, or rarely read but kept for years
- Static assets for web and mobile apps behind a CDN
- Data lakes queried by Spark, Trino or Athena using Parquet files
Avoid it when
- Small records that are updated often or need queries; use a database
- Low-latency random reads and writes inside a file; use block storage or a database
- Treating key prefixes as folders with frequent renames; a rename is a copy plus delete
- Storing blobs inside Postgres or MySQL rows, which bloats backups, replication and the buffer cache
Common mistakes
- Streaming uploads through API servers, which wastes bandwidth and memory and ties up connections for minutes.
- Storing images or videos as BLOBs in the relational database.
- Doing thumbnails or transcoding synchronously inside the upload request.
- Overwriting objects at the same key and then fighting stale CDN caches; use new keys instead.
- Trusting the client's file type and skipping validation, scanning and EXIF stripping.
- Ignoring egress and storage-class costs; at 60 TB a day, storage tiering and CDN hit rate decide the bill.
- Leaving buckets public when content should be private; use signed URLs and block public access.
Interview questions
Quick revision
- Blobs in object storage; metadata and keys in the database.
- S3: eleven nines durability, strong read-after-write consistency since Dec 2020.
- Pre-signed URL: client uploads directly; API never touches bytes.
- Multipart upload for files over about 100 MB: parallel, resumable parts.
- Object-created event -> queue -> idempotent workers -> derivatives.
- Video: transcode to an HLS/DASH ladder (240p-1080p), 2-6 s segments.
- Immutable, versioned keys + CDN cache for a year.
- Lifecycle rules: Standard -> Infrequent Access -> Archive; delete incomplete uploads.
- Egress and storage tier often dominate cost.
Check yourself
1. Why do clients upload directly to object storage using a pre-signed URL?
2. What is the main benefit of multipart upload for a 2 GB file on a flaky mobile network?
3. Users still see an old profile picture after changing it. What is the best fix?
4. Where should video transcoding happen?
See it in a full design
These walkthroughs use this lesson.
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.