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
- · 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
- · 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
3. Core Challenge: Media Pipeline + Feed Fan-out
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).
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.
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
5. Database Choices & Rationale
| Data | Storage | Why |
|---|---|---|
| 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
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.
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.
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.
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
| Decision | Option A | Option B | Chosen & 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
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.
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).
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.
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.