Blog

Databend Incremental Materialized Views - Change Tracking, Incremental Refresh, and Consistent Reads

avatarsundy-liSep 2, 2026
Databend Incremental Materialized Views - Change Tracking, Incremental Refresh, and Consistent Reads

A Refresh May Lag; Query Results Must Not

A materialized view (MV) precomputes and persists the result of a frequently executed query. The additional storage reduces query latency, repeated computation, and the amount of source data scanned.

Persisting a result is the easy part. A production implementation must also answer harder questions:

  • How should the result be maintained while the source table continues to change?

  • Can refresh work run without delaying source-table writes?

  • What should a query return when the persisted result is behind the source?

  • How should aggregate state, physical layout, and storage maintenance evolve independently?

Databend's new materialized view design stores the precomputed result in an independent, read-only FUSE table. Change Tracking identifies source-table changes since the last refresh, while a checkpoint records the source position already consumed by the MV. The refresh path and the consistency path are deliberately separated: background maintenance may lag, but the read path selects a plan that still produces the correct result.

That plan may read the persisted MV directly, merge an unmaterialized delta at query time, or bypass the MV and execute against the source table. The central design principle is straightforward: delayed maintenance may reduce performance, but it must not silently expose an outdated answer.

Materialized View Maintenance Models and Their Trade-offs

Materialized view implementations make two related decisions. The first is when to maintain the result: synchronously in the write transaction or asynchronously in the background. The second is how much to recompute: only the affected data or the complete defining query.

These choices produce several common maintenance models. A database may support more than one of them.

Maintenance modelTypical implementationFreshnessEffect on writesCost and fit
Synchronous incremental maintenanceUpdate derived results inside the source write transaction; common in insert-triggered or write-driven MVsCurrent immediately after commitWrite amplification, lock contention, and MV failures enter the write path; complex aggregation can increase write latencyBest for lighter writes, simple derivations, and workloads that prioritize minimum read latency
Asynchronous full refreshRe-execute the defining query on a schedule or on demand, then atomically replace the resultMay be stale between refreshes unless the system recomputes from the source at read timeNormal writes usually continue, but refresh scans and rewrites the full datasetSimple semantics; appropriate for small or medium tables, infrequent refreshes, or aggregate workloads with many updates and deletes
Asynchronous incremental refreshConsume only a change interval from a log, Stream, CDC feed, or Snapshot differenceDepends on scheduling and requires an explicit consistency strategyWrites retain trackable changes but do not normally wait for MV computationWell suited to append-only fact and event tables; requires reliable change semantics and checkpoints
Asynchronous incremental refresh with query compensationPersist processed history asynchronously and merge the unprocessed delta during reads; fall back to the source when compensation is unsafeCan remain current even when refresh lagsKeeps refresh work out of the write transaction, with additional read-time work when the MV is behindUseful when both ingestion throughput and correct, current analytical results matter

Databend uses asynchronous maintenance and chooses between incremental refresh and full rebuild according to the changes that actually occurred. At query time, it selects a Fresh, Hybrid, or Live Fallback plan according to the MV's materialization progress.

Design questionSynchronous maintenanceAsynchronous maintenanceDatabend's approach
Does MV maintenance affect writes?Yes. Derived computation is part of the source commit path.The source write generally does not wait for MV refresh.A refresh pins one source Snapshot. New writes continue committing to newer Snapshots.
Does refresh always scan the full table?Not necessarily, although complex changes can make maintenance expensive.It may use either full or incremental refresh.The initial refresh is full. Append-only changes are incremental. An aggregate MV is fully rebuilt after an
UPDATE
or
DELETE
.
What happens when refresh falls behind?Refresh lag is generally not a separate state.The database may return an old result, compensate, or read from the source.Fresh reads the MV, Hybrid applies read fix, and all unsafe cases fall back to the source.
How are complex aggregates maintained?Maintain state during writes or restrict supported operators.Recompute when necessary, with cost increasing with data size.Persist mergeable aggregate states; use a full rebuild when a change cannot be safely reversed.

An Independent Storage Model for Materialized Results

A Databend materialized view is not an index attached to source-table Blocks. It is a specialized, read-only FUSE table with

engine = MATERIALIZED_VIEW
. It has its own Table ID,
TableMeta
, Snapshot, Segments, Blocks, Cluster Key, and storage settings.

This separation gives the MV an independent refresh position and allows its physical layout to follow its own query pattern rather than the ingestion pattern of the source table.

From Aggregating Indexes to Standalone Materialized Views

Databend's earlier Aggregating Index model behaves like a Block-level projection that moves with the source table. Both designs can avoid repeated computation, but they differ in storage ownership and lifecycle.

DimensionAggregating IndexNew materialized view
Storage ownershipAttached to source-table BlocksIndependent FUSE storage
Physical layoutConstrained by source Blocks and source-table maintenanceIndependent Cluster Key, Blocks, and maintenance strategy
Query accessPrimarily selected transparently by the optimizerCan be queried directly or selected through query rewrite
Refresh progressNo natural independent consumption endpointMaintains a source checkpoint and its own Snapshot
Source compact/reclusterA changed source Block usually requires its corresponding index to be rebuiltMV storage can be compacted and reclustered independently

Metadata and Transaction Boundaries

The system keeps three categories of metadata to describe the physical result, the view definition, and the dependency on the source table.

MetadataKey contentsPurpose
TableMeta(mv_id)
Physical Schema, MV Snapshot, source table ID and sequence, source Snapshot locationDescribes the independently stored table and its refresh endpoint
MVDefinition(mv_id)
Original query, rewritten Physical Query, Logical SchemaStores the larger definition separately to avoid write amplification from frequent
TableMeta
updates
SourceTableMV(source_id, mv_id)
Source-to-MV binding and generationMaintains the dependency between the source table and the MV

Creating, replacing, or dropping an MV updates the table metadata, MV definition, and source dependency in one Meta transaction. Creation also enables Change Tracking on the source table atomically. There is therefore no intermediate state in which the MV has been published but subsequent source changes cannot yet be tracked.

From Refresh to Read: Maintaining Correct Results

Refresh progress, persisted data, and query planning form one consistency mechanism. The refresh path consumes source changes through Change Tracking and advances a checkpoint. The read path compares the checkpointed source Snapshot with the source table's current Snapshot to decide whether the stored result can be used as-is.

Change Tracking and Checkpoints

Databend reuses the change-table semantics already provided by Streams instead of implementing a separate CDC mechanism for materialized views. A refresh reads the changes between two consumption endpoints. Each change includes internal columns such as:

Internal columnMeaning
change$action
The change operation, such as
INSERT
or
DELETE
change$is_update
Whether the record is part of an update
change$row_id
The stable identity of the source row

After a successful refresh, the materialized result and its checkpoint are committed in the same transaction:

materialized_view_source_table_seq
materialized_view_source_snapshot_location

Atomic commit prevents two invalid states: persisted MV data without a corresponding checkpoint advance, and an advanced checkpoint without the data it claims has been materialized.

The checkpoint still advances when all rows in a change batch are filtered out by the MV's

WHERE
clause. Otherwise, a later refresh would consume the same interval again despite there being no qualifying result rows to write.

Refresh Strategy Follows the Actual Change Set

At the beginning of a refresh, Databend locks the MV, reloads its definition, and pins the source Snapshot for that run. Writes that arrive during the refresh continue committing to newer source Snapshots and are left for a later refresh.

The changes between the previous checkpoint and the pinned Snapshot determine the maintenance strategy.

Source endpoint stateRefresh operationRationale
Initial refreshExecute the complete query through
INSERT OVERWRITE
No starting checkpoint exists, so Databend must establish the initial result.
INSERT
only
Run the Physical MV Query on the delta, then use
INSERT INTO
Append-only changes can safely add result rows or mergeable aggregate states.
UPDATE
or
DELETE
in a non-aggregate MV
Internally apply
MERGE
using
_mv_source_row_id
Stable source-row identity maps a deletion or update to the correct materialized row.
UPDATE
or
DELETE
in an aggregate MV
Rebuild the complete result with
INSERT OVERWRITE
States such as
MIN
MAX
and approximate distinct counts cannot be reversed correctly in the general case.
Unchanged SnapshotProcess checkpoint semantics onlyThere is no new data to materialize.
Empty source tableUse an empty endpointAvoid unnecessary scanning.

For an aggregate MV, an append-only refresh writes aggregate states, not finalized scalar results. A Group Key may temporarily have states in multiple Blocks. Functions such as

sum_merge
,
count_merge
, and
min_merge
combine those states during reads to produce the correct value.

Three Read Paths: Fresh, Hybrid, and Live Fallback

When a query accesses an MV, the Binder compares the source Snapshot recorded in the MV checkpoint with the current source Snapshot.

Read pathConditionQuery planCorrectness and cost
FreshThe two Snapshots match.Scan the MV's Physical Storage; merge states for an aggregate MV.Lowest cost, with a current result.
Hybrid, or read fixThe MV is behind, and all changes after its checkpoint are append-only.Persisted MV Storage UNION ALL Physical MV Query(Source Delta), followed by state merging when required.Completes the result at query time without changing the MV or waiting for refresh.
Live FallbackThe initial refresh has not completed; the delta contains an update or deletion; a required historical Snapshot has been garbage-collected; or the source binding is invalid.Ignore persisted MV storage and execute the original logical query against the source table.More expensive, but never returns a stale or incorrect result.

Read fix is query-plan compensation, not an automatic background refresh. This distinction matters operationally: a delayed refresh can increase query work, but it does not change the answer returned to the user.

Mergeable Aggregate State and Independent Storage Maintenance

Incremental materialization must do more than append each new result. It must preserve mergeable state across batches and prevent state rows and small files from accumulating without bound.

Logical Schema Versus Physical Schema

The columns visible to a user do not always match the columns stored physically. Consider

avg(amount)
: storing only the average of each incremental batch would make it impossible to calculate the correct average across batches. Databend instead persists the components needed to merge the state.

Layer
avg(amount)
example
Role
Logical Schema
average_amount
The result column exposed to the user
Physical Schema
sum_state(amount)
count_state(amount)
Mergeable state persisted across incremental batches and Blocks
Read projection
sum_merge(sum_state) / count_merge(count_state)
Converts physical state into the logical result

Initial support covers common aggregates including

sum
,
min
,
max
,
avg
,
count
, and
approx_count_distinct
. The planner rewrites
avg
as
sum_state + count_state
. A non-aggregate MV also stores
_mv_source_row_id
so that the refresh path can locate the materialized row corresponding to a source record.

Reaggregate, Compact, and Recluster

Repeated append-only refreshes can create multiple state rows for the same Group Key. The query result remains correct because the read path merges those states, but the amount of merge input and the degree of file fragmentation increase over time.

Before compact or recluster writes a new Block, Databend performs reaggregation: states with the same Group Key inside that output Block are combined.

OperationWhat it doesWhat it does not do
ReaggregateGroups by business columns and merges duplicate Aggregate State within the newly written BlockDoes not guarantee one global convergence pass across every Block in the MV
OPTIMIZE TABLE mv COMPACT
Reorganizes MV Blocks and triggers reaggregation on output BlocksDoes not allow users to change the MV's logical data
ALTER MATERIALIZED VIEW mv RECLUSTER [FINAL]
Reorganizes data according to the MV's own Cluster KeyDoes not inherit the clustering layout of the source table

An MV that contains

GROUP BY
without aggregate functions is also deduplicated during maintenance. A non-aggregate MV retains
_mv_source_row_id
, so two source records with identical business columns are not incorrectly collapsed during compaction.

Query Rewrite and a Complete Usage Example

Applications may query an MV directly or continue querying the source table. For source queries, the optimizer compares output expressions, filters, Group Keys, aggregate functions, aggregation granularity, and required columns. When the query matches, the optimizer rewrites it to an MV read plan. That rewritten plan still chooses Fresh, Hybrid, or Live Fallback according to the current data state.

The following MV summarizes paid orders by customer:

CREATE MATERIALIZED VIEW paid_orders_by_customer
(customer_id, total_amount, order_count, average_amount)
CLUSTER BY (customer_id)
AS
SELECT
customer_id,
sum(amount),
count(*),
avg(amount)
FROM orders
WHERE paid
GROUP BY customer_id;

Creation publishes the definition but does not populate the MV immediately. Before the first refresh, a direct query uses Live Fallback. The answer is correct, but the persisted MV provides no acceleration yet.

After the initial refresh, append-only changes can be consumed incrementally. Storage maintenance can be scheduled as the number of incremental batches grows.

-- Build the initial materialized result.
REFRESH MATERIALIZED VIEW paid_orders_by_customer;

-- If subsequent changes are inserts only, consume the delta.
REFRESH MATERIALIZED VIEW paid_orders_by_customer;

-- Reorganize storage as incremental batches accumulate.
OPTIMIZE TABLE paid_orders_by_customer COMPACT;
ALTER MATERIALIZED VIEW paid_orders_by_customer RECLUSTER FINAL;

Where Incremental Materialized Views Fit

The value of an incremental MV depends on the source change pattern, the defining query, and the workload's refresh and consistency requirements. The current Databend implementation is strongest for single-table analytical workloads whose changes can usually be consumed as append-only deltas. It is not intended to replace every continuous data transformation pipeline.

Logs, events, behavioral data, and append-only fact tables. Historical records in these datasets are rarely updated or deleted. Each refresh can process only the unconsumed interval instead of rerunning the full source query.

Frequent

GROUP BY
queries, metric dashboards, and customer or regional summaries. Stable aggregation patterns can reuse persisted Aggregate State. When a source query matches an MV, transparent optimizer rewrite reduces detailed-row scans and repeated computation.

Write-throughput-sensitive systems that cannot accept stale answers. Asynchronous refresh keeps derived computation out of the source write transaction. If the MV falls behind and the delta is safe to merge, read fix supplies the missing rows during the query. If it is not safe, Databend reads from the source.

Workloads in which ingestion and analytics need different physical layouts. The source may be organized around ingestion time, while the MV uses a customer or business dimension as its Cluster Key. Compact and recluster operations can then maintain the two layouts independently.

What to Consider Before Adoption

Definitions currently focus on a single persistent FUSE table. An MV can currently be created from one persistent FUSE table in the

default
Catalog, using a query shaped like
SELECT ... FROM ... [WHERE ...] [GROUP BY ...]
. Multi-table queries,
JOIN
, subqueries, set operations, window functions, and nondeterministic functions are not yet supported. Workloads that depend on wide-table construction or continuous multi-table aggregation still require another transformation and orchestration path.

Updates and deletes can make aggregate refresh expensive. A non-aggregate MV can project an

UPDATE
or
DELETE
accurately through
_mv_source_row_id
. An aggregate MV currently prioritizes correctness by performing a full rebuild after either operation; partition-level incremental recomputation is not yet supported. Large aggregate tables with frequent mutations require careful refresh-cost evaluation.

Refresh scheduling must be configured explicitly. A Databend Task can run

REFRESH MATERIALIZED VIEW
on a schedule. Automatically generated refresh tasks are part of the future Databend Cloud direction. Read fix must not be treated as refresh: only an append-only delta can use the Hybrid path, and unsafe compensation returns to the source query.

Incremental processing depends on continuous Change Tracking history. If a historical Snapshot required for incremental refresh or read compensation has already been garbage-collected, Databend can no longer reconstruct that delta and uses Live Fallback. Snapshot retention, refresh frequency, ingestion rate, and query requirements therefore need to be planned together.

Materialized views are read-only objects. Ordinary

INSERT
,
UPDATE
, and
DELETE
statements are rejected. Users cannot mutate the materialized result directly, and physical maintenance is constrained to dedicated operations. This protects the consistency between persisted data and its checkpoint, but it also means an MV cannot be used as a regular application table.

Correctness Determines the Value of Incremental Maintenance

Databend's new materialized view is not merely a query-result cache. It forms an end-to-end incremental computation path: Change Tracking identifies source changes, a checkpoint records the consumption position, the Physical Schema persists mergeable state, refresh advances data and position atomically, and read fix preserves consistent reads while maintenance is asynchronous.

This model is particularly effective for continuously ingested, append-heavy, single-table workloads that require stable analytical answers. Multi-table dependencies, frequent updates or deletes, and workloads that expect fully automatic scheduling require a different pipeline today—or a careful assessment of full-rebuild cost and future Dynamic Table capabilities.

The relevant engineering question is therefore not only whether an MV avoids computation. It is whether the refresh model, mutation pattern, and storage-maintenance cost remain favorable over the full lifecycle of the workload.

Incremental materialized views were introduced and updated in Databend Enterprise Edition after v1.2.934.

Share this post

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!