[SPARK-58950][SQL] Materialize repeated view references as one CTE - #58229
[SPARK-58950][SQL] Materialize repeated view references as one CTE#58229xumingming wants to merge 1 commit into
Conversation
Introduce a ConvertViewToMaterializedCTE optimizer rule that rewrites multiple references to the same view into a single forceSkipInline CTERelationDef with a reference per occurrence, so the view's underlying plan is computed once (through exchange reuse) instead of once per reference. The rule runs in FinishAnalysis immediately before EliminateView and is gated behind spark.sql.optimizer.convertViewToMaterializedCTE (off by default). Only deterministic, non-streaming views with matching view SQL configs and aligned output schemas convert. Occurrences are grouped by catalog identity plus canonicalized body, but the bottom-up rewrite matches by identifier alone: by the time an outer view is visited, nested views inside its body have already been rewritten into CTERelationRefs, so its body no longer canonicalizes to the key computed up front. A view nested inside another view's body therefore converts too, and definitions are appended in topological order (referenced definitions first), which ReplaceCTERefWithRepartition relies on when it iterates the definitions in order and resolves references from a map it builds incrementally. A view body may contain correlated subqueries whose outer references resolve to relations inside the body (e.g. t WHERE x IN (SELECT y FROM s WHERE s.k = t.k)). The view is analyzed standalone when it is created, so an outer reference that does not resolve inside the body fails view analysis and can never escape to the outer query. The converted definition contains the whole body, so internal correlations resolve within it and these bodies are safe to convert; InlineCTE's rejection of boundary-crossing outer references is only a generic safety net for non-view forceSkipInline producers. Add catalyst rule tests proving that an internally correlated body and a view-over-view body convert (the latter with two definitions in topological order, surviving the Inline CTE batch), and QuerySuite tests proving such views execute correctly end to end with the conversion enabled.
2f2570b to
0deedf1
Compare
|
Thank you @xumingming! LGTM, adding @peter-toth for further review |
|
I'm a bit reluctant to proceed with this PR. Spark doesn't have a cost model that we could use to estimate the benefits and costs of such materialization. That's why Spark currently materializes only multiply referenced, non-deterministic CTEs when materialization is needed for correctness. @cloud-fan, any thoughts on this? |
|
@peter-toth Thanks for the review. I agree with you that we don’t have a cost model to check whether materialization is the right call. Here we do not do any view materialization by default, only when user enabled the switch, we will do it. In production query we have many views that should be materialized to get better performance, so we want to add this ability. |
What changes were proposed in this pull request?
Introduce a new optimizer rule,
ConvertViewToMaterializedCTE, that runs inFinishAnalysis(immediately beforeEliminateView). When the same view isreferenced two or more times in a query, the rule groups the occurrences (by
catalog identity plus canonicalized body) and rewrites each qualifying group
into a single
forceSkipInlineCTERelationDefwith oneCTERelationRefper occurrence. Downstream,
ReplaceCTERefWithRepartitionwraps eachreference site with a shuffle boundary and exchange reuse deduplicates the
computation, so the view's body is evaluated once instead of once per
reference.
Details worth noting for reviewers:
(identifier, canonicalized body); the bottom-uprewrite matches by identifier, so views nested inside another view's body
convert too, with definitions emitted in topological order (referenced
definitions first).
(e.g.
t WHERE x IN (SELECT y FROM s WHERE s.k = t.k)) convert as well: aview body is analyzed standalone at creation, so correlated references
always resolve inside the body and never escape the converted definition.
spark.sql.optimizer.convertViewToMaterializedCTE, off by default, namedper the configuration naming guideline in
ConfigEntry.scala.non-streaming, all occurrences must agree on the effective view SQL config
and output schema, and single-reference views are skipped.
Why are the changes needed?
In ETL workloads, views are almost always costly: they encapsulate large
scans, joins, and aggregations over big base tables. When such a view is
referenced more than once in one query, the current plan inlines the view
body at every reference site, so the costly body executes once per
reference. Materializing the view once and sharing the result across all
references avoids that repeated work, for example:
Today the whole
serving_logscan and aggregation behind the view run twice.Does this PR introduce any user-facing change?
Yes, in the form of a new, opt-in configuration:
spark.sql.optimizer.convertViewToMaterializedCTE(defaultfalse). Whenenabled, repeated references to the same view are materialized as one CTE
with a shuffle boundary per reference site and exchange reuse; when disabled
(the default), the plan is unchanged.
How was this patch tested?
Added and ran unit tests in both suites:
ConvertViewToMaterializedCTESuite(catalyst, 16 tests) - rule-levelcoverage: conversion of self-joined views, single-reference and
non-deterministic/streaming/config-mismatch/schema-mismatch rejection,
references inside scalar subqueries, internally correlated bodies,
view-over-view (nested) conversion with topological definition order,
survival of converted definitions through the
InlineCTEbatch, andidempotency.
ConvertViewToMaterializedCTEQuerySuite(sql/core, 6 tests) - end-to-endcoverage: results identical to the un-converted baseline, one shuffle
boundary per reference site, non-deterministic views not converted,
INSERT INTOfrom a self-referenced view, and internally correlated andnested views executing correctly with the config enabled.
Commands:
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Pi