Skip to content

Repository files navigation

OpenIVM

A DuckDB extension for incremental view maintenance (IVM). Create materialized views pipelines with standard SQL, then refresh them incrementally — without recomputing the entire query.

Based on the OpenIVM paper (SIGMOD 2024).

Quick start

LOAD 'openivm';
-- Create a base table and a materialized viewCREATETABLEsales (region VARCHAR, product VARCHAR, amount INT);
INSERT INTO sales VALUES ('US', 'Widget', 100), ('EU', 'Gadget', 200);
CREATE OR REPLACE MATERIALIZED VIEW regional_totals REFRESH EVERY '5 minutes'ASSELECT region, SUM(amount) AS total, COUNT(*) AS cnt FROM sales GROUP BY region;
-- Insert new dataINSERT INTO sales VALUES ('US', 'Bolt', 50), ('JP', 'Gear', 300);
-- Refresh manually at any time
PRAGMA refresh('regional_totals');
SELECT*FROM regional_totals ORDER BY region;
-- EU | 200 | 1-- JP | 300 | 1-- US | 150 | 2

Views with REFRESH EVERY are maintained automatically by a background daemon. See automatic refresh.

Base table schema changes (ADD, DROP, RENAME COLUMN) are propagated by OpenIVM. Delta tables are synced, referenced renames update MV metadata, and referenced drops are blocked with an error.

Data pipelines and DuckLake

Materialized views can be stacked into pipelines, including over DuckLake tables. DuckLake's snapshot-based time travel enables delta computation through native change tracking, avoiding storage duplication. See DuckLake IVM integration.

INSTALL ducklake;
LOAD ducklake;
ATTACH ':memory:'AS dl (TYPE ducklake);
CREATETABLEdl.orders (id INT, product VARCHAR, region VARCHAR, amount INT);
INSERT INTOdl.ordersVALUES (1, 'Widget', 'US', 500), (2, 'Gadget', 'EU', 200), (3, 'Widget', 'EU', 800);
-- First MV: aggregate by product
CREATE MATERIALIZED VIEW dl.product_totals REFRESH EVERY '5 minutes'ASSELECT product, SUM(amount) AS total, COUNT(*) AS cnt FROMdl.ordersGROUP BY product;
-- Second MV: built on top of the first
CREATE MATERIALIZED VIEW dl.top_products REFRESH EVERY '10 minutes'ASSELECT product, total FROMdl.product_totalsWHERE total >1000;
-- Cascade modes (controls automatic refresh propagation):SET openivm_cascade_refresh ='downstream'; -- default: refreshing product_totals also refreshes top_productsSET openivm_cascade_refresh ='upstream'; -- refreshing top_products first refreshes product_totalsSET openivm_cascade_refresh ='both'; -- refresh in both directionsSET openivm_cascade_refresh ='off'; -- no cascade, each view refreshes independently

Note: without cascading refresh, views refreshing independently may see stale upstream data — results are consistent but not fresh until the next ordered refresh. See pipelines.

Supported operators

MVs can be created using any SQL construct. Unsupported operators automatically fall back to full refresh.

OperatorStrategyDocumentation
SELECT ... FROM, WHERE, expressionsIncrementalProjection & filter
GROUP BY + SUM, COUNT, AVGIncrementalGrouped aggregates
STDDEV, VARIANCE (all variants)IncrementalGrouped aggregates
MIN, MAXIncremental (insert-only) / group-recomputeGrouped aggregates
HAVINGIncrementalGrouped aggregates
Ungrouped aggregatesIncrementalUngrouped aggregates
INNER JOIN, CROSS JOIN, arbitrary join predicatesIncrementalInner join
LEFT JOIN, RIGHT JOINIncrementalLeft join
FULL OUTER JOINIncremental (MERGE + recompute)Full outer join
SEMI JOIN, ANTI JOIN, EXISTS, NOT EXISTSAux-state incremental for supported projection shapesSemi & anti join
UNION ALLIncrementalUnion all
DISTINCTIncrementalDistinct
Window functions (ROW_NUMBER, RANK, etc.)Partition-level recomputeWindow functions
LIST aggregatesIncrementalList aggregates
WITH (CTEs), decorrelated subqueries, scalar correlated subqueriesIncremental when the lowered plan uses supported operators; scalar SINGLE delim shapes use affected-key recomputeCTEs & subqueries

Settings

SettingTypeDefaultDescriptionDocumentation
openivm_cascade_refreshVARCHARdownstreamCascade mode: off, upstream, downstream, bothPipelines
openivm_refresh_modeVARCHARincrementalRefresh strategy: incremental, full, or autoRefresh strategies
openivm_adaptive_refreshBOOLEANfalseEnable adaptive cost model (learned regression)Refresh strategies
openivm_adaptive_backoffBOOLEANtrueAuto-increase refresh interval when refresh takes longer than intervalAutomatic refresh
openivm_disable_daemonBOOLEANfalseDisable the background refresh daemonAutomatic refresh
openivm_skip_empty_deltasBOOLEANtrueSkip refresh work when no pending deltas existEmpty delta skip
openivm_compact_deltasBOOLEANtrueCompact raw delta rows into net Z-set deltas before refreshDelta consolidation
openivm_distinct_aux_stateBOOLEANfalseUse aux-state maintenance for supported inner-DISTINCT-under-aggregate shapesDistinct
openivm_regular_ntermBOOLEANtrueUse N-term telescoping for eligible regular-table inner joins compiled for external enginesInner join
openivm_profile_refreshBOOLEANfalseRecord per-step refresh timings in openivm_refresh_profileAutomatic refresh
openivm_files_pathVARCHARDirectory for compiled SQL reference filesInternals

Pragmas

PragmaDescription
PRAGMA refresh('view_name')Refresh a materialized view
PRAGMA refresh_cost('view_name')Show incremental refresh vs full recompute cost estimate (static + calibrated)
PRAGMA refresh_history('view_name')Show refresh execution history (for learned cost model)
PRAGMA refresh_options(catalog, schema, view_name)Refresh with explicit catalog/schema
PRAGMA refresh_status('view_name')Show refresh interval, last/next refresh, and status

Documentation

  • DuckLake integration — IVM over DuckLake tables with native change tracking
  • Operators — How each SQL operator is incrementalized
  • Refresh — Refresh strategies and view pipelines
  • Optimizations — Delta consolidation, FK pruning, empty-delta skip, indexing
  • Internals — Delta tables, parser, concurrency
  • Limitations — Unsupported operators, known restrictions
  • Build — Building, testing, benchmarks

About

Incremental View Maintenance on DuckDB

Topics

Resources

Stars

42 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages