<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: Materialized Views - Strange Difference in Refresh Performance in Data Engineering</title>
    <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175900#M57070</link>
    <description>&lt;P&gt;Hello &lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/79252"&gt;@johnb1&lt;/a&gt;, I took a look at both internal and external documentation and here is what I found.&lt;/P&gt;
&lt;P&gt;Thanks for posting all the test results; they make this easy to follow. Credit to &lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/250262"&gt;@data_pulse&lt;/a&gt; for the troubleshooting path (event log &lt;CODE&gt;planning_information&lt;/CODE&gt;, then swapping out &lt;CODE&gt;CURRENT_DATE()&lt;/CODE&gt;, then trying &lt;CODE&gt;AUTO&lt;/CODE&gt;). That's what found the real cause.&lt;/P&gt;
&lt;P&gt;You're seeing expected behavior. Incremental doesn't always mean cheaper, and your numbers show why.&lt;/P&gt;
&lt;P&gt;The full refresh is cheap because of pruning. Your two big sources are around 22 TB combined, but the full refresh read 0.17 TB. The date predicate plus partitioning on REGION/YEAR/MONTH/DAY prunes over 99 percent of the data before any join runs. A full recompute here recomputes a small recent window, not 22 TB of history. That's the bar incremental has to clear.&lt;/P&gt;
&lt;P&gt;The incremental refresh is expensive because of the joins. Incremental maintenance has to work out how every changed row propagates through each union, join, and anti-join. For a join, the changed rows on one side get joined against the current state of the other side, and that probe is driven by join keys, not by your date filter. Unless the join condition includes the partition columns, the pruning that saved the full refresh doesn't apply, and with seven-plus joins the big tables get read more than once. 32 TB read is about 1.5 times the raw size of the two big tables, which fits. The changeset is also large relative to that window: one MERGE on one table added about 23 GB of files (&lt;CODE&gt;numTargetBytesAdded&lt;/CODE&gt;), already more than a tenth of what the full refresh reads, and updates count twice on the change side (&lt;CODE&gt;update_preimage&lt;/CODE&gt; plus &lt;CODE&gt;update_postimage&lt;/CODE&gt;), so 128M updates and 169M inserts are closer to 425M change rows. The write side agrees: the full refresh wrote 0.78 TB, which is the whole MV, while the incremental run wrote 1.88 TB, since changes across 219 partitions mean most of the window gets rewritten anyway.&lt;/P&gt;
&lt;P&gt;AUTO got it right. The mv04 log shows the cost model rejected the incremental plan (&lt;CODE&gt;INCREMENTAL_PLAN_REJECTED_BY_COST_MODEL&lt;/CODE&gt;, subtype &lt;CODE&gt;NUM_JOINS_THRESHOLD_EXCEEDED&lt;/CODE&gt;) and chose a full recompute, matching FULL at 55 min. &lt;CODE&gt;REFRESH POLICY INCREMENTAL&lt;/CODE&gt; is documented as an override of the cost model: it runs the incremental plan whenever one is structurally possible. The docs are explicit that a cost-model rejection means the optimizer chose not to incrementalize, not that it can't. An older Community thread with Databricks engineers mentions a configurable join threshold for the Enzyme engine (&lt;CODE&gt;pipelines.enzyme.numberOfJoinsThreshold&lt;/CODE&gt;, default 2 at the time). I can't confirm the current default, and you've already run the experiment showing that forcing incremental loses here.&lt;/P&gt;
&lt;P&gt;&lt;CODE&gt;CURRENT_DATE()&lt;/CODE&gt; was a side issue. Non-deterministic time functions in a WHERE clause are supported for incremental refresh, which is why EXPLAIN reported no issues. They block PARTITION_OVERWRITE specifically. Removing it in mv03 cleared the flag, but partition overwrite still wasn't chosen and the refresh still took 199 min, so I wouldn't restructure around a fixed date. My read, unconfirmed: partition overwrite needs the planner to prove that a change in any input can only touch a bounded set of MV partitions, and your smaller tables and the source MV aren't partition-aligned.&lt;/P&gt;
&lt;P&gt;Recommendations&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Keep &lt;CODE&gt;REFRESH POLICY AUTO&lt;/CODE&gt; (the default). It made the right call, and it'll go incremental on days when the change volume is small. Use &lt;CODE&gt;INCREMENTAL&lt;/CODE&gt; only when you're sure incremental is always cheaper, and &lt;CODE&gt;INCREMENTAL STRICT&lt;/CODE&gt; when an unexpected full refresh should fail loudly.&lt;/LI&gt;
&lt;LI&gt;If you want incremental to win, cut the join count:
&lt;UL&gt;
&lt;LI&gt;Remove unused or redundant joins, including ones hidden in CTEs or upstream views.&lt;/LI&gt;
&lt;LI&gt;Declare &lt;CODE&gt;PRIMARY KEY&lt;/CODE&gt; / &lt;CODE&gt;FOREIGN KEY ... RELY&lt;/CODE&gt; constraints where the data supports them (data_pulse's suggestion too).&lt;/LI&gt;
&lt;LI&gt;Split the MV into a chain: per-source filtering and computed columns in a first layer of MVs with zero or one join each, then join those in a second MV. MVs are a supported incremental source for other MVs, and each stage gets its own eligibility check. Only a test will tell you whether the chain beats 55 minutes.&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI&gt;Check eligibility before deploying with &lt;CODE&gt;EXPLAIN CREATE MATERIALIZED VIEW&lt;/CODE&gt;. It reports structural eligibility, not what the cost model picks at runtime, so keep watching the event log:&lt;/LI&gt;
&lt;/UL&gt;
&lt;PRE&gt;&lt;CODE class="language-sql"&gt;SELECT timestamp, message, details
FROM event_log(TABLE(catalog.schema.your_mv))
WHERE event_type = 'planning_information'
ORDER BY timestamp DESC;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;UL&gt;
&lt;LI&gt;Incremental refresh doesn't need legacy change data feed on the sources; row tracking and deletion vectors are the two features the docs recommend. Your MERGE added 503 change files, so if nothing else consumes CDF on those tables, that's write cost you can drop.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;On GROUP_AGGREGATE with no GROUP BY&lt;/P&gt;
&lt;P&gt;Quick thing to rule out: do your unions use &lt;CODE&gt;UNION&lt;/CODE&gt; or &lt;CODE&gt;UNION ALL&lt;/CODE&gt;? Plain &lt;CODE&gt;UNION&lt;/CODE&gt; implies a DISTINCT, which the Spark optimizer rewrites as an Aggregate over all columns, and that's exactly what GROUP_AGGREGATE maintains. &lt;CODE&gt;EXCEPT&lt;/CODE&gt; does the same (anti-join plus DISTINCT). In EXPLAIN EXTENDED it appears as &lt;CODE&gt;Distinct&lt;/CODE&gt; in the parsed and analyzed plans and only becomes &lt;CODE&gt;Aggregate&lt;/CODE&gt; or &lt;CODE&gt;HashAggregate&lt;/CODE&gt; in the optimized and physical plans, so a text search for "aggregate" can miss it. The support table lists &lt;CODE&gt;UNION ALL&lt;/CODE&gt; specifically. If duplicates can't occur or are acceptable, &lt;CODE&gt;UNION ALL&lt;/CODE&gt; removes the aggregate. If you're already on &lt;CODE&gt;UNION ALL&lt;/CODE&gt; and nothing in the plan explains it, open a Support ticket with the event log and the query, as data_pulse suggested.&lt;/P&gt;
&lt;P&gt;Comparing query profiles&lt;/P&gt;
&lt;P&gt;Open Query History in the workspace sidebar, filter to the run window, and find the REFRESH statements for mv03 and mv04 (or open the pipeline update and click the flow). Compare the scan nodes on the two big tables: files read versus pruned, rows read, and how many times each table appears. MV refresh profiles now also carry incrementalization insights (&lt;CODE&gt;FLOW_FULL_RECOMPUTE&lt;/CODE&gt;) that explain a fallback to full recompute.&lt;/P&gt;
&lt;P&gt;References:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Incremental refresh for materialized views (support table, techniques, refresh policy, optimize section): &lt;A href="https://docs.databricks.com/aws/en/ldp/incremental-refresh" target="_blank"&gt;https://docs.databricks.com/aws/en/ldp/incremental-refresh&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;EXPLAIN CREATE MATERIALIZED VIEW (eligibility versus the cost model, rejection reasons): &lt;A href="https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-explain-materialized-view" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-explain-materialized-view&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;REFRESH POLICY clause: &lt;A href="https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-materialized-view-refresh-policy" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-materialized-view-refresh-policy&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Pipeline event log schema (&lt;CODE&gt;CostModelRejectionSubType&lt;/CODE&gt; values): &lt;A href="https://docs.databricks.com/aws/en/ldp/monitor-event-log-schema" target="_blank"&gt;https://docs.databricks.com/aws/en/ldp/monitor-event-log-schema&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Query profile and the FLOW_FULL_RECOMPUTE insight: &lt;A href="https://docs.databricks.com/aws/en/sql/user/queries/query-profile" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/user/queries/query-profile&lt;/A&gt; and &lt;A href="https://docs.databricks.com/aws/en/sql/user/queries/performance-insights#flow-full-recompute" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/user/queries/performance-insights#flow-full-recompute&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Background on the Enzyme engine and its cost model, from the Databricks team: &lt;A href="https://arxiv.org/pdf/2603.27775" target="_blank"&gt;https://arxiv.org/pdf/2603.27775&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Earlier Community thread on NUM_JOINS_THRESHOLD_EXCEEDED: &lt;A href="https://community.databricks.com/t5/data-engineering/materialized-view-refresh-num-joins-threshold-exceeded/td-p/100761" target="_blank"&gt;https://community.databricks.com/t5/data-engineering/materialized-view-refresh-num-joins-threshold-exceeded/td-p/100761&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;If AUTO settles your refresh times, please mark the thread as solved so others can find it.&lt;/P&gt;
&lt;P&gt;Regards,&lt;BR /&gt;Louis.&lt;/P&gt;</description>
    <pubDate>Sat, 07 Nov 2026 17:56:42 GMT</pubDate>
    <dc:creator>Louis_Frolio</dc:creator>
    <dc:date>2026-11-07T17:56:42Z</dc:date>
    <item>
      <title>Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175011#M56957</link>
      <description>&lt;P&gt;I have built the same materialized view twice with only one difference: mv01 does incremental refresh and mv02 does full refresh.&lt;/P&gt;&lt;P&gt;The mv reads from 7 tables and 1 mv, performs unions, joins, anti-joins as well as filters, and calculates columns on the fly. Two of the source tables are particularly large, one has 168B rows and 12238 GB of total file size and the other one has 52B rows and 9567 GB of total file size. The other source tables are much smaller: btw. 10M and 100M rows and between 0.5 and 20 GB total file size. The source mv has 10M rows and 0.2 GB of total file size. All source tables have row tracking enabled and some of the source tables (including the two large ones) have change data feed enabled.&lt;/P&gt;&lt;P&gt;The refresh of mv01 (incremental) took 210 min, completed 566,966 tasks, read 175B rows and 32.34 TB of data, and wrote 1.88 TB. The refresh of mv02 (full) took 55 min, completed 170,177 tasks, read 30B rows and 0.17 TB of data, and wrote 0.78 TB. The initial refresh for both mv's is comparable to the (full) refresh of mv02.&lt;/P&gt;&lt;P&gt;This observation is completely contrary to what I would expect. Why is the incremental more costly than the full refresh? &lt;SPAN&gt;Shouldn't it be the other way around?&amp;nbsp;&lt;/SPAN&gt;Why is the difference in cost that pronounced? Why is the difference in read data even more pronounced?&lt;/P&gt;</description>
      <pubDate>Fri, 30 Oct 2026 10:29:43 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175011#M56957</guid>
      <dc:creator>johnb1</dc:creator>
      <dc:date>2026-10-30T10:29:43Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175020#M56958</link>
      <description>&lt;P&gt;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/79252"&gt;@johnb1&lt;/a&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;The key check here is which technique mv01 actually used for the 210-minute run.&amp;nbsp;&lt;SPAN&gt;You can verify this from the pipeline UI or query the event logs&lt;/SPAN&gt;:&lt;/P&gt;&lt;LI-CODE lang="markup"&gt;SELECT timestamp, origin.update_id, message, details
FROM event_log(TABLE(catalog.schema.mv01))
WHERE event_type = 'planning_information'
ORDER BY timestamp DESC&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;The logs should show whether it used ROW_BASED, PARTITION_OVERWRITE, FULL_RECOMPUTE&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;etc. The planning details can also reveal which techniques were considered or rejected, along with their estimated costs.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Also, the significant difference in data read (&lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN&gt;32.34 TB incremental vs. 0.17 TB full&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt;) suggests the incremental plan may be doing substantial additional work, potentially around join maintenance or repeated scans of the large source tables&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;Worth checking:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;STRONG&gt;&lt;SPAN&gt;Incrementalizability:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; Try&amp;nbsp;EXPLAIN CREATE MATERIALIZED VIEW .. AS SELECT &lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;using the original MV definition. Check the &lt;/SPAN&gt;&lt;EM&gt;&lt;SPAN&gt;Incremental Update Eligibility&lt;/SPAN&gt;&lt;/EM&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;EM&gt;&lt;SPAN&gt;Detailed Incrementalization Info&lt;/SPAN&gt;&lt;/EM&gt;&lt;SPAN&gt; sections for any unsupported operators or expressions.&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;&lt;SPAN&gt;Change volume:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; How much data changed in the source tables since the previous successful refresh?&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;&lt;SPAN&gt;Query profile:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; Compare scans, shuffle activity, join operators, and spill metrics between both runs to identify where the additional processing occurs.&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;&lt;SPAN&gt;Source configuration:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; Are deletion vectors enabled on the source tables, and is pipeline serverless?&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;&lt;SPAN&gt;Refresh policy:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;Is mv01 using &lt;STRONG&gt;AUTO&lt;/STRONG&gt; or &lt;STRONG&gt;INCREMENTAL&lt;/STRONG&gt;? With AUTO, the optimizer can choose between incremental maintenance and full re-computation based on estimated cost.&lt;/SPAN&gt;&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;If ROW_BASED is confirmed and consistently costlier, you could schedule &lt;STRONG&gt;REFRESH MATERIALIZED VIEW mv01 FULL&lt;/STRONG&gt; as a stopgap, and raise it with Databricks support. It looks like the cost model chose the plan that’s incremental in name only.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 30 Oct 2026 11:43:48 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175020#M56958</guid>
      <dc:creator>data_pulse</dc:creator>
      <dc:date>2026-10-30T11:43:48Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175025#M56959</link>
      <description>&lt;P&gt;I add the information that the mv is partitioned by four columns (the same holds for the largest source tables).&lt;/P&gt;&lt;P&gt;&lt;U&gt;Information from the &lt;EM&gt;details&lt;/EM&gt; field from the event log (according to your proposed query)&lt;/U&gt;&lt;/P&gt;&lt;P&gt;"technique_information":[{"maintenance_type":"MAINTENANCE_TYPE_NO_OP","incrementalization_issues":[{"issue_type":"DATA_HAS_CHANGED","prevent_incrementalization":true}]},{"maintenance_type":"MAINTENANCE_TYPE_PARTITION_OVERWRITE","incrementalization_issues":[{"issue_type":"PLAN_NOT_DETERMINISTIC","prevent_incrementalization":true,"operator_name":"Filter","expression_name":"CurrentDate","plan_not_deterministic_sub_type":"TIME_FUNCTION"}]},{"maintenance_type":"MAINTENANCE_TYPE_COMPLETE_RECOMPUTE","is_chosen":false,"is_applicable":true},{"maintenance_type":"MAINTENANCE_TYPE_GROUP_AGGREGATE","is_chosen":true,"is_applicable":true}],"&lt;/P&gt;&lt;P&gt;&lt;U&gt;Incrementalizability check with&amp;nbsp;&lt;SPAN&gt;EXPLAIN command&lt;/SPAN&gt;&lt;/U&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;== Incremental Update Eligibility == The Materialized View can be incrementally refreshed. == Detailed Incrementalization Info == No issues detected.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;U&gt;Previous changes in largest source table (from&amp;nbsp;&lt;SPAN&gt;&lt;EM&gt;operationMetrics&lt;/EM&gt; field from DESCRIBE HISTORY)&lt;/SPAN&gt;:&lt;/U&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;SPAN class=""&gt;numTargetRowsMatchedUpdated:&amp;nbsp;114011636&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;SPAN&gt;numTargetRowsInserted:&amp;nbsp;94445597&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;&lt;SPAN&gt;numTargetRowsMatchedDeleted: 0&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;numTargetBytesRemoved:&amp;nbsp;14789848976&lt;/LI&gt;&lt;LI&gt;numTargetRowsUpdated:&amp;nbsp;&lt;SPAN&gt;114011636&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;numTargetBytesAdded:&amp;nbsp;&lt;SPAN&gt;23081777848&lt;/SPAN&gt;&lt;/LI&gt;&lt;LI&gt;numTargetChangeFilesAdded: 503&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;&lt;U&gt;Source configuration&lt;/U&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Are deletion vectors enabled on the source tables? -&amp;gt; Yes&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Is pipeline serverless?&amp;nbsp;-&amp;gt; Yes&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Refresh policy:&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;REFRESH&lt;/SPAN&gt; &lt;SPAN&gt;POLICY&lt;/SPAN&gt; &lt;SPAN&gt;INCREMENTAL&lt;/SPAN&gt;&lt;/P&gt;</description>
      <pubDate>Fri, 30 Oct 2026 14:32:48 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175025#M56959</guid>
      <dc:creator>johnb1</dc:creator>
      <dc:date>2026-10-30T14:32:48Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175026#M56960</link>
      <description>&lt;P&gt;Thanks, this is helpful. Two things stand out in the event log:&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;STRONG&gt;PARTITION_OVERWRITE was rejected&lt;/STRONG&gt; because of &lt;STRONG&gt;PLAN_NOT_DETERMINISTIC&lt;/STRONG&gt; (&lt;STRONG&gt;CurrentDate&lt;/STRONG&gt; in a Filter, &lt;STRONG&gt;TIME_FUNCTION&lt;/STRONG&gt;). Given that your MV and large sources are partitioned the same way, that technique looks like a good fit.&amp;nbsp;As a diagnostic experiment, you could test an equivalent MV definition on a copy, with a fixed date in place of&lt;STRONG&gt; current_date()&lt;/STRONG&gt; for the same refresh snapshot. Then check whether &lt;STRONG&gt;PARTITION_OVERWRITE&lt;/STRONG&gt; becomes applicable and whether performance improves. If it does, you could look at making the date filter deterministic, for example by passing the date in as a parameter or moving the filter into a view on top of the MV.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;GROUP_AGGREGATE based Incremental was chosen&lt;/STRONG&gt; over COMPLETE_RECOMPUTE. Does the MV include a&lt;STRONG&gt; GROUP BY&lt;/STRONG&gt; or &lt;STRONG&gt;DISTINCT&lt;/STRONG&gt;? The last MERGE show about 208M rows (~114M updated,&amp;nbsp;~98M Inserted). &lt;STRONG&gt;If those rows spread across most groups or partitions&lt;/STRONG&gt;, maintenance may need to re-read most of the table, which would fit the 175B rows read. &lt;STRONG&gt;Checking how many partitions that MERGE touched would help confirm this.&lt;/STRONG&gt;&lt;/LI&gt;&lt;/OL&gt;</description>
      <pubDate>Fri, 30 Oct 2026 15:21:55 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175026#M56960</guid>
      <dc:creator>data_pulse</dc:creator>
      <dc:date>2026-10-30T15:21:55Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175036#M56964</link>
      <description>&lt;P&gt;Thank you for your inputs and suggestions&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/250262"&gt;@data_pulse&lt;/a&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;The question is if there are any ways to decrease the cost of the incremental refresh considerably, such that it beats the cost of the full refresh in a significant way.&lt;/P&gt;&lt;P&gt;I will test creating a different mv without the CURRENT_DATE() function but a fixed date instead.&lt;/P&gt;&lt;P&gt;Question: So from the event log I can infer that maintenance_type = MAINTENANCE_TYPE_GROUP_AGGREGATE was chosen, but&amp;nbsp;maintenance_type = MAINTENANCE_TYPE_PARTITION_OVERWRITE was not chosen, due to the use of the CURRENT_DATE() function?&lt;/P&gt;&lt;P&gt;No GROUP BY or DISTINCT clause is used in the query.&lt;/P&gt;&lt;P&gt;I don't know how to verify the number of partitions in the large source table affected by the last MERGE. Partition columns are REGION, YEAR, MONTH and DAY, so the new rows should not spread across many partitions. The refresh runs once a day.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 30 Oct 2026 16:44:55 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175036#M56964</guid>
      <dc:creator>johnb1</dc:creator>
      <dc:date>2026-10-30T16:44:55Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175038#M56966</link>
      <description>&lt;P&gt;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/79252"&gt;@johnb1&lt;/a&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;In order to decrease the incremental refresh cost, we have to identify&amp;nbsp;where the additional processing overhead is coming from and that's what we are trying to triage here.&lt;/P&gt;&lt;P&gt;Yes, the event log shows PARTITION_OVERWRITE was blocked by the CurrentDate time function, so your fixed-date test is the right experiment.&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;GROUP_AGGREGATE without GROUP BY &lt;/STRONG&gt;is&amp;nbsp;surprising.&amp;nbsp;It may be a global aggregate, or an aggregate or DISTINCT hidden in a view, CTE or the source MV. EXPLAIN EXTENDED on the query should show whether an Aggregate node is in the plan.&lt;/P&gt;&lt;P&gt;With one refresh a day and a 55-minute full refresh versus 210 minutes incremental, you could try &lt;STRONG&gt;ALTER MATERIALIZED VIEW .. SET REFRESH POLICY AUTO&lt;/STRONG&gt; and see what it picks.&amp;nbsp;&lt;SPAN&gt;This probably allows Databricks to choose between incremental and full re-computation based on estimated cost.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;&lt;SPAN&gt;Identify affected partitions:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; If Change Data Feed (CDF) is enabled on the source, you can check which partitions were affected by the MERGE:&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="markup"&gt;SELECT
    REGION, YEAR, MONTH, DAY,
    _change_type,
    COUNT(*) AS changed_rows
FROM table_changes('catalog.schema.source_table', &amp;lt;merge_version&amp;gt;)
GROUP BY REGION, YEAR, MONTH, DAY, _change_type
ORDER BY changed_rows DESC;&lt;/LI-CODE&gt;&lt;P&gt;Replace &amp;lt;merge_version&amp;gt; with the Delta version on which Merge has been run from DESCRIBE HISTORY on that table.&lt;/P&gt;&lt;P&gt;Also can you compare the Query profiles from Incremental refresh and Full refresh, It could give some leads on whether scan/join/shuffle n spill taking more time.&lt;/P&gt;&lt;P&gt;I know it’s a bit tedious to triage, but hunting down the mole is worth it to get a truly incremental refresh.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 30 Oct 2026 17:20:35 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175038#M56966</guid>
      <dc:creator>data_pulse</dc:creator>
      <dc:date>2026-10-30T17:20:35Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175191#M56968</link>
      <description>&lt;P&gt;I created two more materialized views:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;mv03 without the current_date() function and with REFRESH POLICY INCREMENTAL&lt;/LI&gt;&lt;LI&gt;mv04 with&amp;nbsp;REFRESH POLICY AUTO.&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;The refresh pipeline of mv03 ran 199 min this morning. The refresh pipeline of mv01&amp;nbsp;ran 208 min. The refresh pipelines of mv02 (REFRESH POLICY FULL) and mv04 (REFRESH POLICY AUTO) both ran 55 min.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;U&gt;Planning information from event log of refresh pipeline of mv03&lt;/U&gt;&lt;/P&gt;&lt;P&gt;{"maintenance_type": "MAINTENANCE_TYPE_NO_OP",&lt;BR /&gt;"incrementalization_issues": [{"issue_type": "DATA_HAS_CHANGED", "prevent_incrementalization": true}]},&lt;BR /&gt;{"maintenance_type": "MAINTENANCE_TYPE_COMPLETE_RECOMPUTE", "is_chosen": false, "is_applicable": true},&lt;/P&gt;&lt;P&gt;-&amp;gt; The issue PLAN_NOT_DETERMINISTIC is not detected anymore, but it still chose&amp;nbsp;maintenance_type = MAINTENANCE_TYPE_GROUP_AGGREGATE.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;I ran&amp;nbsp;EXPLAIN EXTENDED on the query and searched for the keyword "aggregate" in the output (which includes the Parsed Logical Plan). No hit.&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;U&gt;Planning information from event of refresh pipelines of mv02 and mv04&lt;/U&gt;&lt;/P&gt;&lt;P&gt;{"maintenance_type": "MAINTENANCE_TYPE_NO_OP",&amp;nbsp;"incrementalization_issues": [{"issue_type": "DATA_HAS_CHANGED", "prevent_incrementalization": true}]},&lt;/P&gt;&lt;P&gt;{"incrementalization_issues": [{"issue_type": "INCREMENTAL_PLAN_REJECTED_BY_COST_MODEL", "prevent_incrementalization": true, "cost_model_rejection_subtype": "NUM_JOINS_THRESHOLD_EXCEEDED", "source_rules": ["INCREMENTALIZATION_RULE_DEFAULT"]}]},&lt;/P&gt;&lt;P&gt;{"maintenance_type": "MAINTENANCE_TYPE_PARTITION_OVERWRITE", "incrementalization_issues": [ {"issue_type": "PLAN_NOT_DETERMINISTIC", "prevent_incrementalization": true, "operator_name": "Filter", "expression_name": "CurrentDate", "plan_not_deterministic_sub_type": "TIME_FUNCTION"}]},&lt;/P&gt;&lt;P&gt;{"maintenance_type": "MAINTENANCE_TYPE_COMPLETE_RECOMPUTE", "is_chosen": true, "is_applicable": true}&lt;/P&gt;</description>
      <pubDate>Sat, 31 Oct 2026 10:00:25 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175191#M56968</guid>
      <dc:creator>johnb1</dc:creator>
      <dc:date>2026-10-31T10:00:25Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175196#M56969</link>
      <description>&lt;P&gt;I ran the query with the table_changes() function. 219 partitions were affected by the operations between yesterdays's last MERGE and today's last MERGE. change_type is insert, update_preimage or update_postimage. Overall, there were 169M rows inserted and 128M rows updated in today's daily run.&lt;BR /&gt;&lt;BR /&gt;I do not know how to compare the Query profiles from Incremental refresh and Full refresh.&lt;/P&gt;</description>
      <pubDate>Sat, 31 Oct 2026 09:45:47 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175196#M56969</guid>
      <dc:creator>johnb1</dc:creator>
      <dc:date>2026-10-31T09:45:47Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175207#M56970</link>
      <description>&lt;P&gt;&lt;SPAN&gt;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/79252"&gt;@johnb1&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Thanks for running these additional tests and the efforts. The results help narrow down the issue considerably.&lt;/SPAN&gt;&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;&lt;STRONG&gt;current_date() wasn’t the cause:&lt;/STRONG&gt;&amp;nbsp;Removing it eliminated PLAN_NOT_DETERMINISTIC, but the refresh still took 199 minutes. So, while the fixed-date test was useful, it hasn't improved incremental performance.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;The cost model got it right:&amp;nbsp;&lt;/STRONG&gt;&amp;nbsp;In mv04’s log, AUTO rejected the incremental plan&amp;nbsp;(&lt;STRONG&gt;NUM_JOINS_THRESHOLD_EXCEEDED&lt;/STRONG&gt;) and chose a full recompute, which matches FULL at 55 min. REFRESH POLICY INCREMENTAL was forcing a plan the model rejected. This corrects my earlier comment.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Practical answer:&lt;/STRONG&gt; Use AUTO. It matches FULL now and can go incremental if the plan or data changes.&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;If you want incremental to win, the join count is the hurdle.&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;STRONG&gt;&lt;SPAN&gt;On reducing join complexity:&lt;/SPAN&gt;&lt;/STRONG&gt;&lt;SPAN&gt; One possible optimization is to identify redundant joins or declare &lt;/SPAN&gt;&lt;STRONG&gt;&lt;SPAN&gt;PRIMARY KEY / FOREIGN KEY constraints &lt;/SPAN&gt;&lt;/STRONG&gt;with &lt;STRONG&gt;RELY&lt;/STRONG&gt;&amp;nbsp;&lt;SPAN&gt;on the underlying tables, where the relationships are valid. This allows the optimizer to eliminate certain unnecessary joins. Docs &lt;A href="https://docs.databricks.com/aws/en/ldp/incremental-refresh/#optimize-materialized-views" target="_blank" rel="noopener"&gt;reference&lt;/A&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;I ran a small experiment with a 100K row Delta dataset to validate whether these optimizations influence the MV refresh strategy:&lt;/SPAN&gt;&lt;/P&gt;&lt;TABLE&gt;&lt;TBODY&gt;&lt;TR&gt;&lt;TD&gt;&lt;STRONG&gt;Auto Variant&lt;/STRONG&gt;&lt;/TD&gt;&lt;TD&gt;&lt;STRONG&gt;Optimized Joins&lt;/STRONG&gt;&lt;/TD&gt;&lt;TD&gt;&lt;STRONG&gt;Selected Maintenance&lt;/STRONG&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;SPAN&gt;Baseline&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;&lt;SPAN&gt;6&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;COMPLETE_RECOMPUTE&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;SPAN&gt;Removed redundant LEFT JOINs&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;&lt;SPAN&gt;2&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;ROW_BASED&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;SPAN&gt;Declared valid PK/FK constraints with &lt;STRONG&gt;RELY&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;&lt;SPAN&gt;2&lt;/SPAN&gt;&lt;/TD&gt;&lt;TD&gt;ROW_BASED&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;P&gt;In my small test, reducing joins was enough to move the planner from &lt;STRONG&gt;COMPLETE_RECOMPUTE&lt;/STRONG&gt; to &lt;STRONG&gt;ROW_BASED&lt;/STRONG&gt;, so incremental maintenance became an option. Whether it’s also faster depends on the workload, so it’s worth testing on yours. A good way to approach it:&lt;/P&gt;&lt;OL&gt;&lt;LI&gt;&lt;STRONG&gt;Find redundant joins first.&lt;/STRONG&gt; Check the optimized plan for joins that don’t change the result, such as LEFT JOINs to tables whose columns aren’t used or that can’t add rows.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Use RELY where the relationships are guaranteed.&lt;/STRONG&gt; Declare PK/FK/unique constraints only where they genuinely hold, since Databricks doesn’t enforce them and the optimizer will trust them.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Re-check the event log.&lt;/STRONG&gt; If &lt;STRONG&gt;NUM_JOINS_THRESHOLD_EXCEEDED&lt;/STRONG&gt; disappears, the incremental plan is back in play.&lt;/LI&gt;&lt;LI&gt;&lt;STRONG&gt;Measure with AUTO.&lt;/STRONG&gt; Compare against the 55-minute full refresh and keep whichever wins. If incremental still doesn’t beat it, you’ve lost nothing, since &lt;STRONG&gt;AUTO&lt;/STRONG&gt; keeps choosing the cheaper option.&lt;/LI&gt;&lt;/OL&gt;&lt;P&gt;&lt;STRONG&gt;Next step&lt;/STRONG&gt;: Compare the query profiles of &lt;STRONG&gt;mv03&lt;/STRONG&gt; and &lt;STRONG&gt;mv04&lt;/STRONG&gt;: Open each run’s query profile from &lt;STRONG&gt;Query History&lt;/STRONG&gt; (filter by the MV name and run time, find the REFRESH statement) or from the pipeline update details. Open the incremental run (mv03) and the full run (mv04) in two tabs and compare:&lt;/P&gt;&lt;UL&gt;&lt;LI&gt;Total duration, and rows and bytes read&lt;/LI&gt;&lt;LI&gt;The scans of the two large tables (rows read, files read vs. pruned)&lt;/LI&gt;&lt;LI&gt;The slowest operators, especially the joins&lt;/LI&gt;&lt;LI&gt;Shuffle size and spill&lt;/LI&gt;&lt;/UL&gt;&lt;P&gt;The &lt;STRONG&gt;GROUP_AGGREGATE&lt;/STRONG&gt; label despite no aggregation in the plan is still worth raising with Databricks Support.&lt;/P&gt;</description>
      <pubDate>Sat, 31 Oct 2026 12:27:51 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175207#M56970</guid>
      <dc:creator>data_pulse</dc:creator>
      <dc:date>2026-10-31T12:27:51Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175900#M57070</link>
      <description>&lt;P&gt;Hello &lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/79252"&gt;@johnb1&lt;/a&gt;, I took a look at both internal and external documentation and here is what I found.&lt;/P&gt;
&lt;P&gt;Thanks for posting all the test results; they make this easy to follow. Credit to &lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/250262"&gt;@data_pulse&lt;/a&gt; for the troubleshooting path (event log &lt;CODE&gt;planning_information&lt;/CODE&gt;, then swapping out &lt;CODE&gt;CURRENT_DATE()&lt;/CODE&gt;, then trying &lt;CODE&gt;AUTO&lt;/CODE&gt;). That's what found the real cause.&lt;/P&gt;
&lt;P&gt;You're seeing expected behavior. Incremental doesn't always mean cheaper, and your numbers show why.&lt;/P&gt;
&lt;P&gt;The full refresh is cheap because of pruning. Your two big sources are around 22 TB combined, but the full refresh read 0.17 TB. The date predicate plus partitioning on REGION/YEAR/MONTH/DAY prunes over 99 percent of the data before any join runs. A full recompute here recomputes a small recent window, not 22 TB of history. That's the bar incremental has to clear.&lt;/P&gt;
&lt;P&gt;The incremental refresh is expensive because of the joins. Incremental maintenance has to work out how every changed row propagates through each union, join, and anti-join. For a join, the changed rows on one side get joined against the current state of the other side, and that probe is driven by join keys, not by your date filter. Unless the join condition includes the partition columns, the pruning that saved the full refresh doesn't apply, and with seven-plus joins the big tables get read more than once. 32 TB read is about 1.5 times the raw size of the two big tables, which fits. The changeset is also large relative to that window: one MERGE on one table added about 23 GB of files (&lt;CODE&gt;numTargetBytesAdded&lt;/CODE&gt;), already more than a tenth of what the full refresh reads, and updates count twice on the change side (&lt;CODE&gt;update_preimage&lt;/CODE&gt; plus &lt;CODE&gt;update_postimage&lt;/CODE&gt;), so 128M updates and 169M inserts are closer to 425M change rows. The write side agrees: the full refresh wrote 0.78 TB, which is the whole MV, while the incremental run wrote 1.88 TB, since changes across 219 partitions mean most of the window gets rewritten anyway.&lt;/P&gt;
&lt;P&gt;AUTO got it right. The mv04 log shows the cost model rejected the incremental plan (&lt;CODE&gt;INCREMENTAL_PLAN_REJECTED_BY_COST_MODEL&lt;/CODE&gt;, subtype &lt;CODE&gt;NUM_JOINS_THRESHOLD_EXCEEDED&lt;/CODE&gt;) and chose a full recompute, matching FULL at 55 min. &lt;CODE&gt;REFRESH POLICY INCREMENTAL&lt;/CODE&gt; is documented as an override of the cost model: it runs the incremental plan whenever one is structurally possible. The docs are explicit that a cost-model rejection means the optimizer chose not to incrementalize, not that it can't. An older Community thread with Databricks engineers mentions a configurable join threshold for the Enzyme engine (&lt;CODE&gt;pipelines.enzyme.numberOfJoinsThreshold&lt;/CODE&gt;, default 2 at the time). I can't confirm the current default, and you've already run the experiment showing that forcing incremental loses here.&lt;/P&gt;
&lt;P&gt;&lt;CODE&gt;CURRENT_DATE()&lt;/CODE&gt; was a side issue. Non-deterministic time functions in a WHERE clause are supported for incremental refresh, which is why EXPLAIN reported no issues. They block PARTITION_OVERWRITE specifically. Removing it in mv03 cleared the flag, but partition overwrite still wasn't chosen and the refresh still took 199 min, so I wouldn't restructure around a fixed date. My read, unconfirmed: partition overwrite needs the planner to prove that a change in any input can only touch a bounded set of MV partitions, and your smaller tables and the source MV aren't partition-aligned.&lt;/P&gt;
&lt;P&gt;Recommendations&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Keep &lt;CODE&gt;REFRESH POLICY AUTO&lt;/CODE&gt; (the default). It made the right call, and it'll go incremental on days when the change volume is small. Use &lt;CODE&gt;INCREMENTAL&lt;/CODE&gt; only when you're sure incremental is always cheaper, and &lt;CODE&gt;INCREMENTAL STRICT&lt;/CODE&gt; when an unexpected full refresh should fail loudly.&lt;/LI&gt;
&lt;LI&gt;If you want incremental to win, cut the join count:
&lt;UL&gt;
&lt;LI&gt;Remove unused or redundant joins, including ones hidden in CTEs or upstream views.&lt;/LI&gt;
&lt;LI&gt;Declare &lt;CODE&gt;PRIMARY KEY&lt;/CODE&gt; / &lt;CODE&gt;FOREIGN KEY ... RELY&lt;/CODE&gt; constraints where the data supports them (data_pulse's suggestion too).&lt;/LI&gt;
&lt;LI&gt;Split the MV into a chain: per-source filtering and computed columns in a first layer of MVs with zero or one join each, then join those in a second MV. MVs are a supported incremental source for other MVs, and each stage gets its own eligibility check. Only a test will tell you whether the chain beats 55 minutes.&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI&gt;Check eligibility before deploying with &lt;CODE&gt;EXPLAIN CREATE MATERIALIZED VIEW&lt;/CODE&gt;. It reports structural eligibility, not what the cost model picks at runtime, so keep watching the event log:&lt;/LI&gt;
&lt;/UL&gt;
&lt;PRE&gt;&lt;CODE class="language-sql"&gt;SELECT timestamp, message, details
FROM event_log(TABLE(catalog.schema.your_mv))
WHERE event_type = 'planning_information'
ORDER BY timestamp DESC;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;UL&gt;
&lt;LI&gt;Incremental refresh doesn't need legacy change data feed on the sources; row tracking and deletion vectors are the two features the docs recommend. Your MERGE added 503 change files, so if nothing else consumes CDF on those tables, that's write cost you can drop.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;On GROUP_AGGREGATE with no GROUP BY&lt;/P&gt;
&lt;P&gt;Quick thing to rule out: do your unions use &lt;CODE&gt;UNION&lt;/CODE&gt; or &lt;CODE&gt;UNION ALL&lt;/CODE&gt;? Plain &lt;CODE&gt;UNION&lt;/CODE&gt; implies a DISTINCT, which the Spark optimizer rewrites as an Aggregate over all columns, and that's exactly what GROUP_AGGREGATE maintains. &lt;CODE&gt;EXCEPT&lt;/CODE&gt; does the same (anti-join plus DISTINCT). In EXPLAIN EXTENDED it appears as &lt;CODE&gt;Distinct&lt;/CODE&gt; in the parsed and analyzed plans and only becomes &lt;CODE&gt;Aggregate&lt;/CODE&gt; or &lt;CODE&gt;HashAggregate&lt;/CODE&gt; in the optimized and physical plans, so a text search for "aggregate" can miss it. The support table lists &lt;CODE&gt;UNION ALL&lt;/CODE&gt; specifically. If duplicates can't occur or are acceptable, &lt;CODE&gt;UNION ALL&lt;/CODE&gt; removes the aggregate. If you're already on &lt;CODE&gt;UNION ALL&lt;/CODE&gt; and nothing in the plan explains it, open a Support ticket with the event log and the query, as data_pulse suggested.&lt;/P&gt;
&lt;P&gt;Comparing query profiles&lt;/P&gt;
&lt;P&gt;Open Query History in the workspace sidebar, filter to the run window, and find the REFRESH statements for mv03 and mv04 (or open the pipeline update and click the flow). Compare the scan nodes on the two big tables: files read versus pruned, rows read, and how many times each table appears. MV refresh profiles now also carry incrementalization insights (&lt;CODE&gt;FLOW_FULL_RECOMPUTE&lt;/CODE&gt;) that explain a fallback to full recompute.&lt;/P&gt;
&lt;P&gt;References:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Incremental refresh for materialized views (support table, techniques, refresh policy, optimize section): &lt;A href="https://docs.databricks.com/aws/en/ldp/incremental-refresh" target="_blank"&gt;https://docs.databricks.com/aws/en/ldp/incremental-refresh&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;EXPLAIN CREATE MATERIALIZED VIEW (eligibility versus the cost model, rejection reasons): &lt;A href="https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-explain-materialized-view" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-explain-materialized-view&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;REFRESH POLICY clause: &lt;A href="https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-materialized-view-refresh-policy" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-ddl-create-materialized-view-refresh-policy&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Pipeline event log schema (&lt;CODE&gt;CostModelRejectionSubType&lt;/CODE&gt; values): &lt;A href="https://docs.databricks.com/aws/en/ldp/monitor-event-log-schema" target="_blank"&gt;https://docs.databricks.com/aws/en/ldp/monitor-event-log-schema&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Query profile and the FLOW_FULL_RECOMPUTE insight: &lt;A href="https://docs.databricks.com/aws/en/sql/user/queries/query-profile" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/user/queries/query-profile&lt;/A&gt; and &lt;A href="https://docs.databricks.com/aws/en/sql/user/queries/performance-insights#flow-full-recompute" target="_blank"&gt;https://docs.databricks.com/aws/en/sql/user/queries/performance-insights#flow-full-recompute&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Background on the Enzyme engine and its cost model, from the Databricks team: &lt;A href="https://arxiv.org/pdf/2603.27775" target="_blank"&gt;https://arxiv.org/pdf/2603.27775&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;Earlier Community thread on NUM_JOINS_THRESHOLD_EXCEEDED: &lt;A href="https://community.databricks.com/t5/data-engineering/materialized-view-refresh-num-joins-threshold-exceeded/td-p/100761" target="_blank"&gt;https://community.databricks.com/t5/data-engineering/materialized-view-refresh-num-joins-threshold-exceeded/td-p/100761&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;If AUTO settles your refresh times, please mark the thread as solved so others can find it.&lt;/P&gt;
&lt;P&gt;Regards,&lt;BR /&gt;Louis.&lt;/P&gt;</description>
      <pubDate>Sat, 07 Nov 2026 17:56:42 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175900#M57070</guid>
      <dc:creator>Louis_Frolio</dc:creator>
      <dc:date>2026-11-07T17:56:42Z</dc:date>
    </item>
    <item>
      <title>Re: Materialized Views - Strange Difference in Refresh Performance</title>
      <link>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175901#M57071</link>
      <description>&lt;P&gt;The incremental refresh may actually be doing more work because of the complexity of the query and how changes propagate through joins, unions, and anti-joins. I’d be interested to know whether the query plan shows expensive change detection or recomputation. Have you compared the execution plans of both refresh modes? That might reveal where the additional processing is coming from. &lt;span class="lia-unicode-emoji" title=":magnifying_glass_tilted_left:"&gt;🔍&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":framed_picture:"&gt;🖼&lt;/span&gt;&lt;/P&gt;</description>
      <pubDate>Sun, 08 Nov 2026 05:08:15 GMT</pubDate>
      <guid>https://community.databricks.com/t5/data-engineering/materialized-views-strange-difference-in-refresh-performance/m-p/175901#M57071</guid>
      <dc:creator>Fastlearning272</dc:creator>
      <dc:date>2026-11-08T05:08:15Z</dc:date>
    </item>
  </channel>
</rss>

