DOC: refresh-ma
STATUS: ● PUBLISHED
CONCEPT

REFRESH MATERIALIZED VIEW CONCURRENTLY: How It Works, How to Break It

The incremental refresh, and the one mistake that turns it into a full rewrite.

A materialized view is a stored snapshot of a query. Refreshing it re-runs the query and replaces the contents. CONCURRENTLY is what makes refreshing safe on a live system, and it’s easy to silently defeat.

What CONCURRENTLY actually does

A plain REFRESH MATERIALIZED VIEW takes an ACCESS EXCLUSIVE lock: every reader blocks until the refresh finishes. On a dashboard people are watching, that’s a visible stall.

REFRESH MATERIALIZED VIEW CONCURRENTLY instead:

  1. builds the new result in a temporary place,
  2. diffs it against the current rows,
  3. applies only the inserts, updates, and deletes needed to reconcile them.

Readers never block, and if little changed, little work happens. (It requires a UNIQUE index on the view so rows can be matched between old and new.)

How to break it

The diff in step 2 compares rows by value. So anything non-deterministic in the view makes every row look changed, and CONCURRENTLY falls back to rewriting everything:

SELECT
    account_id,
    count(*) AS events,
    NOW() AS refreshed_at   -- different every refresh → every row "changes"
FROM ...

NOW(), clock_timestamp(), random(), anything time- or randomness-dependent: each one poisons the diff. You keep the lock-free behavior but lose the incremental behavior, so the refresh is as expensive as a full rebuild while looking like it should be cheap.

The rule

Keep materialized views pure: deterministic aggregations of their inputs, nothing else. If you need to track when the view was refreshed, store that in a separate one-row-per-view table updated alongside the refresh, never as a column inside the view.

Takeaway

CONCURRENTLY gives you lock-free, incremental refreshes, but only for deterministic views. The moment a value changes every run, you’re rewriting the whole table on every refresh. Observability about the view belongs outside the view.

@frogwebp brand mark
ANTHONY PENA · @FROGWEBP
I build data systems and write about everything around them, the architecture, the failures, what each one teaches me. Documenting in public since 2021: the process, not just the result.

// NEWSLETTER — THE BUILD LOG SIGNAL

When I ship something or learn something worth keeping, it lands here first — build logs, concepts, and the honest process behind them. Come along; no spam, leave anytime.