4.4.22 · D4Databases

Exercises — Denormalization — when and why

3,424 words16 min readBack to topic

See the parent for the full derivation: Denormalization — when and why.

Figure — Denormalization — when and why

Level 1 — Recognition

Recall Solution L1.1
  1. Pre-joined / merged column — a column copied from the parent table into the child to kill a join.
  2. Pre-computed / derived column — stores an aggregate that would otherwise be recomputed.
  3. Inline repeating / array column — a list stored in-row instead of via a junction table.
  4. Materialized view — a cached result of an expensive query, refreshed on a schedule.
Recall Solution L1.2

Makes reads cheaper (fewer/no joins) and writes more expensive (you must keep the copies in sync). You are moving cost from the read path to the write path, not deleting it. The figure below shows this schema move concretely.

Figure — Denormalization — when and why

Level 2 — Application

Recall Solution L2.1

Compute both sides of .

  • Left: .
  • Right: . Since , the inequality holds → denormalize. On the decision-boundary map this workload sits far to the right of the diagonal. Cross-check with net saving units/day saved. Strongly positive.
Recall Solution L2.2

Set and solve for : So you need at least 6000 reads/day to justify it. Below that, staying normalized wins. This is exactly the point where the workload lands on the diagonal boundary — and, from the tie-case rule above, break-even alone is not a reason to denormalize; you'd want to be clearly past it.


Level 3 — Analysis

Figure — Denormalization — when and why
Recall Solution L3.1

(a) Normalized read cost = views × N = row-scans/day (2 billion). (b) Denormalized: each read touches 1 stored value ( reads → touches) and each write touches 1 row ( writes → touches). Total = row-touches/day. (c) Factor = . Denormalizing cuts row work by 800,000×. This is why aggregate counts are the textbook denormalization case.

Recall Solution L3.2

(a) The diagonal boundary crosses at . (b) . ✅ They match exactly — the break-even ratio is the cost ratio, which is precisely what the boxed rule says. Right of the line, denormalizing wins; left of it, stay normalized. And on the line (the blue dot) is the tie: , so you would not denormalize there.


Level 4 — Synthesis

Recall Solution L4.1

Trigger (i) fits best. It runs inside the same transaction as the item change, so total_amount is always consistent atomically — no window where the order shows a wrong total. The transactional flow is drawn below.

  • App logic (ii) works but is fragile: if any code path (a script, an admin tool, a bulk import) writes order_items without going through that code, the total drifts. The invariant lives outside the database.
  • Scheduled refresh (iii) is wrong for "exact instantly": it leaves a staleness window between refreshes. That's fine for a dashboard, not for a checkout total. Materialized-view refresh trades freshness for cheaper writes — the opposite of what this requirement wants.

insert order_item

trigger fires

update total_amount

commit together

read sees exact total

Recall Solution L4.2

(a) Left . Right . Since denormalize. Net units/day saved. (b) Add users.follower_count; guard it with a trigger on follows that does +1 on insert and -1 on delete, inside the same transaction. (c) Failure modes: (1) a follow inserted without firing the trigger → drift; (2) concurrent follows racing to update the same counter → lost updates (fix with an atomic count = count + 1, not read-modify-write); (3) double-firing on retries → over-count. A periodic reconciliation job that recomputes the true count is wise insurance.


Level 5 — Mastery

Recall Solution L5.1

(a) Left . Right . Since , the inequality failsdo NOT denormalize. Net units/day (a loss). On the map this workload sits left of the diagonal. (b) is high because every balance change must be atomic, durable, and auditable; a cached balance would need locking, transactional coupling, and reconciliation for regulatory correctness — expensive per write. And is nearly as large as , so the sync cost is paid almost as often as the saving. (c) Keep the ledger as the single source of truth and compute the balance from it (optionally an indexed running total per account rather than a duplicated field). This is where Indexing beats denormalization: it speeds reads without introducing a second copy of the fact.

Recall Solution L5.2

(a) units/day. (b) units/day → still strongly positive. Denormalize (build the view). (c) The verdict is a clear yes: even after paying /day for storage you net /day. The knob to tune is refresh cadence. The dominant write cost is the refresh (/day); if you batch refreshes — say once/hour instead of on every underlying write — you cut the effective that drives sync cost, pushing even higher, at the price of some staleness. Because this is a reporting query (an OLAP-style workload), slight staleness is acceptable, so trading freshness for a bigger net saving is the right move here.

Recall Solution L5.3

A good answer hits: (1) Normalize first — never start denormalized; measure to find an actually-slow hot read. (2) Estimate the read/write ratio and the cost ratio ; denormalize only when the former clearly beats the latter (a tie, , is not enough — the non-math costs break the tie toward "stay normalized"), and note that truly write-once data () is an automatic yes. (3) Choose a guard mechanism matched to the freshness requirement — trigger/transaction for must-be-exact-now, scheduled refresh for tolerable staleness. (4) Consider whether Indexing or a materialized view solves it without duplicating a fact. (5) Add reconciliation/monitoring because unguarded duplicates will drift into anomalies.


Connections

  • Denormalization — when and why — the parent this drills
  • Normalization (1NF 2NF 3NF BCNF) — always the starting point
  • Update Insert Delete Anomalies — the drift risk every solution must guard
  • Indexing — speed reads without a second copy (L5.1)
  • Materialized Views — cached-query denormalization (L4, L5.2)
  • Database Triggers — the exact-now sync mechanism (L4.1)
  • OLTP vs OLAP — why analytics tolerate stale denormalized data
  • CAP Theorem — distributed denormalization to dodge cross-node joins

#flashcards/coding