Design a News Feed
Design a News Feed
A news feed is the core product surface of platforms like Twitter, Facebook, Instagram, and LinkedIn. When you open the app and see a ranked stream of posts from people you follow, that stream is your feed. At scale — hundreds of millions of users each refreshing every few minutes — delivering that feed cheaply, quickly, and correctly is one of the hardest distributed-systems problems in industry.
This lesson focuses on the single most important architectural decision in feed design: fan-out on write vs. fan-out on read — and how real platforms combine both.
What the System Must Do
Before picking an architecture, frame the requirements:
- Post: A user creates a post. Everyone who follows them should eventually see it in their feed.
- Read feed: A user opens the app and gets the ~20 most-recent posts from people they follow, ranked by time (or engagement).
- Scale assumptions: 300 million daily active users, ~5 million posts per minute, average follow count ~200, some celebrities with 50–100 million followers.
- Latency target: Feed read < 200 ms at p99. Post write can be eventually consistent (a few seconds' delay is acceptable).
Option 1 — Fan-Out on Write (Push Model)
When a user posts, the system immediately pushes that post into the feed cache of every follower. Each user has a personal feed mailbox (e.g. a Redis sorted set keyed by user ID). Reading the feed is trivial: fetch the top N entries from your mailbox.
- Write path: Post → look up all followers → write post ID into each follower's feed cache. This is the "fan-out" step.
- Read path: Fetch the pre-built feed list → hydrate post details → return to client. O(1) per user.
Pros: reads are extremely fast (single cache lookup); no complex join at read time; works well for users with modest follower counts.
Cons: writing one post by a celebrity with 50 million followers means 50 million cache writes — a "hot key" storm. This is called the celebrity problem. Fan-out jobs must be queued asynchronously, introducing write latency. Storage cost is high: every post is duplicated once per follower.
Option 2 — Fan-Out on Read (Pull Model)
When a user reads their feed, the system pulls posts on demand: look up all the people the user follows, fetch their recent posts from the Post DB (or a cache), merge and rank them, then return the result. Nothing is precomputed.
- Write path: Post → write once to Post DB. Done. O(1) regardless of follower count.
- Read path: Fetch the user's follow list → fan-out N parallel reads to post sources → merge → rank → return. O(N) where N is the number of people followed.
Pros: writes are extremely cheap; no celebrity problem; no storage duplication; feed is always fresh (deleted posts disappear immediately).
Cons: reads are expensive and slow — a user following 2,000 accounts triggers 2,000 sub-queries at read time. For active users who open the app constantly, this amplifies DB load enormously. Hard to keep under 200 ms p99.
The Hybrid Approach — What Twitter & Facebook Actually Do
Neither pure strategy works at web scale. Both Twitter and Facebook converged on a hybrid model:
- Regular users (small follower count): fan-out on write. Their post is pushed to all followers' feed caches immediately, asynchronously via a queue.
- Celebrities (millions of followers): fan-out on read. Their posts are NOT pushed — they are too expensive to fan-out. Instead, they are stored in a dedicated hot cache. At read time, the feed service injects celebrity posts into the user's pre-built feed.
- Inactive users: their feed mailbox is not pre-built at all. When they wake up, their feed is assembled on read (they do not generate constant cache-maintenance cost).
Feed Cache Data Structure
The feed mailbox is typically a Redis Sorted Set per user, keyed by feed:{user_id}. Each entry stores a post ID (not the full post) as the member and the post timestamp as the score. Fetching the feed is a single ZREVRANGE feed:{user_id} 0 19 call — O(log N + 20) — followed by a batch MGET on a post-detail cache to hydrate the posts.
Keeping only post IDs (not full content) in the mailbox is important: if post content changes after fan-out (e.g., an edit or deletion), the authoritative post-detail cache is the source of truth. The mailbox is just an ordered pointer list.
Ranking Beyond Chronological Order
Real feeds are not purely chronological. Facebook and Instagram use ML ranking models that score each post on engagement signals (likes, comments, shares, watch time) combined with freshness. Architecturally this means:
- The feed service fetches a larger candidate set (e.g. 200 posts).
- A ranking service scores and reorders them.
- The top N are returned to the client.
The pre-built mailbox becomes a candidate pool, not the final ranked list. Ranking happens at read time on a small candidate set, which keeps it fast while allowing sophisticated personalisation.
Component Summary
- Post Service — accepts writes, stores in Post DB, publishes to fan-out queue.
- Fan-out Workers — async consumers; write post IDs into feed mailboxes for regular users; skip celebrities.
- Feed Service — handles read requests; fetches mailbox, injects celebrity posts on read, triggers ranking.
- Post Cache — Redis/Memcached layer in front of Post DB; holds full post objects.
- Follow Graph Store — graph DB or sharded MySQL table; read-heavy, denormalised for fast lookups.
- Ranking Service — ML-based scorer; called at read time on the candidate set.