System designCore11 min

Fan-out on write vs read

A home feed shows the newest posts from everyone you follow. Building it means copying each post to many people, either when it is written or when someone reads. Which moment you choose decides where the cost lands, and one account with millions of followers can break either choice.

Watch it evolve1 / 7
The problem

A home feed across 200 cooks

Mia follows 200 cooks and expects their 50 newest recipes, newest first, in well under a second. The app sees 2,000 posts and 200,000 feed opens a second.

80
Start
Stage 1
Stage 2
Stage 3
Final

1 · The problem

A home feed is a merge across everyone you follow

A recipe-sharing app has a home feed. Mia follows 200 cooks, and when she opens the app she expects the 50 newest recipes from all of them, newest first, in well under a second. Every post exists once, in the posts table, stored under its author. The feed has to gather posts from 200 authors and merge them.

Copying one item out to many places is called fan-out. The question is when it happens:

  • Fan-out on read (pull): when Mia opens her feed, fetch the recent posts of all 200 cooks she follows and merge them.
  • Fan-out on write (push): when a cook posts, copy the post's ID into a ready-made feed for each of the cook's followers. Mia's feed is already built when she opens it.

Either way each post must reach each follower; the difference is copying it once when it's posted, or gathering it again on every read. Who pays, and how often, changes the total work. Feeds are opened far more often than recipes are posted. The app assumes these numbers, and the rest of the page uses them:

Assumption

Value

Posts written

2,000 a second

Feeds opened

200,000 a second, 100 times the posts

Accounts a user follows, on average

200

Followers an account has, on average

200

The last two averages are always equal. Every follow is one "following" for one user and one "follower" for another, so both averages are total follows ÷ users. The spread is what differs: nobody follows 8 million accounts, but some accounts have 8 million followers. That asymmetry decides the rest of the page.

2 · Pull

Fan-out on read does the work every time someone looks

With fan-out on read, posting is a single insert. Opening a feed does the gathering:

Mia's appFeed service200 lookups, then mergePosts storesharded by authoropen feedrecent posts × 200Mia's appFeed service200 lookups, then mergePosts storesharded by authoropen feedrecent posts × 200

Each feed open asks for the recent posts of all 200 followed cooks, spread across many shards, then merges and sorts them.

It has real strengths:

  • Posting is cheap and instant: one row, visible to everyone the moment it is saved.
  • Nothing extra is stored: no per-user feeds to keep, repair or delete.
  • The feed is always current: an unfollow, a deleted post or a block takes effect on the next read.

The cost lands on the busiest path. At 200,000 feed opens a second with 200 accounts each, the posts store answers 200,000 × 200 = 40 million lookups a second. And each feed waits for the slowest of its many lookups, so its latency is set by the tail of the posts store, not its average (Latency). Large systems do run feeds this way: Facebook's news feed has been served by aggregators that query, at request time, many leaf servers holding recent activity, then rank and filter the results.

3 · Push

Fan-out on write does the work once, when someone posts

With fan-out on write, every user has a precomputed feed: a short list of post IDs, newest first, usually kept in an in-memory store. When a cook posts, the post is saved once, and a background job adds its ID to the feed of every follower:

Fan-out queuea job per saved postFan-out workerslook up followersFeed cacheone list per userpost 9182add ID × 200Fan-out queuea job per saved postFan-out workerslook up followersFeed cacheone list per userpost 9182add ID × 200

The cook gets an answer as soon as the post is saved. Workers then add its ID to 200 followers' feeds, and Mia's next feed open is a single read of her list.

Now compare the two with the app's numbers. Fan-out on write turns each read into one lookup and moves the copying to the much rarer writes:

Per second

Fan-out on read

Fan-out on write

Work when posting

2,000 inserts

2,000 × 200 = 400,000 feed inserts

Work when reading

200,000 × 200 = 40,000,000 lookups

200,000 lookups

Total

About 40 million operations

About 0.6 million operations

More than 60 times less work, and a feed read that touches one list instead of 200 authors. The costs are new ones:

  • Delay. Followers see a post only after the workers reach their feed, usually seconds later. The author's own feed is updated directly, so they never wonder where their post went.
  • Storage. A list for every user, copied for availability (see the details section).
  • Work for nobody. A post is copied into the feeds of followers who may never open the app again.

4 · The catch

One account with millions of followers breaks fan-out on write

The app's most famous chef has 8 million followers. Each of her recipes is not 200 feed inserts but 8,000,000. If the whole fan-out fleet can insert 1 million IDs a second, one post occupies it for 8 seconds, while every other cook's posts wait behind it. Post three times in a minute and the fleet spends 24 of those 60 seconds on her.

Twitter hit exactly this. Its home timelines were built by fan-out on write, and for accounts with tens of millions of followers, delivering one tweet to every timeline could take up to 5 minutes. That produced a visible bug: followers saw replies to a tweet before the tweet itself, because the replies came from ordinary accounts and were delivered first:

Both jobs run on the same fleet. The reply needs 200 copies and lands at once; the post it answers needs 8 million and lands last. Order across authors is lost.

Fan-out on read has the mirror problem, but milder: nobody follows millions of accounts, so a read never has to gather from millions of authors. What hurts fan-out on read is the total volume of reads, not any one user.

5 · The fix

Push for most accounts, pull for the few huge ones

The standard answer combines the two. Accounts below a follower threshold are fanned out on write as before. Accounts above it are not fanned out at all; their posts are pulled and merged in when a feed is read. Twitter moved to this hybrid: celebrity tweets are fetched at read time and merged with the precomputed timeline.

Mia follows 200 cooks, and 2 of them are above the app's threshold of 100,000 followers. Opening her feed now takes three lookups instead of one, run at the same time:

One read of the pushed list plus a pull for the 2 big accounts, merged by time. The famous chef's posts never touch 8 million lists.

The threshold is a dial between the two costs:

Threshold

Effect on posting

Effect on reading

Very high (push almost everyone)

Huge accounts flood the fan-out fleet

Feed reads stay one lookup

Very low (pull almost everyone)

Posting is cheap

Feed reads are back to many lookups

Around the largest audiences only

Fan-out stays fast and predictable

A read adds one lookup per big account followed

The pull side stays cheap because popular accounts are few and their recent posts are read constantly, so they sit in cache. The same account is pulled by millions of feeds, and each pull is one cached read.

6 · The details

Store IDs, cap each list, and skip people who aren't looking

Precomputed feeds are kept small and cheap with three habits, all used by Twitter:

  • Store IDs, not posts. Each entry is a post ID and its author's ID. The recipe text, photos and like counts are fetched when the feed is shown, so an edit appears everywhere at once.
  • Cap each list. Twitter kept 800 entries per home timeline. Nobody scrolls further in a feed; older posts can be pulled if someone ever does.
  • Only active users. Twitter kept timelines in memory only for users who had logged in within 30 days. When an inactive user returns, their feed is rebuilt once by fan-out on read, and pushing to them resumes.

With these habits the memory is easy to estimate. Say 50 million active users, 800 entries each, 16 bytes per entry (two 8-byte IDs):

Step

Result

50,000,000 × 800 entries

40 billion entries

× 16 bytes

640 GB

× 3 copies for availability

About 1.9 TB of memory, spread over a cluster

Copies of a post ID in 200 feeds also make removal harder. When a recipe is deleted, an account goes private, or Mia unfollows a cook, the pushed IDs are already in place. Chasing every copy is slow and easy to get wrong, so feed services usually filter at read time: when the feed is shown, posts that are deleted, hidden or from someone Mia no longer follows are dropped, and a background job cleans the lists later.

7 · In practice

Choose by the ratio of reads to writes and the largest audience

Feeds are the classic case, but the same choice appears wherever one thing reaches many people: notifications, group chats, activity streams, dashboards. Two numbers settle most of it:

If…

Lean toward

Because

Reads far outnumber writes

Fan-out on write

Pay once per post instead of once per view

Writes are frequent and reads rare

Fan-out on read

Don't copy items nobody will look at

A few accounts have huge audiences

Hybrid: pull those accounts

Keeps one post from flooding the fan-out fleet

Every view must reflect deletions and blocks at once

Fan-out on read, or filter at read time

Pushed copies lag behind changes

And whichever you pick:

  • Run fan-out on write in the background, through a queue, and answer the author as soon as the post is saved (Message queues & pub/sub).
  • Measure delivery time, from post to the last follower's feed, and watch it for the biggest accounts.
  • Keep lists of IDs, capped, and fetch the content when the feed is shown.
  • Push only to active users, and rebuild a returning user's feed on read.
  • Filter at read time for deletions, blocks and unfollows.
  • Watch hot keys: a famous account's posts are read by millions of feeds and need caching and replication of their own (Hot partitions & skew).

Check yourself

5 questions

1. 200,000 feeds are opened per second, each user follows 200 accounts, and 2,000 posts are written per second. How many posts-store lookups per second does pure fan-out on read need?
2. In a follow graph, how does the average number of accounts a user follows compare with the average number of followers an account has?
3. Under pure fan-out on write, followers of an account with 30 million followers see replies before the post itself. Why?
4. What does the hybrid approach do with accounts above the follower threshold?
5. Mia unfollows a cook, but the cook's last 5 post IDs are already in her precomputed feed. What do feed services usually do?

Takeaways

Remember this

  • Fan-out on read gathers posts when a feed is opened; fan-out on write copies each post into followers' feeds when it is written.
  • When reads far outnumber writes, fan-out on write is much cheaper overall, at the cost of delay, storage and copies for inactive users.
  • Accounts with millions of followers make fan-out on write slow and break ordering across authors.
  • The hybrid pushes for ordinary accounts and pulls the few huge ones at read time, merging both into the feed.
  • Keep capped lists of IDs, push only to active users, and filter deletions, blocks and unfollows at read time.