Design Twitter / News Feed
hardWhen you open the app, you see posts from everyone you follow, newest first. The question is when that list gets built.
Key Concepts
High-level design
Write path: client → API → tweet service (write to durable store) → Kafka FanoutEvent → fanout workers → timeline cache.
Read path: client → API → timeline cache (precomputed) + celebrity outbox pulls (merged) → ranker → response.
Search: tweets → Kafka → Elasticsearch indexer.
Media: tweets with photos/videos → S3 + CDN.
Async pipelines: notifications, analytics, recommendation training.
Components
- Tweet service (write path; sharded by tweet_id).
- Social graph service (followers, following).
- Fanout workers (Kafka consumers).
- Timeline cache (Redis sorted set per user, capped at 800 entries).
- Durable timeline store (Cassandra wide row) for cold/pagination.
- Ranker (TF Serving / Triton).
- Search indexer + Elasticsearch.
- Media service (S3 + CDN, transcoding pipeline).
- Notification service.
- Anti-abuse service (spam, hate, NSFW).
Capacity, worked
Daily active users 200,000,000 Posts per user per day 2 Average followers 200 ----------------------------------------------------------- Posts per day 200M x 2 = 400,000,000 Posts per second 400M / 86,400 = 4,630 At 3x peak = 13,900
Fan-out writes/s 4,630 x 200 = 926,000 At peak 13,900 x 200 = 2,780,000
Timeline cache
Ids held per user 800
Bytes per id 8
200M x 800 x 8 B = 1.28 TB Post storage
Body + metadata per post ~1,000 B
400M/day x 365 = 146B posts/yr
146B x 1 KB = ~146 TB/yrConsequences:
- ~1M timeline writes/s is why the timeline store is a key-value fleet,
not a relational database.
- 1.28 TB of ids fits in memory across a Redis cluster; 146 TB of bodies
does not, which is why timelines hold ids and hydrate separately.
- A 50M-follower account is 50M writes for one post: at 100k writes/s per
node that is 500 node-seconds, and the reason for the hybrid path.
Hybrid fan-out, concretely
Ordinary account (below the threshold)
Post -> queue -> fan-out workers -> append id to each follower's list.
Reader: single range read. Latency dominated by one cache hit. Large account (above the threshold)
Post -> stored once, no fan-out.
Reader: range read of the assembled timeline, plus a fetch of recent
posts from the handful of large accounts they follow, merged in memory. Choosing the threshold
Not a constant. Derive it: fan-out cost rises linearly with followers,
merge cost rises with the number of large accounts a reader follows.
The crossover for most follower distributions lands in the tens of
thousands. State the method, not a magic number. Consequences to mention before you are asked
- A reader following 50 large accounts pays 50 extra reads; cap it by
caching each large account's recent posts once, globally.
- Deletion touches one post row, not millions of lists, because
timelines hold ids and hydration skips missing posts.Failure modes in production
Fan-out lag during peak
Symptom: some followers see a post minutes late; engagement skews to
whoever was written first.
Cause: queue depth grows faster than workers drain it.
Fix: priority by recency of follower activity — write to users who are
online now first, backfill the rest.
Timeline cache cold after a node loss
Symptom: a slice of users get slow feeds for minutes.
Cause: lists were only in memory.
Fix: rebuild on read from the posts store, and treat the cache as
reconstructible rather than authoritative. Unbounded timeline growth
Symptom: memory climbs steadily for heavy users.
Cause: appends without trimming.
Fix: cap at ~800 ids on write; older history comes from the posts store
on demand. Follower-count spike
Symptom: an account crosses the hybrid threshold mid-day and fan-out
workers saturate.
Cause: threshold evaluated only at post time.
Fix: re-evaluate on follower-count change, and make the switch
per-account rather than per-post.