Materialized views in Materialize are views whose results are persisted in durable storage and incrementally updated as new data arrives. Unlike traditional databases that recompute materialized views on a schedule, Materialize maintains them continuously using differential dataflow.
CREATE MATERIALIZED VIEW sales_summary ASSELECT product_id, COUNT(*) as order_count, SUM(quantity) as total_quantity, SUM(amount) as total_revenueFROM ordersGROUP BY product_id;
Materialized views can be queried from any cluster, making them ideal for cross-cluster data sharing and multi-tier architectures.
Computation: Recomputed from scratch on every queryStorage: No persistent storageUpdates: N/A (computed on demand)Clusters: Not associated with a clusterBest for: Simple queries, rarely accessed data
Materialized Views
Computation: Incrementally maintainedStorage: Results in durable storageUpdates: Continuous, as data arrivesClusters: Associated with a compute clusterBest for: Complex queries, frequent access, cross-cluster sharing
Materialized views compile to dataflow graphs executed by the compute layer:
CREATE MATERIALIZED VIEW daily_revenue ASSELECT DATE_TRUNC('day', order_time) as day, SUM(amount) as revenueFROM ordersWHERE status = 'completed'GROUP BY DATE_TRUNC('day', order_time);
CREATE MATERIALIZED VIEW monthly_reportWITH (REFRESH AT '2024-01-01 00:00:00') ASSELECT DATE_TRUNC('month', order_time) as month, SUM(amount) as revenueFROM ordersGROUP BY month;
CREATE MATERIALIZED VIEW hourly_summaryWITH (REFRESH EVERY '1 hour') ASSELECT DATE_TRUNC('hour', event_time) as hour, COUNT(*) as event_countFROM eventsGROUP BY hour;
Non-incremental refresh strategies recompute the entire view on each refresh. Use sparingly for cold/archival data where staleness is acceptable.
Query performance improves by indexing materialized views:
CREATE MATERIALIZED VIEW customer_summary ASSELECT customer_id, COUNT(*) as order_count, SUM(total) as lifetime_valueFROM ordersGROUP BY customer_id;-- Create an index in the serving clusterCREATE INDEX idx_customer IN CLUSTER serving_clusterON customer_summary (customer_id);
Without index: Query reads from storage (still fast, sub-second)With index: Query reads from memory (microseconds, especially for point lookups)
Indexes are local to a cluster. Create indexes in each cluster that queries the materialized view frequently.
Build complex pipelines by stacking materialized views:
-- Stage 1: Filter and cleanCREATE MATERIALIZED VIEW clean_orders ASSELECT order_id, customer_id, order_time, amountFROM raw_ordersWHERE amount > 0 AND customer_id IS NOT NULL;-- Stage 2: Aggregate by customerCREATE MATERIALIZED VIEW customer_totals ASSELECT customer_id, COUNT(*) as order_count, SUM(amount) as total_spentFROM clean_ordersGROUP BY customer_id;-- Stage 3: Classify customersCREATE MATERIALIZED VIEW customer_segments ASSELECT customer_id, CASE WHEN total_spent > 10000 THEN 'VIP' WHEN total_spent > 1000 THEN 'Premium' ELSE 'Standard' END as segmentFROM customer_totals;
Materialize optimizes stacked views by:
Sharing arrangements between views when possible
Only storing differences at each stage
Propagating changes incrementally through the pipeline
Predicate pushdown: Filters applied as early as possible
Join reordering: Optimal join order selected automatically
Decorrelation: Subqueries converted to efficient joins
Aggregate fusion: Multiple aggregations combined when possible
-- Before optimizationCREATE MATERIALIZED VIEW example ASSELECT *FROM ( SELECT customer_id, SUM(amount) as total FROM orders GROUP BY customer_id) subqWHERE total > 1000;-- After optimization (conceptual)CREATE MATERIALIZED VIEW example ASSELECT customer_id, SUM(amount) as totalFROM ordersGROUP BY customer_idHAVING SUM(amount) > 1000; -- Filter pushed into aggregation
-- View materialized view sizeSELECT mz_views.name, pg_size_pretty(SUM(mz_arrangement_sizes.size)) as total_sizeFROM mz_materialized_viewsJOIN mz_views ON mz_materialized_views.id = mz_views.idJOIN mz_arrangement_sizes ON mz_views.id = mz_arrangement_sizes.object_idGROUP BY mz_views.name;-- Check hydration statusSELECT name, status FROM mz_internal.mz_materialized_view_refreshes;-- Monitor freshnessSELECT object_id, lagFROM mz_internal.mz_wallclock_global_lagWHERE object_id IN (SELECT id FROM mz_materialized_views);