Solving High-Concurrency Bottlenecks: Implementing the Fan-Out Pattern for Timeline Delivery
Learn how to implement the Fan-out pattern to solve timeline scalability issues, moving from expensive database joins to pre-computed cached feeds.
20 Sept 2026, 02:38 UTC

The Scaling Wall: Read vs. Write Performance
When building a social feed or timeline, the primary engineering challenge is the imbalance between write operations (posting a message) and read operations (loading a feed). In a traditional relational database model, loading a timeline requires a complex join of followers and posts, which becomes computationally expensive as the user base grows. When thousands of users request their feeds simultaneously, the database reaches a CPU and I/O ceiling, leading to systemic timeouts.
The solution is to shift the computational burden from the read path to the write path using a Fan-out strategy. Instead of querying the database every time a user opens their app, the system pre-computes the timeline and stores it in a high-speed cache.
Prerequisites for Fan-Out Implementation
- Distributed Cache: A memory-store (such as Redis or Memcached) capable of handling millions of small, independent lists.
- Message Queue: A system (like Kafka or RabbitMQ) to handle asynchronous processing so the user doesn't wait for the fan-out to complete before receiving a "Post Successful" confirmation.
- User Graph Service: A dedicated service to quickly retrieve a list of a user's followers.
The Fan-Out Procedure
The following workflow describes how to transition from a "Pull" model (on-demand query) to a "Push" model (pre-computed delivery).
1. Asynchronous Trigger
When a user submits a post, the application should not update followers' feeds synchronously. Instead, the API writes the post to the primary database and pushes a PostEvent to a message queue.
2. Follower Lookup
A background worker consumes the PostEvent. It queries the User Graph Service to retrieve the IDs of all users following the author. This step isolates the graph traversal from the main request-response cycle.
3. Cache Injection
The worker iterates through the follower list and injects the Post ID into each follower's pre-computed timeline cache. This is the "Fan-out" phase.
# Conceptual Logic for Fan-out Worker
# Run as a background process with high-concurrency permissions
function handlePostEvent(event):
authorId = event.author_id
postId = event.post_id
# Retrieve followers from Graph Service
followers = GraphService.getFollowers(authorId)
for followerId in followers:
# Push PostID to the head of the follower's cached timeline
# Risk: High write amplification for users with millions of followers
CacheStore.lpush("timeline:" + followerId, postId)
# Trim cache to keep only the most recent 1,000 posts
CacheStore.ltrim("timeline:" + followerId, 0, 999)
4. Optimized Read
When a user requests their timeline, the system no longer performs a database join. It simply fetches the list of IDs from timeline:[userId] in the cache and hydrates those IDs with the actual post content.
Handling the "Celebrity Problem" (Write Amplification)
A pure Fan-out model fails when a user has millions of followers. Injecting a single post into 50 million caches creates a massive spike in write operations, potentially lagging the system for minutes.
To mitigate this, implement a Hybrid Model:
| User Type | Delivery Strategy | Logic |
|---|---|---|
| Standard User | Push (Fan-out) | Post is pushed to all follower caches. |
| High-Profile (Celebrity) | Pull (On-Demand) | Post is NOT fanned out. It is merged into the follower's timeline at read-time. |
Verification and Diagnostics
To verify the implementation, monitor the Cache Hit Ratio and Fan-out Latency. If the time between a post being created and it appearing in a follower's cache exceeds a defined threshold (e.g., 2 seconds), the message queue is likely bottlenecked.
Check: Run a trace on a single PostEvent to measure the time from Queue Entry $\rightarrow$ Graph Lookup $\rightarrow$ Cache Update.
Rollback and Recovery
Since this operation changes the state of the distributed cache, a failure in the fan-out worker can lead to "missing" posts in timelines. To recover:
- Flush Affected Caches: Clear the timeline caches for users who missed the update.
- Force Re-sync: Trigger a manual "Pull" query for those users to rebuild their cache from the primary database.
- Replay Queue: If the message queue persists the events, replay the
PostEventlogs from the point of failure.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.