Implementing Denormalization Strategies

1. Understanding When to Denormalize

TriggerReason
Hot Read PathJoins dominate latency budget
Reporting / BIAggregations on huge tables
Read:Write Ratio > 10:1Update overhead acceptable
Cross-shard JoinsNetwork round-trips dominate
Cache SubstitutionPre-computed values vs runtime cache miss

2. Creating Redundant Columns

PatternExampleMaintained By
Copy parent columnorder.customer_name (from customers)Triggers / app logic
Generated columnfull_name = first || ' ' || lastDB engine
Counter cachepost.comment_countTriggers / queues

Example: Generated Column (Postgres)

ALTER TABLE users ADD COLUMN full_name TEXT
  GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED;

3. Combining Tables

StrategyWhen
Merge 1:1 tablesAlways joined together, low NULL ratio
Embed 1:N as array/JSONSmall child set, never queried independently
Pre-join fact + dimensionsOLAP wide tables for analytics

4. Using Materialized Views

FeaturePostgresOracle
CreateCREATE MATERIALIZED VIEWCREATE MATERIALIZED VIEW
RefreshREFRESH MATERIALIZED VIEW [CONCURRENTLY]DBMS_MVIEW.REFRESH
Auto-refreshExternal (cron/trigger)ON COMMIT / scheduler
IndexableYesYes

Example: Materialized Sales Summary

CREATE MATERIALIZED VIEW daily_sales AS
SELECT date_trunc('day', placed_at) AS day,
       SUM(total) AS revenue,
       COUNT(*)   AS orders
FROM orders
GROUP BY 1
WITH DATA;

CREATE UNIQUE INDEX ON daily_sales(day);
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;

5. Implementing Summary Tables

TypeUpdate Strategy
Rollup tableIncremental via trigger or batch job
Pre-aggregated countersUPDATE on each write
Bucketed snapshotsHourly/daily snapshot of metrics

6. Handling Data Consistency in Denormalized Schemas

MechanismUse
TriggersSynchronous propagation within DB
Change Data Capture (CDC)Debezium → downstream sinks
Outbox + Event BusReliable async propagation
Scheduled ReconciliationNightly diff + fix drift
Application TransactionsSame tx writes source + copy
Warning: Denormalized data WILL drift. Always have a reconciliation job to detect and repair inconsistencies.

7. Creating Read-Optimized Schemas

PatternDescription
CQRSSeparate write model + read model
Star SchemaCentral fact + denormalized dimensions
Snowflake SchemaNormalized dimensions, joined to fact
Wide TablesPre-joined columns to avoid runtime joins
Read ReplicasEventually consistent copies for queries

8. Balancing Storage vs Query Performance

DecisionProsCons
DenormalizeFewer joins, faster readsMore storage, update cost
Materialized viewPre-computed readsRefresh latency, staleness
Index moreFaster lookupsSlower writes, more disk
Columnar storageCompression, scan speedSlow point updates

9. Managing Update Complexity

RiskMitigation
Write amplificationBatch updates; use queues
Lost updatesUse transactions or optimistic locking
Cascading writesLimit scope; async via CDC
Race conditionsSerializable isolation or row locks

10. Documenting Denormalization Decisions

DocumentContents
Decision Record (ADR)Context, decision, alternatives, consequences
Source of TruthWhich table is canonical for each fact
Propagation PathHow copies stay in sync
Refresh SLAMax staleness allowed
Reconciliation JobFrequency, query, alert thresholds