CMD Guide
HomeSystem DesignSystem Design Problems

News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced

Fan-out is only half the design

Most news-feed discussions are entirely about the write side — fan-out-on-write versus fan-out-on-read, and the celebrity problem. That debate is well covered elsewhere in this guide. This page is about what happens on the read side, which is where a feed system actually spends its money, and which the fan-out debate never touches.

The key realization: the news feed cache stores nothing a user can see. It stores a list of post IDs. A feed is not a list of IDs — it is usernames, profile pictures, post text, images, like counts, and whether you personally liked each item. All of that has to be fetched and assembled on every single feed open. That assembly step is called hydration, and it is the dominant read workload of the entire system.

The two APIs

Note what is not a parameter: whose feed. The user is identified by the auth token, not by a path parameter — a feed endpoint that accepts a user ID is an authorization bug waiting to be found.

A feed request flows from client to the News Feed cache, which returns only post IDs. For each post ID the system then reads five further caches: Content for the post body, Social Graph for relations, Action for whether this user liked it, Counters for like and reply totals, and User for name and avatar. The results are assembled into a hydrated JSON feed with media served from CDN. A callout notes the read amplification: roughly 20 posts times 5 lookups is about 100 cache reads per feed open. The five tiers are separate because the pieces change at very different rates.
A feed request flows from client to the News Feed cache, which returns only post IDs. For each post ID the system then reads five further caches: Content for the post body, Social Graph for relations, Action for whether this user liked it, Counters for like and reply totals, and User for name and avatar. The results are assembled into a hydrated JSON feed with media served from CDN. A callout notes the read amplification: roughly 20 posts times 5 lookups is about 100 cache reads per feed open. The five tiers are separate because the pieces change at very different rates.

Trace: one feed open, end to end

  1. The user requests her news feed: GET /v1/me/feed.
  2. The load balancer distributes the request to a web server.
  3. The web server calls the news feed service.
  4. The news feed service gets a list of post IDs from the news feed cache.
  5. Because a feed is more than IDs, the service fetches the complete user and post objects from the user cache and post cache to construct the fully hydrated feed.
  6. The hydrated feed is returned as JSON for the client to render.

Step 5 is where the cost lives. If a feed page shows 20 posts and each post needs its body, its author, its counters and your action state, one feed open becomes on the order of 100 cache reads. That is the number that determines your cache fleet size — not the request rate. A system serving 10,000 feed opens/second is issuing roughly a million cache reads/second, and any design conversation that stops at "we'll cache the feed" has missed two orders of magnitude.

The five cache tiers, and why they are five

The obvious question is why not cache one fully-rendered feed blob per user and serve it in a single read. The answer is invalidation rate. These five things change on completely different clocks: a post body is effectively immutable; a like counter changes several times a second on a popular post; a friend list changes weekly. A single blob is invalidated by its fastest-moving field — every new like on any post in your feed would evict the entire feed, so your cache hit rate collapses to roughly zero exactly when the content is popular. Splitting by change rate lets each piece live as long as it actually stays valid.

This is a generalizable rule worth carrying to other designs: cache boundaries should follow mutation rate, not object shape. Grouping a stable field with a volatile one costs you the stable field's hit rate.

Why a graph store for the social graph

Friend and follower relationships are naturally a graph, and the fan-out service's central query is "who follows this user?" — a traversal, executed on every publish. A graph database is a reasonable fit for storing and traversing those relations. That said, be honest about the alternative: at scale, most production systems store the edge list in a sharded key-value store with an adjacency list per user, because the query needed is a single-hop lookup rather than a deep traversal, and a KV store is easier to shard and cache. Choose a graph engine when you actually issue multi-hop queries (friends-of-friends, mutual connections); choose an adjacency list when the hot query is one hop.

Which read strategy, when

StrategyRead costInvalidation costChoose it whenWhen it is wrong
Cache rendered feed per user1 readEnormous — any field change evicts everythingFeeds that are immutable once built (a daily digest, an email)Live social feeds with counters and personal action state
IDs + hydration from tiers~5 reads per itemLow — each tier expires on its own clockLive feeds; the standard choiceVery low traffic, where the complexity is not repaid
IDs + hydration, batched~5 multi-get round tripsSame as aboveAny real deployment — batch per tier, not per postNever wrong; it is the fix for the naive version
Hot cache for popular content1 read, in-memory localNeeds its own TTL disciplineCelebrity posts read by millionsUniform-popularity content, where it adds a tier for nothing

Row 3 is the one that matters operationally. Hydrating naively means a loop issuing one cache GET per post per tier — 100 sequential round trips, and at 0.5 ms each that is 50 ms of pure network wait inside one feed request. Batching into one multi-get per tier turns 100 round trips into 5, which is the difference between a 5 ms and a 50 ms feed. The N+1 query problem, relocated from a database to a cache.

Pitfalls

Cost model — what dominates the bill

A news feed's bill is cache memory plus the network chatter of hydration, and both are driven by the amplification factor rather than the user-facing request rate.

Rough BOTE at 10 million DAU opening their feed 5 times a day: that is 50 million feed opens/day ≈ 580 opens/second average, peaking maybe 1,200. Multiply by ~100 hydration reads and the cache tier serves roughly 120,000 reads/second at peak. Memory: if 10 million users each keep a 500-ID feed window at 8 bytes per ID, the feed tier alone is 10M × 500 × 8 B = 40 GB — small. The content tier is the large one: 100 million recent posts at ~1 KB is 100 GB, and at roughly $0.02/GB-hour for managed in-memory cache, ~150 GB of RAM runs to about $2,200/month.

Media never touches this path — images and video are served from CDN, and that egress is typically the largest single line item in the whole system, dwarfing the cache. A feed showing one 200 KB image per post at 50 million opens × 20 posts would be prohibitive if it were not for client-side and CDN caching, which is why image sizing and CDN hit rate are feed-cost levers, not frontend details.

Dominant line items: CDN egress for media; then in-memory cache for the content tier; then the compute doing hydration.

Levers: batch hydration (cuts hydration compute and tail latency without touching cost of goods); bound the cached feed window (directly sizes the feed tier); keep a hot cache for celebrity content so a single popular post is not read from the distributed tier a million times; and aggressively size and cache media, which attacks the biggest line.

Operability: the fingerprints of a sick feed

The tell-tales here are mostly about amplification going wrong. Feed latency scaling linearly with posts per page is the per-item hydration loop — the giveaway is that P50 latency moves when you change page size, which a batched implementation would barely notice. Cache hit rate collapsing precisely on popular content is a tier-boundary problem: something volatile (a counter, a view count) shares a key with something stable, so the hottest items are also the most-evicted. One cache node running far hotter than its peers is a hot key, almost always a celebrity's counter or profile; the fix is a local hot cache or key replication, not a bigger cluster.

Feeds showing stale like counts while post bodies are current is normal and expected here — different tiers, different TTLs — and it is worth writing down as intended behaviour so it does not get "fixed" by merging the tiers. The genuinely dangerous fingerprint is a database CPU spike immediately after a cache deploy or flush: every tier missing at once, all falling through to the database. Without request coalescing and a fallback rate limit, that is a self-inflicted outage, and it happens at deploy time rather than at peak traffic, so it surprises people.

Watch also for hydration fetching more items than the page shows — a sign that privacy or block filtering happens after hydration, meaning you are paying to assemble content you then discard. Signals worth having: hydration reads per feed open (should be ~5 batches, not ~100 gets), per-tier hit rate and TTL, per-key request rate outliers, database fallback QPS with a coalescing counter, and filtered-after-hydration item count.


Re-authored for this guide from the Alex Xu Vol. 1 news-feed chapter (cache-tier taxonomy from its five-layer design); hydration fan-out diagram hand-authored as SVG. Read-path complement to the existing "Designing Facebook Newsfeed", "News Feed: Fan-out-on-Write vs Fan-out-on-Read" and "Designing Twitter's Timeline" pages, which cover the write path.

🤖 Don't fully get this? Learn it with Claude

Stuck on News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced** (System Design) and want to truly understand it. Explain News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **News Feed Caching & Hydration — Five Tiers, Graph Store & Feed APIs, Traced** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes