System Design Article

Blob Storage, Object Stores & CDNs

Difficulty: Medium

Databases are wrong for storing large unstructured files - photos, videos, backups, logs. Object stores like S3 give you cheap, durable, infinitely scalable storage for blobs, while CDNs cache that content at edges close to users. This lesson covers the object-storage data model, multi-part upload, storage classes, presigned URLs, and how a CDN turns a globally slow origin into a globally fast experience. By the end you can design the media layer for any social, video, or e-commerce system.

System Design
/

Blob Storage, Object Stores & CDNs

Blob Storage, Object Stores & CDNs

Databases are wrong for storing large unstructured files - photos, videos, backups, logs. Object stores like S3 give you cheap, durable, infinitely scalable storage for blobs, while CDNs cache that content at edges close to users. This lesson covers the object-storage data model, multi-part upload, storage classes, presigned URLs, and how a CDN turns a globally slow origin into a globally fast experience. By the end you can design the media layer for any social, video, or e-commerce system.

System Design
Medium
blob-storage
object-storage
cdn
content-delivery-network
s3
caching
system-design
intermediate

698 views

20

Block, File, and Object Storage

Before we talk about object stores, anchor the three storage paradigms in mind.

TypeInterfaceExampleBest For
BlockRead/write fixed-size blocks at offsetsEBS, local SSDDatabases, OS volumes
FileHierarchical paths, POSIX semanticsEFS, NFS, SMBShared application file systems
ObjectHTTP API, flat namespace, immutable objectsS3, GCS, Azure BlobLarge unstructured blobs at internet scale

Object storage wins for blobs because the API is dead simple (HTTP), there is no coordination cost between clients, and the underlying system can scale storage and bandwidth independently across thousands of nodes.

The Object-Storage Data Model

An object store has three concepts:

  • Bucket: a globally named namespace (my-app-uploads).
  • Key: an arbitrary string that identifies an object within a bucket (avatars/users/42/profile.webp). Keys look like file paths but the namespace is flat.
  • Object: the binary body plus user-defined metadata (key/value strings). Once written, objects are immutable - to 'update' an object, you write a new version (or overwrite the same key, breaking caches).
Text
---------- S3 object example ----------
bucket   = my-app-uploads
key      = videos/abc/HD/segment_0001.ts
body     = <2.4 MB binary>
meta     = { content-type: video/mp2t,
             cache-control: public, max-age=31536000 }
version  = 'k8Hp2...' (only if versioning is enabled)

Durability and Availability

Object stores like S3 advertise eleven nines (99.999999999%) of durability. They achieve this by replicating each object across multiple devices and availability zones - typically using erasure coding (a fancier form of RAID) so objects survive multiple disk and AZ failures without storing 3x raw copies. Availability is lower (99.99% for S3 Standard) - the data is there, but the API may briefly be unreachable.

The consequence: never store the only copy of important data only in your database. Heavy assets (photos, videos, exports) belong in an object store, with the database holding metadata and pointers.

Multi-Part Upload

Large uploads cannot be done in a single HTTP request. The standard pattern:

  1. Client tells the server 'I want to upload a 5 GB video'.
  2. Server initiates a multi-part upload (CreateMultipartUpload) and returns an upload ID.
  3. Client splits the file into N parts (5 MB to 5 GB each), uploads them in parallel directly to the object store using presigned URLs generated by the server.
  4. Client tells the server 'I'm done, here are the part ETags'.
  5. Server calls CompleteMultipartUpload to atomically assemble the object.

Why this matters:

  • Resumable: if part 7 fails, only retry part 7.
  • Parallel: saturate the network link with many concurrent part uploads.
  • No proxying: the bytes never go through your server, which would otherwise be an instant bandwidth bottleneck.

Presigned URLs

A presigned URL is a normal https://... URL that contains a signed query parameter authorizing a specific operation (PUT, GET) on a specific object for a limited time. The server generates it; the client uses it directly against the object store.

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: 'us-east-1' });

async function getUploadUrl(userId, filename) {
    const cmd = new PutObjectCommand({
        Bucket: 'my-app-uploads',
        Key: `avatars/users/${userId}/${filename}`,
        ContentType: 'image/webp'
    });
    return await getSignedUrl(s3, cmd, { expiresIn: 900 }); // 15 minutes
}

The client then uploads with a plain PUT to the returned URL. The server sees no bytes; it only orchestrates.

Storage Classes and Lifecycle Rules

S3 (and equivalent services) offer multiple storage classes that trade access latency for cost.

ClassLatencyCost (relative)Use Case
S3 Standardms1xHot user-facing data
Intelligent-Tieringms0.4x-1x (auto)Unknown access patterns
Standard-IAms0.5xBackups, infrequent reads
One Zone-IAms0.4xRecreatable content (thumbnails)
Glacier Instant Retrievalms0.2xArchives accessed once a quarter
Glacier Flexible Retrievalminutes-hours0.05xAudit logs, regulatory backups
Glacier Deep Archive12+ hours0.02xCompliance archives

Lifecycle policies automatically move objects between classes based on age. A typical rule for user uploads:

Yaml
# S3 lifecycle config
rules:
  - id: archive-old-uploads
    filter: { prefix: uploads/ }
    transitions:
      - days: 30
        storageClass: STANDARD_IA
      - days: 180
        storageClass: GLACIER
    expiration: { days: 2555 } # 7 years for compliance

This single config can cut a media bucket's monthly cost by 70-80% over time.

What a CDN Actually Does

A Content Delivery Network is a global fleet of caching proxies. When a user requests https://cdn.example.com/avatar.webp:

  1. DNS routes them to the nearest edge location (Anycast or geo-DNS).
  2. The edge checks its cache for the object using a cache key (typically the URL plus selected request headers).
  3. Cache hit -> serve from edge. Latency is the round trip to the nearest edge - usually 5-30 ms.
  4. Cache miss -> fetch from the origin (your S3 bucket or app server), serve the response, and store it in the edge cache for next time.
Text
---------- Pull-through CDN ----------
           edge cache (Tokyo)
                  ^   miss
  user (Tokyo) -->|--> origin (us-east-1, S3)
                  v   hit (next request)
           edge cache (Tokyo) -- 30 ms RTT

The wins are huge:

  • Latency: serving from a city near the user beats one trans-Pacific round trip.
  • Bandwidth offload: the origin sees a tiny fraction of total traffic.
  • DDoS absorption: the edge fleet has more capacity than any one origin.
  • Encryption termination: TLS is handled at the edge.

Pull vs Push CDNs

  • Pull: the CDN fetches from origin on first miss. Default for S3 + CloudFront, Cloudflare, Fastly. Almost always the right choice.
  • Push: you upload assets to the CDN explicitly. Used for time-sensitive launches when you want to pre-warm caches.

Cache Control: TTL, Cache-Control, ETag

The origin tells the CDN (and the browser) how to cache via HTTP response headers:

Text
---------- Common cache headers ----------
Cache-Control: public, max-age=31536000, immutable   -- versioned static assets
Cache-Control: public, max-age=300                   -- semi-dynamic content
Cache-Control: private, no-store                     -- per-user data
ETag: "5d0a92f1-2ab3"                                  -- conditional revalidation

Best-practice patterns:

  • For user-uploaded media: include the asset's content hash or version in the URL (avatar.v17.webp) and set a long max-age. New versions get new URLs - no cache invalidation needed.
  • For HTML/API responses: short TTLs (60s-5min) so changes propagate quickly.
  • For per-user pages: private, no-store to keep the CDN out of it entirely.

Cache Invalidation

When you must invalidate (a leaked image, a corrected video):

  1. Versioned URL (preferred): change the URL, the old one is just orphaned in the cache.
  2. Purge by URL: instruct the CDN to evict a specific object. Slow (seconds to minutes globally), expensive, often rate-limited.
  3. Purge by tag/surrogate key: tag related objects when storing them, then purge by tag (Fastly, Cloudflare). Powerful and often free.

The order is important: prefer versioned URLs. Active purges are an emergency tool, not a default workflow.

End-to-End Media Pipeline

A typical flow for a photo-sharing app:

Text
---------- Upload + delivery ----------
1. Client requests presigned upload URL from API
2. Client PUTs original to s3://uploads/raw/<id>.jpg
3. Upload triggers a Lambda / queue job:
     - generate thumbnails (multiple sizes)
     - convert to WebP / AVIF
     - extract metadata (EXIF, dominant color)
     - write derived assets to s3://uploads/derived/<id>/<size>.webp
4. Database row stores key paths and metadata
5. CDN (CloudFront / Cloudflare) sits in front of s3://uploads/derived/
6. Front-end uses URL like https://cdn.example.com/u/<id>/sm.webp
7. Browser hits nearest edge, edge serves cached object

For video, the same idea but with HLS/DASH segments: the encoder produces 5-10 second .ts chunks, each becomes its own object, the player requests them in sequence, and the CDN happily caches every single segment.

Signed URLs for Private Content

Not all media is public. For paid or private content, the CDN can validate signed cookies or signed URLs at the edge:

  • The application server signs a URL with an expiry and an optional IP/policy.
  • The CDN verifies the signature before serving.
  • Without a valid signature, the request is rejected at the edge - no origin call.

Netflix uses this pattern for video streams; Stripe uses it for invoice PDFs. The edge becomes the authorization boundary, not your origin.

Real-World Examples

How real systems implement this in production

Netflix Open Connect

Netflix runs its own CDN and embeds caching appliances directly inside ISP networks. Popular movies are pre-positioned based on regional forecasts, so the bytes never traverse the public internet during peak viewing.

Trade-off: Building a custom CDN is enormously expensive in capex and engineering, justified only by Netflix's traffic share. Smaller companies use public CDNs (CloudFront, Cloudflare, Fastly) and accept slightly higher per-GB costs.

Instagram media pipeline

Instagram stores raw uploads in object storage, fans out async pipelines that produce thumbnails, transcodes, and ML features, and serves through a CDN tightly integrated with feed ranking.

Trade-off: Pre-generating many derivatives multiplies storage cost but is essential for fast feed rendering. The bet is that storage is cheap and CPU-on-demand at view time would not meet latency targets.

Cloudflare R2

Cloudflare R2 is S3-compatible object storage with no egress fees when delivered through Cloudflare's CDN. Many media-heavy SaaS products migrate user-content buckets to R2 to escape AWS egress charges.

Trade-off: R2 lacks some advanced S3 features (full IAM integration, certain analytics tools) and locks you into Cloudflare's ecosystem. Best as a content delivery store paired with S3 for operational data.

Quick Interview Phrases

Key terms to use in your answer

presigned URL
multi-part upload
edge caching
origin shield
cache-control max-age
lifecycle policy

Common Interview Questions

Questions you might be asked about this topic

Browser asks the API for a multi-part upload session. API initiates the upload with the object store and returns presigned PUT URLs for each part. Browser uploads parts in parallel directly to S3, retrying any that fail. On completion, browser calls API again to finalize. The API then enqueues a transcoder job that produces HLS chunks at multiple bitrates, writes them back to S3, and updates the database with the playback manifest URL. CDN serves the HLS chunks with long TTLs.

Interview Tips

How to discuss this topic effectively

1

Default to 'object store + CDN' for any blob: photos, videos, PDFs, exports. State it in one sentence and move on. Trying to store these in a relational database is a junior-level mistake.

2

Always mention presigned URLs for uploads. 'My server never proxies the bytes' shows you understand bandwidth economics.

3

Talk about cache TTL strategy explicitly: long TTL on versioned URLs, short TTL on dynamic content, no-store on private. Interviewers love precise cache headers.

4

Bring up storage classes when cost is mentioned. 'I'd lifecycle to Standard-IA after 30 days' demonstrates production awareness.

5

For private content, mention signed URLs at the edge. Treating the CDN as the authorization boundary - not just the origin - is a senior pattern.

Common Mistakes

Pitfalls to avoid in interviews

Storing user-uploaded files in a database BLOB/binary column

Databases are not built for multi-MB blobs - they bloat backups, kill replication speed, and push you toward expensive disk tiers. Store the bytes in object storage and put only the key and metadata in the database.

Proxying uploads through your application server

Your server's network bandwidth becomes the bottleneck. Use presigned URLs so the client uploads directly to the object store. Your server only orchestrates.

Relying on cache purge as the default invalidation strategy

Purges are slow, rate-limited, and asymmetric across edges. Use versioned URLs (asset.v17.webp) so updates produce new URLs and you never have to purge except for emergencies.

Forgetting that S3 buckets are NOT directories

S3 keys are flat strings; slashes are convention, not structure. Listing 'objects in a folder' is just a prefix scan. Designing keys with predictable prefixes (uploads/users/<id>/) makes lifecycle rules and access policies much cleaner.

Leaving everything in S3 Standard forever

Most uploaded content is read heavily for the first week, then almost never. Lifecycle to Standard-IA at 30 days and Glacier at 180 days. This is one of the highest-ROI cost optimizations in any media-heavy product.