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
- Feed publishing —
POST /v1/me/feedwith the content and an auth token. This is the write path that triggers fan-out. - Feed retrieval —
GET /v1/me/feed. Returns the assembled feed.
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.
Trace: one feed open, end to end
- The user requests her news feed:
GET /v1/me/feed. - The load balancer distributes the request to a web server.
- The web server calls the news feed service.
- The news feed service gets a list of post IDs from the news feed cache.
- 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.
- 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
- News Feed — stores IDs of news feeds.
- Content — stores every post's data; popular content is kept in a separate hot cache.
- Social Graph — stores user relationship data.
- Action — stores whether a user liked, replied to, or otherwise acted on a post.
- Counters — stores counters for likes, replies, followers, following.
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
| Strategy | Read cost | Invalidation cost | Choose it when | When it is wrong |
|---|---|---|---|---|
| Cache rendered feed per user | 1 read | Enormous — any field change evicts everything | Feeds 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 item | Low — each tier expires on its own clock | Live feeds; the standard choice | Very low traffic, where the complexity is not repaid |
| IDs + hydration, batched | ~5 multi-get round trips | Same as above | Any real deployment — batch per tier, not per post | Never wrong; it is the fix for the naive version |
| Hot cache for popular content | 1 read, in-memory local | Needs its own TTL discipline | Celebrity posts read by millions | Uniform-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
- Per-item hydration loops. The single most common performance defect in feed code; batch per tier instead.
- Caching the feed as one blob and then being surprised by a near-zero hit rate on popular content.
- Counters in the same tier as content. Counter writes then invalidate immutable post bodies.
- Unbounded feed cache per user. Store a bounded window (the first few hundred IDs); users almost never scroll further, and the tail can be recomputed on demand. Caching complete history for every user is how a feed cache outgrows its memory budget.
- Hydrating posts the viewer cannot see. Privacy and block checks must happen before or during hydration, not in the client — otherwise deleted or blocked content ships to the device.
- Treating a cache miss as an error. Every tier needs a database fallback path, and that path must be rate-limited, or a cache flush becomes a database outage (a thundering herd).
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.
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.
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.
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.
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.