Skip to main content

Denormalization, Precomputation & Materialized Views

Level 3: Level 3: Scaling the Data Tierhard30 minfan-outmaterialized-viewsfeed

Precompute rollups and sketches so reads are O(1) lookups, and feed timelines with hybrid fan-out: push for normal users, pull-and-merge for celebrities.

Trade write cost for cheap reads, on purpose

Normalization optimizes for write correctness: every fact lives in exactly one place. That is the wrong default when reads outnumber writes by 100:1 or 1000:1, which is the common web shape. Denormalization deliberately duplicates data so the read path does no joins and no aggregation at request time. You pay with write amplification (one logical write fans out to many physical writes) and the ongoing job of keeping the copies consistent. The trade is almost always worth it when a read is on the hot path and a write is not.

The three concrete tools

  • Precomputed/materialized views: instead of running SELECT count(*) ... GROUP BY on every dashboard load, maintain a rollup table (daily_orders_by_region) that a job or a stream updates. Reads become a single indexed lookup. You trade freshness and storage for read latency.
  • Approximate structures: when the answer does not need to be exact, use sketches. HyperLogLog counts unique visitors in ~12 KB per counter with ~0.8% standard error instead of storing every visitor id. Count-Min Sketch gives approximate frequencies for "top trending" in fixed memory. Redis ships both. Exactness is a cost you should only pay when the product needs it.
  • Feed fan-out: the canonical denormalization problem. A user opens their home timeline and wants the merged, time-sorted posts of everyone they follow, in under ~100 ms.

The two feed strategies

  • Fan-out-on-write (push): when Alice posts, immediately write that post id into the precomputed timeline of every follower (a per-user list, often in Redis). Reads are trivial: read your own list. But a post by someone with 50M followers triggers 50M writes. Write amplification is O(followers).
  • Fan-out-on-read (pull): store each post once. At read time, query the recent posts of everyone the reader follows and merge-sort them. Writes are O(1), but a read for someone following 5,000 accounts is a large scatter-gather merge on the hot path.
Check yourself
Follower counts are power-law: the median user has a few hundred followers, the top accounts have 50 million. Which fan-out strategy do you ship?
 fan-out-on-write            fan-out-on-read
 Alice posts                 Bob opens feed
   |                           |
   +-> write to each of        +-> query recent posts of
       Alice's followers'          each account Bob follows,
       precomputed feed            then merge-sort at read time
 cheap reads, costly writes  cheap writes, costly reads

Neither pure form survives real distributions, because follower counts are power-law. The production answer is a hybrid: fan-out-on-write for normal accounts, but do not push posts from celebrity/whale accounts. Instead, at read time, pull the celebrity posts the reader follows and merge them into the precomputed list. This is exactly what Twitter/X described.

Interview nuance: the disqualifying mistake is proposing pure fan-out-on-write and not noticing that one celebrity post is now 50M writes and a thundering write storm. Say the threshold out loud: accounts above roughly 10k to 1M followers are handled on read; everyone else on write. The second nuance is owning the consistency cost you just created: denormalized copies (a cached follower count, a duplicated author name) can drift, and now you own an invalidation or reconciliation job.

Recap: denormalize when reads dominate, using materialized/rollup views and approximate sketches to make reads O(1) lookups; for feeds, use a hybrid that precomputes normal-user feeds and merges celebrity posts at read time, and accept that you now own write amplification and copy consistency.

Check yourself

For each product need, pick the precomputation tool this lesson gives you.

A dashboard shows daily orders by region without running a 'GROUP BY' per page load
Count unique monthly visitors within a percent, in kilobytes of memory
Top trending hashtags from a firehose, in fixed memory
A home timeline that loads in under 100 ms for a user who follows two celebrities

Apply

Your turn

The task this lesson builds to.

Design a social timeline/feed, choosing between fan-out-on-write and fan-out-on-read for a mix of normal and celebrity users, and specify where the hybrid boundary sits.

Think about

  1. When does fan-out-on-write beat fan-out-on-read, and vice versa?
  2. How does a hybrid handle celebrity accounts?
  3. What is the write-amplification and consistency cost you now own?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the analytics/counts layer for a live-streaming platform (Twitch-scale: a top stream has 300k concurrent viewers) that must show a near-real-time viewer count, unique-viewer count for the session, and a 'top 10 trending streams' board, without hammering the primary DB on every read.

Think about

  1. Which of these numbers actually needs to be exact?
  2. What does a HyperLogLog buy over storing every viewer id?
  3. Where does the trending board's aggregation run so reads stay O(1)?