system-design/design-twitter.md

33. Design Twitter / News Feed

The canonical fan-out problem. Tests: timeline construction, push vs pull, hot keys (celebrities), denormalization, caching at scale.

~4 min read·updated 5/29/2026

33. Design Twitter / News Feed

The canonical fan-out problem. Tests: timeline construction, push vs pull, hot keys (celebrities), denormalization, caching at scale.

33.1 Requirements

Functional

  • Post a tweet (text, optional media).
  • Follow / unfollow users.
  • Home timeline: feed of tweets from followees, reverse-chronological (or ranked).
  • Profile timeline: a user's own tweets.
  • Likes, retweets, replies.
  • Notifications.
  • Search.

Non-functional

  • Read-heavy (~100:1 read/write).
  • Low-latency timeline reads (P99 < 200ms).
  • Eventual consistency OK (a few seconds delay for new tweets is fine).
  • Scale: 300M MAU, 150M DAU, 75M tweets/day, 15B timeline reads/day.

33.2 Capacity (recap from chapter 2)

  • Writes: ~3K tweets/sec peak.
  • Reads: ~500K timeline reads/sec peak.
  • Storage: ~30 GB tweets/day (text) + ~4 PB media/year (object storage).
  • Cache: ~100 TB (last 800 tweets per active user).

33.3 High-level architecture

Mobile / Web
   │
[ API gateway ]
   │
[ Tweet service ] ←→ [ Tweet DB (sharded by author) ]
[ Timeline service ] ←→ [ Timeline cache (Redis) ]
[ Follow service ] ←→ [ Graph DB / Postgres ]
[ Media service ] ←→ [ Object storage (S3) + CDN ]
[ Notification service ]
[ Search service ] ←→ [ Elasticsearch ]
[ Fan-out service ] ←→ [ Kafka ]

33.4 Data models

Tweets

tweet_id (snowflake, time-ordered) | author_id | text | media_url | created_at | reply_to | retweet_of

Sharded by author_id.

Follow graph

follower_id | followee_id | created_at

Stored both directions for fast lookup. Sharded by follower_id for "who am I following" and by followee_id for "who follows me."

Timeline (precomputed, per user)

A list of tweet IDs sorted by time. Stored in Redis as a sorted set per user_id.

33.5 Push vs pull (the central trade-off)

Pull (read fan-out)

On timeline read: query each followee's recent tweets, merge, sort.

Pros: cheap writes (just save the tweet); always fresh. Cons: expensive reads — for a user following 1000 people, query 1000 tweet streams.

Bad at scale.

Push (write fan-out)

On tweet post: copy the tweet ID into every follower's timeline cache.

Pros: cheap reads — just read your timeline. Cons: expensive writes for users with many followers; wasted work for inactive users.

For typical user (200 followers): 200 cache writes per tweet. Manageable.

For celebrities (100M followers): 100M writes per tweet. Catastrophic.

Hybrid (Twitter's actual approach)

  • Push for ordinary users: precompute timelines.
  • Pull for celebrities: don't fan out; merge in at read time.
  • Hybrid at read: read your precomputed timeline + query the few celebs you follow + merge.

This is the seminal Twitter design. Discussed in countless talks (Raffi Krikorian's especially).

Determining "celebrity"

Threshold (e.g., > 100K followers). Cache flag per user.

Inactive users

Don't fan out to inactive users (last login > 30 days). When they return, recompute their timeline on demand.

33.6 Timeline cache (Redis)

For each active user, store last 800 tweet IDs in a Redis sorted set:

timeline:user:42 → ZSET of (tweet_id, score=created_at)

Read: ZREVRANGE timeline:user:42 0 19 → 20 most recent tweet IDs → multi-get tweets.

Memory: 150M DAU × 800 IDs × 8 bytes = ~1 TB cache. Spread across 100s of Redis nodes.

33.7 Tweet storage

Append-mostly. Sharded by author_id. Postgres or Cassandra.

For Twitter scale: Manhattan (Twitter's KV) or Cassandra. For interview: shard Postgres or use Cassandra.

Index by author (timeline lookups), by time, by hashtag (via search index).

33.8 Media

Direct upload to S3 / GCS via signed URL. Background workers transcode (multiple resolutions, formats). CDN serves.

Tweet stores media_url; fan-out only references — no media duplication.

33.9 Posting a tweet (write path)

POST /tweets
  → Tweet service validates, generates tweet_id (Snowflake)
  → INSERT into Tweet DB (sharded by author_id)
  → Emit `TweetCreated` event to Kafka
  → 201 Created

Async:

Fan-out service consumes:
  → Look up author's followers (Follow service)
  → If author is celebrity: skip fan-out
  → Else for each active follower: ZADD timeline:user:<follower>
  → Trim timelines to 800 entries (ZREMRANGEBYRANK)
  → Index in Elasticsearch (Search service)
  → Trigger notification fan-out

33.10 Reading the home timeline

GET /home_timeline
  → Read precomputed cache: ZREVRANGE timeline:user:<me> 0 19 → tweet IDs
  → For each followed celeb (small list): query their recent tweets
  → Merge, dedupe, sort by time, take top 20
  → MGET tweets by ID (cache → DB)
  → Render

P99 < 200ms achievable: cached timeline is microseconds; tweet fetch is from cache mostly.

33.11 Ranked feed (post-2017 Twitter)

Reverse chrono replaced by ranked feed:

  • Candidates: precomputed timeline + recent tweets from interest signals.
  • Ranking: ML model scores by engagement probability.
  • Ranked top N served.

Architecturally: candidate generation cheap (cache) + ranker (ML serving) at request time. Ranker is the hot service.

33.12 Search

  • Tweets indexed in Elasticsearch as they're posted.
  • Query: full text + filters (lang, time, has-media).
  • Trending hashtags: Count-Min Sketch in real time (chapter 17) over the firehose.

33.13 Notifications

When you're mentioned, replied to, liked:

  • Fan-out service emits NotificationEvent.
  • Notification service writes to per-user list (Cassandra) and pushes to client (WebSocket / mobile push).

For users with millions of mentions (high-profile accounts), aggregate ("X liked your tweet" → "1.2M people liked your tweet").

33.14 Hot key handling (celebrity)

Celebrities create thundering herds:

  • Their tweet is fetched 100M times in seconds.
  • Their tweet_id → tweet_obj cache key is a hot shard.

Mitigations:

  • Replicate hot tweets to multiple cache nodes; client picks random.
  • Local cache in API servers for top-N tweets.
  • CDN-cache tweet JSON (with short TTL).

33.15 Failure modes

  • Cache cluster outage: fall back to DB; latency spikes; rate limit harder.
  • Fan-out queue backed up: tweets visible on profile but slow to appear in timelines. Acceptable degradation.
  • DB shard down: tweets from that shard unreadable; some users lose access to recent posts. Replicas help.
  • Search index lag: search results slightly stale (seconds-minutes).

33.16 Replicas and geo

  • Tweet DB primary in one region, replicas globally.
  • Cache replicated.
  • Reads served from local region.
  • Writes still cross-region; user accepts a brief "still saving" UX.

For true multi-region writes, you'd need a more complex story (Spanner-class infra).

33.17 What you should sketch in interview

[ Client ]
   │
[ API gateway ]
 ┌─┴───────────────────────────────┐
 │                                 │
[ Tweet svc ] → [ Tweet DB ] → Kafka(TweetCreated) → [ Fan-out svc ] → [ Timeline cache (Redis) ]
 │                                                                    └ [ Search indexer → ES ]
[ Timeline svc ] ← [ Timeline cache ] + (celeb tweets) ← [ Tweet DB ]
[ Follow svc ] ← [ Graph DB ]
[ Media svc ] → [ Object store + CDN ]

33.18 Common pitfalls

  • Pure write fan-out without celebrity special case → catastrophic fan-out for top accounts.
  • No timeline cache → DB melts.
  • Synchronous fan-out → tweet latency is huge.
  • No throttling on fan-out → Kafka-consumer lag explodes during viral moments.
  • Storing media in DB → DB is hosed.

Key takeaways

  • Hybrid push/pull: push to ordinary followers, pull for celebrities.
  • Timeline cache (Redis sorted set) per user, last ~800 tweets.
  • Async fan-out via Kafka decouples post latency from fan-out cost.
  • Hot keys (celebrities) need explicit handling: replicate, CDN, dedup at query time.
  • Scale by sharding the DB by author_id; cache + search + media on independent paths.

// 2 views

main
UTF-8·typescript