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.

Play a level

See it on the board

The same traffic runs on two boards. The simulator measured both when this page was built.

Board to show
HTTP/2SQLUsersAPI server1 vCPU · 2 GBPostgres0.5 vCPU · 2 GB
HTTP/2SQLUsersAPI server1 vCPU · 2 GBPostgres0.5 vCPU · 2 GB
Six lookups for each read: misses a goal
HTTP/2SQLUsersAPI server1 vCPU · 2 GBPostgres0.5 vCPU · 2 GB
HTTP/2SQLUsersAPI server1 vCPU · 2 GBPostgres0.5 vCPU · 2 GB
One stored feed for each read: meets the goals
See it on the board
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

Default part values from the catalog. The mini board has 2,000 feed reads/s and a freshness limit of 1 s.
NumberValue
Reads per second, one database vCPU15,000 reads/s
Database read, median0.5 ms
CPU time for one API call0.3 ms
Tables join, documents copyTap or press Space to switch between tables and documents

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.