SD System Design In Depth
0%
lesson system-designmediauploads

Direct-to-object-storage upload

Module 5.2: Media Processing & CDN Distribution Updated Sep 18, 2026
📄 Cheat Sheet
Object Storage in System Design Interviews w/ Ex-Meta Staff Engineer
Hello Interview Object Storage in System Design Interviews w/ Ex-Meta Staff Engineer Watch on YouTube ↗

Kept: the most on-topic system-design explanation of presigned URLs, direct client uploads, multipart and event-driven post-processing.

0:00 / --:--

Why Avoid Routing Uploads Through Application Servers?

In naive architectures, users upload photos or video files directly to an application server, which buffers the file in memory or temporary disk before uploading it to object storage (like Amazon S3 or Google Cloud Storage). This anti-pattern exhausts server network bandwidth, ties up worker threads during slow mobile uploads, and concentrates network, connection and memory pressure on the API tier.

The Pre-Signed URL Architecture

flowchart TD Client["Client Mobile / Web"] -->|1. Request Upload Ticket| AppAPI["Application Server API"] AppAPI -->|"2. Sign URL locally with SigV4"| Signer["Local signing with IAM credentials: no call to S3"] AppAPI -->|3. Return Signed PUT URL + Object Key| Client Client -->|4. Direct Binary PUT Upload| S3Bucket[("Amazon S3 / GCS Bucket")] S3Bucket -->|5. S3 Event Notification| SQS["SQS / EventBridge Queue"] SQS --> Worker["Async Processing Worker: Thumbnails / Virus Scan"] Worker --> DB[("Metadata Database")]

How Pre-Signed URLs Work

The application server uses AWS IAM credentials to generate a cryptographically signed HMAC-SHA256 signature containing:

  • The exact S3 object key (e.g., uploads/users/42/avatar.jpg).
  • Allowed HTTP method (strictly PUT).
  • Expiration timestamp (e.g., 15 minutes).
  • Optional signed headers such as an exact Content-Type. A pre-signed S3 PUT does not provide a size range (signing Content-Length pins one exact length, nothing more); use a pre-signed POST with a content-length-range condition where appropriate, and verify the stored object's size before publishing it.

The client uses this URL to stream the binary payload directly to S3. The application server never touches the file bytes, scaling effortlessly to hundreds of thousands of concurrent uploads.

Worked example: why proxying bytes hurts

With 50,000 concurrent mobile uploads averaging 5 Mbit/s, proxying through your API means about 250 Gbit/s of ingress and egress on application servers. A 20 MB photo on a 2 Mbit/s uplink takes about 80 seconds, holding a worker, a socket and buffer memory the whole time. With direct upload, the API handles one small signing request (a few milliseconds of CPU) and one completion callback per file.

Upload flow in detail

  1. Client calls POST /uploads with intended type and size. The server checks quota and auth, creates a metadata row with state pending, and picks key pending/u42/9f1c...jpg.
  2. The server signs a presigned POST policy with conditions: exact key, content-length-range from 1 byte to 25 MB, Content-Type starting with image/, expiry 15 minutes. Signing is a local HMAC computation with the server's credentials; no call to S3 is needed.
  3. The client uploads directly to the bucket.
  4. An object-created event goes to a queue; a worker checks the real size and magic bytes, scans it, strips EXIF location data, generates thumbnails, and copies it to its final key.
  5. The worker marks the metadata row ready; only now can the object be referenced by posts or profiles.

Trade-offs

MethodSize enforcementResumableServer loadBest for
Proxy through app serverFull controlOnly if you build itHigh: all bytesTiny files, strict inline validation
Presigned PUTExact length only if Content-Length is a signed header; otherwise verify afterNoMinimalSimple uploads up to a few hundred MB
Presigned POST with policycontent-length-range condition enforced by S3NoMinimalBrowser form uploads with size limits
Multipart with presigned partsPer part, verify total afterYes, per partMinimal; one signing call per partLarge files and flaky networks

Equivalents in other clouds

  • Google Cloud Storage offers signed URLs and a resumable upload protocol.
  • Azure Blob Storage uses shared access signature (SAS) tokens scoped to a blob, permissions and expiry.
  • If the URL is created with temporary credentials (for example an IAM role session), it stops working when those credentials expire, even if its own expiry is later.
Check yourself Question 1 of 3
What a presigned PUT binds recall
From: How Pre-Signed URLs Work

Your API issues presigned S3 PUT URLs for avatar uploads and wants to allow any file between 1 byte and 5 MB. Which statement from the lesson is correct?

Key Takeaways

Never stream large file uploads through application servers; upload directly to S3 via pre-signed URLs.
Pre-signed URLs bind the permitted operation, object key, expiry, and any explicitly signed headers; enforce an upload size range with an appropriate POST policy or post-upload validation.
Use S3 Event Notifications (via SQS/SNS) to trigger asynchronous downstream thumbnailing and validation.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue