← System Design
Building Blocks

Fan-out — Push vs Pull, and Pub/Sub

Fan-out is what happens when one event has to reach many recipients: a post reaches every follower's feed, a message reaches every group member, an order event reaches email, analytics and inventory. Where you do that work, when it's written (push) or when it's read (pull), is one of the most important decisions in feed, notification and messaging designs.

In simple words

A teacher can photocopy the notice and put one in every student's bag (push: work up front, easy to read later), or pin one copy on the notice board and let students come and read it (pull: little work up front, everyone walks to the board).

Push for most authors, pull for celebritiesAuthor postsFan-out queueFan-out workersFeed: BobFeed: CarolFeed read(merge)pushpost idpushpost idcelebrity posts pulled at read
Normal posts are pre-delivered to followers' feeds; celebrity posts are fetched and merged when a feed is read.

Deep Dive: Push (Fan-out on Write) vs Pull (Fan-out on Read)

PUSH: when Alice posts, write the post id into each follower's feed
  post --> [fan-out workers] --> feed:bob   [p9, p7, ...]
                            \--> feed:carol [p9, p3, ...]
  read feed = read one precomputed list    (fast reads)
  cost: one write per follower             (celebrity = millions of writes)

PULL: when Bob opens his feed, fetch recent posts of everyone he follows
  read feed = for each followee: get recent posts -> merge -> sort -> top N
  cost: one read per followee, at read time (slow for users following many)
  writes are cheap (store the post once)

Deep Dive: The Hybrid (What Real Feeds Do)

Most followers-of-normal-users relationships use push: a typical account has hundreds of followers, so the write cost is small and reads stay fast. Accounts with huge follower counts (celebrities) are excluded from push; their posts are pulled at read time and merged into the precomputed feed.

feed(bob) = precomputed_feed(bob)                     # pushed from normal users
          + recent_posts(c) for c in celebrities_bob_follows   # pulled, cached
          -> merge by time/rank -> top 50

Other refinements: skip pushing to inactive users (compute their feed on their next login), cap feed length (keep the latest ~500–800 ids), and store only post ids in feeds, hydrating post content from a cache.

Deep Dive: Pub/Sub as a Fan-out Engine

Fan-out to systems (not users) usually goes through publish/subscribe: a producer publishes OrderPlaced once to a topic, and each subscriber (email, analytics, search index, inventory) has its own subscription and processes independently at its own pace. Adding a new consumer requires no change to the producer.

Key Pointers

How It Works

  1. On write, persist the post, then enqueue a fan-out job.
  2. Workers read the author's follower list in pages and prepend the post id to each (active) follower's feed list in a cache.
  3. Skip authors above a follower threshold; mark them as "pull" sources.
  4. On read, fetch the precomputed list, merge recent posts from followed pull sources, rank, hydrate and return.

Follow-up Questions

A user with 50M followers posts. What happens under pure push?
50M feed writes, which take minutes and saturate workers and cache, delaying everyone else's posts. That's why celebrities are pulled instead.
Someone unfollows a user. What about posts already pushed to their feed?
Filter at read time (check the follow relation while hydrating) and/or run an async cleanup job; a few stale items for a short time is usually acceptable.

Trade-offs

Push buys fast, predictable reads with write amplification and storage for every feed. Pull buys cheap writes and fresh data with expensive, latency-sensitive reads. The hybrid gets most of both at the cost of two code paths and a threshold to tune.

Related