Skip to Content
Kido Bag
Architecture Blueprints

05. Twitter/X Hybrid Fanout Feed Engine

Push-Write + Pull-Read + O(N log K) Binary Max-Heap Cursor Merger slashing database load from 20,600 req/s to 1,201 req/s.

05. Twitter/X Hybrid Fanout Feed Engine

Production Paradigm: Asymmetric Push-Write for Normal Users + Pull-Read for Celebrities + O(N log K) Binary Max-Heap Keyset Cursor Merger + Valkey Capped Timeline Mixer.


💥 The Real-World Bottleneck

Timeline feeds (Twitter/X, Facebook News Feed, Threads) represent one of the most notorious challenges in distributed systems:

  1. The Celebrity Write Explosion (Push Breakdown):
    • When a celebrity with 30,000,000 followers posts a tweet, pushing that tweet into 30 million user timeline inboxes requires 30 million Redis writes and database transactions. The system stalls for minutes.
  2. The Follower Read Explosion (Pull Breakdown):
    • If a normal user follows 2,000 accounts and you pull tweets on every feed refresh (WHERE author_id IN (...) ORDER BY created_at DESC LIMIT 20), the relational query must scan, filter, and sort across millions of rows, blowing up database IOPS and memory.

🏛️ High-Concurrency Architecture

This blueprint implements the Twitter/X Hybrid Fanout Architecture (proven in GonnaCrash Level 22 fanout_feed):

Architecture BlueprintMermaid
Compiling architecture diagram...

Key Architectural Phases

  1. Follower Threshold Branching (Celebrity Cutoff = 25,000):
    • Authors with < 25,000 followers fan out asynchronously on write into each follower's Valkey timeline inbox.
    • Authors with >= 25,000 followers skip fanout entirely and write only to their own single outbox list.
  2. Capped Sorted Set Inboxes:
    • Follower timelines are capped at 800 items using atomic Redis Lua scripts (LUA_ZADD_CAPPED_TIMELINE). Older items are pruned automatically, keeping RAM usage deterministic.
  3. $O(N \log K)$ Binary Max-Heap Cursor Merger:
    • When a user requests their feed, the engine fetches the user's pre-computed timeline plus the outboxes of any followed celebrities.
    • A specialized binary max-heap merges these $K$ streams sorted by createdAtMs DESC, id DESC in $O(N \log K)$ time.
  4. Strict Keyset Cursor Pagination:
    • Uses Base64URL-encoded deterministic cursors (createdAtMs + id), eliminating offset pagination drift when new tweets are posted.

🧩 Composed Functional Slices

SliceFile PathCore Role
hybrid-fanout-enginepackages/hybrid-feed/src/hybrid-fanout-engine.tsAsymmetric push/pull classification and database query generation with SQL injection safety
k-way-cursor-mergerpackages/hybrid-feed/src/k-way-cursor-merger.tsDeterministic $O(N \log K)$ Binary Max-Heap merger (kWayMergeFeeds, BinaryMaxHeap, encodeTimelineCursor)
valkey-timeline-mixerpackages/hybrid-feed/src/valkey-timeline-mixer.tsAtomic Lua-capped sorted set timeline storage (LUA_ZADD_CAPPED_TIMELINE) and L1 celebrity cache
progressive-tierpackages/hybrid-feed/src/progressive-tier.tsTier 0 SME, Tier 1 Growth, and Tier 2 Big-Tech hybrid feed benchmark runners

💻 Code Implementation

1. Copy Slices into Your Project

mkdir -p src/lib/feed
cp packages/hybrid-feed/src/hybrid-fanout-engine.ts src/lib/feed/
cp packages/hybrid-feed/src/k-way-cursor-merger.ts src/lib/feed/
cp packages/hybrid-feed/src/valkey-timeline-mixer.ts src/lib/feed/

2. Implementation (src/services/feed-service.ts)

import { HybridFanoutEngine } from '@/lib/feed/hybrid-fanout-engine';
import { kWayMergeFeeds, encodeTimelineCursor, decodeTimelineCursor } from '@/lib/feed/k-way-cursor-merger';
import { ValkeyTimelineMixer } from '@/lib/feed/valkey-timeline-mixer';

const CELEBRITY_THRESHOLD = 25000;
const mixer = new ValkeyTimelineMixer({ maxTimelineItems: 800 });

export async function publishPost(authorId: string, followerCount: number, post: { id: string; createdAtMs: number }) {
  if (followerCount >= CELEBRITY_THRESHOLD) {
    // Mode: Celebrity Pull Bypass (Write once to outbox)
    await mixer.appendCelebrityOutbox(authorId, post);
  } else {
    // Mode: Push Write to follower inboxes
    const followerIds = await getFollowerIds(authorId);
    await mixer.fanoutPostToInboxes(followerIds, post);
  }
}

export async function getHomeTimeline(userId: string, cursorStr?: string, limit: number = 20) {
  const cursor = cursorStr ? decodeTimelineCursor(cursorStr) : undefined;

  // 1. Fetch user inbox from Valkey
  const inboxItems = await mixer.getUserInbox(userId);

  // 2. Fetch outboxes of followed celebrities
  const followedCelebrityIds = await getFollowedCelebrities(userId);
  const celebrityFeeds = await Promise.all(
    followedCelebrityIds.map(celebId => mixer.getCelebrityOutbox(celebId))
  );

  // 3. Merge all streams in O(N log K) via Binary Max-Heap
  const allStreams = [inboxItems, ...celebrityFeeds];
  const { items, nextCursor } = kWayMergeFeeds(allStreams, { limit, cursor });

  return {
    items,
    nextCursor: nextCursor ? encodeTimelineCursor(nextCursor) : null,
  };
}

📊 Production Scale Benchmark & Verification

Verified on the GonnaCrash Level 22 (fanout_feed) simulation:

Architecture PatternPure Pull-on-ReadPure Push-on-WriteKido Hybrid Fanout
Write Amplification1 write / tweet30M writes / celeb tweet (💥)1 write for celebs, bounded fanout for normal
Read Load on Relational DB20,600 req/sec1,201 req/sec1,201 req/sec (94% reduction)
K-Way Merge LatencyN/AN/A< 1.2 ms for 10 streams
Pagination ConsistencyProne to duplicate itemsProne to duplicate items0 Duplicate Posts (Base64 Keyset Cursor)

On this page