Materialized View
A materialized view is a pre-computed, stored result of a query, refreshed periodically or on change. Unlike a regular SQL view (which is just a saved query re-executed on read), a materialized view holds actual data — so reads are fast (a single table scan or key lookup instead of a complex join) but writes bear the cost of keeping the view up to date. The pattern trades write cost and staleness for read speed and simplicity, and is the workhorse pattern behind every dashboard, every search index, and every read-optimized projection in a CQRS system.
How it works
A regular SQL view is a saved query — every time you read it, the database re-runs the query. A materialized view persists the query's result as actual rows in a table, so reads are fast (no re-computation). The trade-off: the view must be refreshed when underlying data changes.
Three refresh strategies:
1. On-demand (manual) — refresh when you say so. Used for nightly batch refreshes: at 2 AM, drop and rebuild the view from current data. Reads during the day are fast and consistent with the 2 AM snapshot. Stale, but predictably stale. Common for daily reports.
2. Scheduled (periodic) — refresh every N minutes/hours. Trade staleness for freshness. The view is at most N minutes behind. Used for near-real-time dashboards.
3. Incremental / event-driven — refresh on every write, or on every event. Most fresh, most expensive. Two flavors:
- Synchronous: the write transaction also updates the view. Strongly consistent, but slower writes and harder to recover from failure.
- Asynchronous: the write emits an event; a consumer updates the view. Eventually consistent, but decouples write and view latencies.
The right strategy depends on the query's tolerance for staleness and the write volume. A nightly sales report can tolerate 24-hour staleness; a leaderboard during a live game needs near-real-time.
Materialized views can live in:
- The same database — native materialized views in PostgreSQL, Oracle; refreshed via
REFRESH MATERIALIZED VIEW. - A different store — Elasticsearch index as a view of Postgres data; Redis cache as a view of a row; a denormalized table in a data warehouse as a view of operational data.
- Multiple stores — one source, many views, each optimized for its read pattern (search, analytics, caching).
Choosing a refresh strategy is the core decision. Trade-offs:
Full rebuild — drop and recreate the view from scratch. Simple, correct, but expensive. Use when:
- The view is small enough to rebuild in minutes.
- Underlying data changes so much that incremental is barely cheaper.
- The refresh runs nightly when traffic is low.
Incremental refresh — only update the rows that changed. Much cheaper for large views with small change sets. Use when:
- The view is large (millions of rows) but only a small fraction changes per refresh.
- You can identify which rows changed (via updated_at timestamp, CDC, or event sourcing).
- You can correctly handle deletes (often the hard part).
Event-driven / streaming — every write emits an event; a consumer updates the view. Lowest staleness, highest write amplification. Use when:
- Near-real-time freshness is required (seconds, not minutes).
- The view is a projection of an event-sourced system.
- You're already operating a streaming pipeline (Kafka, Flink).
Synchronous update in the write transaction — the write transaction also updates the view. Strongly consistent reads, slower writes, harder recovery. Use when:
- Reads must be strongly consistent (no staleness acceptable).
- Write volume is low enough that the extra cost is negligible.
- The view fits in the same database (so the update is in the same transaction).
A subtle but important point: materialized views have schemas, and schemas evolve. When the view's definition changes, you may need to rebuild it from scratch — which can take hours for large views. Plan for this: version the view, rebuild in the background, switch over atomically.
These are related but distinct. A cache stores the result of a query with no transformation — same shape, just faster. A materialized view stores a transformed, often aggregated, often denormalized version of the data, optimized for a specific query. A cache is invalidated on write (or expires on TTL); a materialized view is refreshed on a schedule or via events. Caches are usually per-row; materialized views are usually per-query or per-aggregate. Both trade freshness for read speed. In practice, the line blurs: a Redis cache of a denormalized user feed is both a cache (fast lookup) and a materialized view (transformed shape).
When to use materialized views:
- Aggregations — sum, count, average over many rows. Pre-compute once, read many times. Dashboards, reports, leaderboards.
- Denormalized reads — joins that are expensive at read time but cheap when pre-computed. ‘Get user with all their orders’ as one row instead of a join.
- Different query patterns — relational store for writes, search index for full-text, graph for relationships. Each is a view optimized for its query.
- Read-heavy workloads — 100:1 read:write ratio justifies the write amplification.
- CQRS read models — projections of the write model, each shaped for its query.
When NOT to use materialized views:
- Simple CRUD with low read volume — the overhead of refresh isn't worth it.
- Strongly consistent reads required — materialized views are eventually consistent by nature.
- Low write volume AND low read volume — just use the source directly.
- High write volume with low read volume — the refresh cost dominates; don't materialize.
- When the view is rarely queried — pay for refresh, never read. Delete it.
A common anti-pattern: materializing every possible query. Each materialized view costs write amplification, storage, and operational complexity. Materialize the queries that actually matter — the slow ones, the hot ones, the ones on the critical path.
An analytics dashboard queries `SELECT SUM(revenue) FROM orders WHERE date = TODAY` on a table of 100M rows. Reads are slow. Which pattern is most appropriate?
Pick one answer.
You have a materialized view refreshed nightly at 2 AM. A user updates their data at 10 AM and then queries their dashboard. What will they see, and what are the options?
Pick one answer.
Engineering mental model
Mental model. Think of Materialized View as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Materialized View mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing Materialized View, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
// Pseudocode
request = receive()
result = materialized_view(request)
return result
// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?Back-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: Materialized View
Change the variables below and predict what breaks first in Materialized View. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on Materialized View, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using Materialized View. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using Materialized View?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses Materialized View, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose Materialized View, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
A useful engineering lens for Materialized View: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.
Numerical sanity check
Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
Imagine the simplest version of a system using Materialized View. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?
Pick one answer.
What you gain, what you pay
- +Reads are fast — pre-computed, often a single-row lookup.
- +Read patterns can be optimized independently — denormalized, indexed, aggregated as needed.
- +Read tier scales independently of the write tier.
- +Multiple views support multiple query patterns from one source.
- +Reduces load on the source of truth — expensive queries run at refresh time, not on every read.
- −Writes bear the cost of keeping the view fresh — write amplification.
- −Staleness — the view lags the source, by seconds to hours depending on refresh strategy.
- −Operational complexity — now you operate the source AND the view(s) AND the refresh pipeline.
- −Schema evolution is harder — changing the view definition requires a rebuild, which can take hours.
- −Storage cost — the view duplicates data.
How this breaks in production
- Refresh falls behind under high write volume — view becomes increasingly stale.
- Refresh job fails — view is silently stale, reads return wrong data; needs monitoring.
- Incremental refresh logic has a bug — view drifts from source; only full rebuild fixes it.
- Schema mismatch — view's schema lags the source's after a migration.
- Rebuild takes too long — large views can take hours to rebuild, blocking schema changes.
- View becomes a SPOF — if the view store (Elasticsearch, Redis) dies, reads fail even though the source is up.
Don't fall into these traps
- •Materializing every query — each view has cost; materialize only the ones that matter.
- •Choosing the wrong refresh strategy — too infrequent (stale) or too frequent (expensive).
- •Not monitoring refresh lag — silent staleness causes user-visible bugs.
- •Not having a rebuild strategy — when the view drifts or the schema changes, you must be able to rebuild from scratch.
- •Treating materialized views as strongly consistent — they're eventually consistent by design.
- •Forgetting read-your-writes — users see stale data after their own writes; route critical reads to the source.
Real systems using this
How real systems implement this
- PostgreSQL materialized views — Native support: `CREATE MATERIALIZED VIEW ... WITH DATA` persists the query result. `REFRESH MATERIALIZED VIEW` re-runs the query and replaces the data. `REFRESH ... CONCURRENTLY` allows reads during refresh. Used for pre-computing aggregates and denormalized reads.
- Snowflake / BigQuery materialized views — Cloud data warehouses support materialized views with automatic incremental refresh — when source data changes, only the affected view rows are updated. Used heavily in BI dashboards where the same aggregate queries run repeatedly.
- Elasticsearch indexes — An Elasticsearch index is a materialized view of source data, optimized for full-text and structured search. Source data (from Postgres, Kafka, etc.) is indexed into Elasticsearch; reads query the index, not the source. Refresh strategy is near-real-time (1-second default refresh interval).
Practice saying it out loud
- Q1What is a materialized view, and how does it differ from a regular view or a cache?
- Q2Your analytics dashboard is slow because it aggregates 100M rows per query. How do you fix it?
- Q3What are the refresh strategies for a materialized view, and how do you choose?
- Q4After a user updates their data, they see stale data on their dashboard. What's happening, and how do you fix it?
- Q5When should you NOT use a materialized view? Give concrete criteria.
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
CQRS