Denormalization
Denormalization stores a derived copy of data for one read pattern. In the mini board it cut 12,000 lookups/s to 2,000. A copy with no update path goes stale, so keep the source of truth and update the copy on each change.
See it on the board
The same traffic runs on two boards. The simulator measured both when this page was built.
| Measure | Goal | Six lookups for each read | One stored feed for each read |
|---|---|---|---|
| p99 latency | under 150 ms | 6.8 s Fail | 26 ms Pass |
| Data age | under 1 s | 0 ms Pass | 0 ms Pass |
| Monthly cost | 16 USD/mo | 16 USD/mo | |
| Error rate | 36.5% | 0% |
Key numbers
| Number | Value |
|---|---|
| Reads per second, one database vCPU | 15,000 reads/s |
| Database read, median | 0.5 ms |
| CPU time for one API call | 0.3 ms |
Tables · a read touches 3 places in 0.5 ms · a shared change writes 1 place
How it works
A normalized model stores each fact once. A feed page then joins several tables. In the mini board each feed request needs six indexed lookups, and users read feeds 2,000 times each second. That is 12,000 lookups/s for reads alone.
A denormalized model stores the finished feed as one record for each user. A read then needs one lookup, and the load falls to 2,000 lookups/s. The read is simple and fast. The work moves to the write side: when a source changes, the system must update every copy that uses it.
The update path is the hard part. In the mini board 1 percent of requests change a feed, and the change must show in the feed within 1 s. A copy that you build once and cache for 60 s serves feeds that are up to 39.9 s old, which fails the limit. A copy that you update on every change stays fresh.
Keep one source of truth, and treat the copy as derived: you can rebuild it from the source at any time. Update it from a change feed, so it never relies on a person to do it.
Do not denormalize without a read pattern that needs it. Each copy costs storage and write work.
In the levels you pick the stored shape for the reads that matter and keep the copy fresh.