AlgoPlusAlgoPlus
Learn/System Design
Lesson

Design Instagram (Photo Sharing)

Upload and serve billions of photos with an object store + CDN, sharded Postgres for metadata, and time-sortable IDs generated inside the database.

11 min read Watch it move Build it

Instagram is upload-a-photo, then serve it to followers' feeds — billions of times. The core design tension is that a photo has two very different parts: the bytes (large, immutable, served worldwide) and the metadata (small, relational: caption, author, likes). They want completely different homes.

Estimation

Take 100M photos/day uploaded, averaging ~200 KB–2 MB each. Even at 500 KB that is ~50 TB/day of new media — far too much to sit in a relational database, and it must be served with low latency to users on every continent. Metadata, by contrast, is tiny: a few hundred bytes per photo.

Bytes go to an object store, delivered by a CDN

  1. 1The photo file is uploaded to an object store — Instagram uses Amazon S3 (cheap, durable, built for immutable blobs).
  2. 2It is served through a CDN — Amazon CloudFront: a network of edge servers that cache each photo close to the viewer, so the bytes travel a short distance, not across the planet.
  3. 3The database keeps only the metadata: caption, author, and the photo's URL pointing at S3/CDN.
Not Haystack
A common mix-up: Instagram uses S3 + CloudFront, *not* Facebook's Haystack photo store. Even though Facebook owns Instagram, Instagram's documented stack is the AWS object-store + CDN pattern.

Metadata: sharded Postgres with in-database IDs

Core data lives in sharded PostgreSQL (Instagram chose relational over NoSQL for it). There are several thousand logical shards — each a Postgres schema — mapped onto far fewer physical machines, and a row's shard is picked by `id % N`. The clever part is the 64-bit ID, generated *inside* Postgres with no separate ID service:

64-bit ID layout:
  [ 41 bits ]  milliseconds since a custom epoch   -> IDs sort by time
  [ 13 bits ]  shard id (which logical shard)        -> the ID encodes its own home
  [ 10 bits ]  per-shard sequence (1024 ids / ms)    -> uniqueness within the ms

Because the shard is baked into the ID, you scale by MOVING logical shards
between machines, never by re-bucketing individual rows.
Why bake the shard into the ID
A time-sortable ID means feeds sort by recency for free. Encoding the shard means any service can route a lookup from the ID alone — no coordination service, no global counter to become a bottleneck. This ID scheme is Instagram's signature idea.

The feed: fan-out on write vs read

To assemble a home feed you can either fan-out on write (when you post, push the post's ID into each follower's precomputed feed list) or fan-out on read (at read time, query recent posts from everyone you follow and merge). Fan-out-on-write makes reads cheap but writes expensive; fan-out-on-read is the reverse. A follows table (follower_id, followee_id) drives either path. Slow follow-on work — cross-posting, push notifications — is pushed to a task queue (Instagram used Gearman with ~200 workers) so the upload returns fast.

Stay honest about the feed algorithm
Instagram has published *where* the feed and Direct inbox live — first Redis, then Cassandra (and a C++ engine, Rocksandra, to cut tail latency) — but not the exact feed-assembly algorithm. Teach the general fan-out trade-off; don't assert an invented ranking as Instagram's.
OperationTimeSpace
Serve photo bytes · CDN edge cache hitO(1)
Metadata read · shard = id % N, then PK lookupO(1)
Fan-out on write · cost paid at post timeO(followers)O(followers)
Fan-out on read · cost paid at read timeO(followees)O(1)
Check yourself
Why store photo bytes in S3 + CloudFront instead of in the PostgreSQL database?