HV
home / system-design / instagram

Design Instagram (Photo/Video Sharing)

Similar feed fan-out problem as Twitter, but the core challenge shifts to massive media storage, processing pipelines, and CDN delivery for photos and videos.

1. Requirements

Functional
  • · Upload photos/videos with captions
  • · Follow / unfollow users
  • · View home feed of followed users' posts
  • · Like, comment, and view stories (24h expiry)
  • · Explore/discover page (recommendations)
  • · Direct messaging
Scale Assumptions
  • · 500M daily active users
  • · 100M photos/videos uploaded per day
  • · Average user follows 300 accounts
  • · Read:Write ratio ~100:1 (feed views vs uploads)
  • · Average media size: photo ~2MB, video ~50MB

2. Estimation

Upload QPS: 100M/day / 86,400 ≈ 1,160 write QPS Feed read QPS: 500M DAU x 10 feed refreshes/day = 5,000M/day / 86,400 ≈ 58,000 read QPS Storage (media, per day): Photos: 90M x 2MB = 180 TB/day Videos: 10M x 50MB = 500 TB/day Total ≈ 680 TB/day of RAW media -> stored in S3/blob storage, NOT a database Plus thumbnails/transcoded variants: +20-30% storage overhead Metadata storage (post record ~1KB): 100M x 1KB x 365 ≈ 36 TB/year (this DOES fit in a regular DB/NoSQL store)

3. Core Challenge: Media Pipeline + Feed Fan-out

Media Upload Pipeline

Client uploads directly to blob storage via a pre-signed URL (bypasses app servers for the heavy bytes). An async worker then generates thumbnails, multiple resolutions, and transcodes video into adaptive bitrate formats (HLS/DASH).

✓ App servers never touch raw media bytes — no bottleneck
✓ Processing is async — upload feels instant to the user
Feed Fan-out (same trade-off as Twitter)

Fan-out on write for regular users (push post_id to followers' feed cache). Fan-out on read for celebrities/large accounts (merged in at read time) to avoid a single post triggering millions of writes.

✓ Fast reads for the common case
~ Feed ranking adds complexity — not pure chronological (ML-ranked)
Why feed ranking, not just fan-out, matters here

Unlike a pure chronological timeline, Instagram's feed is ranked by a model considering recency, engagement prediction, and relationship strength. This means the feed service doesn't just merge-sort cached post_ids — it re-ranks a candidate pool (e.g. top 500 chronological posts) through a lightweight scoring pass before returning the top 20.

4. Architecture

Upload flow: Client -> request pre-signed URL from Media Service Client -> PUT raw photo/video directly to S3/Blob Storage (bypasses app servers) Client -> POST /post {media_url, caption} -> Post Service | Post Service writes Post DB record, emits Kafka event "post_created" | Media Processing Workers (consume event): - generate thumbnails (multiple sizes) - transcode video -> adaptive bitrate (HLS/DASH renditions) - run moderation/ML classifiers (NSFW detection, etc.) - write processed URLs back to Post DB, mark post "ready" | Fan-out Service: - regular user -> push post_id to followers' feed cache (Redis sorted set) - celebrity user -> skip push; injected at read time Read flow: GET /feed?userId=123 -> Feed Service reads precomputed feed cache (Redis) -> merges in celebrity posts (fan-out on read) -> Ranking Service re-scores candidate pool (recency + engagement model) -> hydrate post_ids -> full post objects (Post Cache / DB) -> media URLs point to CDN edge (not origin S3) for actual image/video bytes Stories: Stored with TTL (24h) in a separate Story table / Redis with expiry Background job purges expired story media from CDN cache

5. Database Choices & Rationale

DataStorageWhy
Raw photos/videos S3 / Blob Storage + CDN Object storage scales infinitely for large binary blobs; CDN caches at edge for low-latency delivery worldwide.
Post metadata Cassandra / DynamoDB High write volume (1,160 QPS), partition by user_id, time-ordered by post_id (Snowflake-style ID).
Feed cache Redis Sorted Set O(log n) inserts, fast range reads for pagination, scores by timestamp/rank.
Users, follows MySQL/PostgreSQL Relational — needs JOINs (follower graph) and ACID guarantees on account data.
Comments/likes counts Redis counters + async DB write-back High-frequency increments; eventually persisted for durability without blocking the hot path.
Stories Redis / DB with TTL Native expiry (24h) — no need for manual cleanup jobs on the hot path.
Search / hashtag discovery Elasticsearch Full-text + tag search, near real-time indexing of new posts.

6. Bottlenecks & Solutions

⚠️ Celebrity account (100M followers) posts -> fan-out on write would mean 100M cache writes
→

Same as Twitter's celebrity problem: skip fan-out for accounts above a follower threshold; merge their posts into followers' feeds at read time instead.

⚠️ Video transcoding is CPU/GPU intensive and can queue up during upload spikes
→

Dedicated auto-scaling worker pool consuming from a Kafka/SQS queue, decoupled from the upload path. Users see 'processing' status; original upload is never blocked waiting for transcoding.

⚠️ CDN cache miss storm when a viral post suddenly gets millions of views (cold cache at edge)
→

Pre-warm CDN caches for posts crossing an engagement velocity threshold. Use tiered caching (regional cache in front of origin) so only one request per region hits the origin, not one per edge node.

⚠️ Storage costs balloon linearly with media uploads (680 TB/day)
→

Tiered storage: hot/recent media on fast storage + CDN, older/rarely-accessed media moved to cheaper cold storage (e.g. S3 Glacier-class) via lifecycle policies. Compress/re-encode older video at lower bitrate.

7. Key Trade-offs Summary

DecisionOption AOption BChosen & Why
Media upload path Through app server Direct-to-blob (pre-signed URL) Direct-to-blob — app servers stay stateless and lightweight, no byte-streaming load
Feed fan-out Pure push Pure pull Hybrid — push for regular users, pull for celebrities (same as Twitter)
Feed ordering Strict chronological ML-ranked ML-ranked — better engagement, at the cost of predictability/complexity
Media storage tiering Single storage class Hot/cold tiering Tiered — dramatically cuts cost for rarely-accessed old media
Video delivery Single fixed bitrate Adaptive bitrate (HLS/DASH) Adaptive — smooth playback across varying network conditions
Consistency Strong Eventual Eventual for feed/likes; strong only for account/payment-adjacent data

8. Follow-up Questions

Q: How would you implement the 24-hour story expiry efficiently?

A: Store stories with a TTL directly in Redis (native expiry, no cron cleanup needed) or in a DB with a 'expires_at' column plus a lazy-delete-on-read check. CDN edge cache for story media also gets a matching short TTL so it naturally evicts without an explicit purge call.

Q: How do you generate the Explore/discover page recommendations?

A: Offline batch job (or streaming ML pipeline) computes candidate posts per user based on engagement similarity, hashtag/interest embeddings, and collaborative filtering — written to a per-user candidate cache. Read path just fetches the precomputed candidate list and applies light real-time re-ranking (freshness, diversity).

Q: How would you avoid serving stale like/comment counts under high write concurrency?

A: Use Redis atomic INCR for the hot counter (avoids race conditions from read-modify-write), with async batched write-back to the durable DB every few seconds. Reads hit Redis directly; a small window of eventual consistency between Redis and DB is acceptable.

Q: How do you handle duplicate/near-duplicate image uploads (e.g. reposts)?

A: Perceptual hashing (pHash) on upload — generates a hash robust to minor edits (compression, resizing). Compare against an index of existing hashes; flag high-similarity matches for copyright/spam review rather than blocking outright, since some duplication (memes, reposts) is legitimate.