-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-40259][SQL] Support merging equivalent DataSource V2 scans in MergeSubplans #57360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
peter-toth
wants to merge
23
commits into
apache:master
from
peter-toth:SPARK-40259-dsv2-planmerger-support
Closed
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
48658c2
[SQL] Move MergeSubplans and PlanMerger to a new sql/core planmerging…
peter-toth dfab083
[SPARK-40259][SQL] Support merging equivalent DataSource V2 scans in …
peter-toth eaa81ce
[SPARK-40259][SQL] Address review comments
peter-toth 928fcc7
Merge remote-tracking branch 'apache/master' into SPARK-40259-dsv2-pl…
peter-toth 120aabb
[SPARK-40259][SQL] Drop the non-deterministic filter merge guard, red…
peter-toth 762ba67
[SPARK-40259][SQL] Build the merged DSv2 scan once at the enclosing F…
peter-toth d83d2c0
[SPARK-40259][SQL] Make DSv2 scan merging default-safe
peter-toth fef130d
[SPARK-40259][SQL] Re-check the rebuilt merged DSv2 scan is mergeable
peter-toth 66eeda3
[SPARK-40259][SQL] Do not merge DSv2 scans reading a nested-pruned co…
peter-toth 1d8548d
[SPARK-40259][SQL] Match merged DSv2 scans by position and exprId, no…
peter-toth d8e600e
[SPARK-40259][SQL] Fix DSv2 scan-merge comments and config doc
peter-toth 1499597
[SPARK-40259][SQL] Default dsv2SymmetricFilterPropagation to false
peter-toth 32f0241
[SPARK-40259][SQL] Assert no placeholder relation survives a DSv2 sca…
peter-toth 5fb710d
[SPARK-40259][SQL] Propagate the DSv2 merge fact to outer Filter pairs
peter-toth f7d4414
[SPARK-40259][SQL] Add migration note for the MergeSubplans package move
peter-toth d782869
[SPARK-40259][SQL] Signal DSv2 scan merging via a TableCapability, no…
peter-toth b4b5207
[SPARK-40259][SQL] Address self-review feedback on the DSv2 scan merge
peter-toth 0991f10
Merge apache/master into SPARK-40259-dsv2-planmerger-support
peter-toth 466b6b6
[SPARK-40259][SQL] Address review: temporary reference wording and @p…
peter-toth 14c0f0e
[SPARK-40259][SQL] Fix a >100-char comment line in MergeSubplansSuite
peter-toth 642c557
[SPARK-40259][SQL] Do not decline a DSv2 merge for an empty reported …
peter-toth 72b2e63
Merge branch 'master' into SPARK-40259-dsv2-planmerger-support
peter-toth c92137f
[SPARK-40259][SQL] Reconcile the master merge in pruneColumns
peter-toth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
...lyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/MergeSubplansReferences.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.spark.sql.catalyst.optimizer | ||
|
|
||
| import org.apache.spark.sql.catalyst.expressions.{Attribute, ExprId, LeafExpression, Unevaluable} | ||
| import org.apache.spark.sql.catalyst.plans.logical.LeafNode | ||
| import org.apache.spark.sql.catalyst.trees.TreePattern.{NO_GROUPING_AGGREGATE_REFERENCE, SCALAR_SUBQUERY_REFERENCE, TreePattern} | ||
| import org.apache.spark.sql.types.DataType | ||
|
|
||
| // The temporary reference placeholders below are produced by the `MergeSubplans` rule (now in | ||
| // sql/core) but must remain in catalyst: `ScalarSubqueryReference` is referenced by the catalyst | ||
| // expression `BloomFilterMightContain`, and catalyst cannot depend on sql/core. | ||
|
|
||
| /** | ||
| * Temporary reference to a subquery which is added to a `PlanMerger`. | ||
| * | ||
| * @param level The level of the replaced subquery. It defines the `PlanMerger` instance into which | ||
| * the subquery is merged. | ||
| * @param mergedPlanIndex The index of the merged plan in the `PlanMerger`. | ||
| * @param outputIndex The index of the output attribute of the merged plan. | ||
| * @param dataType The data type of the original scalar subquery. | ||
| * @param exprId The expression id of the original scalar subquery. | ||
| */ | ||
| case class ScalarSubqueryReference( | ||
| level: Int, | ||
| mergedPlanIndex: Int, | ||
| outputIndex: Int, | ||
| override val dataType: DataType, | ||
| exprId: ExprId) extends LeafExpression with Unevaluable { | ||
| override def nullable: Boolean = true | ||
|
|
||
| final override val nodePatterns: Seq[TreePattern] = Seq(SCALAR_SUBQUERY_REFERENCE) | ||
| } | ||
|
|
||
| /** | ||
| * Temporary reference to a non-grouping aggregate which is added to a `PlanMerger`. | ||
| * | ||
| * @param level The level of the replaced aggregate. It defines the `PlanMerger` instance into which | ||
| * the aggregate is merged. | ||
| * @param mergedPlanIndex The index of the merged plan in the `PlanMerger`. | ||
| * @param outputIndices The indices of the output attributes of the merged plan. | ||
| * @param output The output of the original aggregate. | ||
| */ | ||
| case class NonGroupingAggregateReference( | ||
| level: Int, | ||
| mergedPlanIndex: Int, | ||
| outputIndices: Seq[Int], | ||
| override val output: Seq[Attribute]) extends LeafNode { | ||
| final override val nodePatterns: Seq[TreePattern] = Seq(NO_GROUPING_AGGREGATE_REFERENCE) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
...cala/org/apache/spark/sql/connector/catalog/InMemoryScanMergingPartitionFilterTable.scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.spark.sql.connector.catalog | ||
|
|
||
| import java.util | ||
|
|
||
| import org.apache.spark.sql.catalyst.analysis.TableAlreadyExistsException | ||
| import org.apache.spark.sql.connector.expressions.Transform | ||
| import org.apache.spark.sql.connector.read.{Batch, Scan, ScanBuilder} | ||
| import org.apache.spark.sql.types.StructType | ||
| import org.apache.spark.sql.util.CaseInsensitiveStringMap | ||
|
|
||
| /** | ||
| * Catalog that hands out [[InMemoryScanMergingPartitionFilterTable]]s, an iterative-pushdown source | ||
| * that additionally opts in to Spark-side scan merging. Used to exercise merging two DSv2 scans | ||
| * whose (equal) filter is strict only via the iterative PartitionPredicate second pass. | ||
| */ | ||
| class InMemoryScanMergingPartitionFilterCatalog | ||
| extends InMemoryTableEnhancedPartitionFilterCatalog { | ||
| import CatalogV2Implicits._ | ||
|
|
||
| override def createTable( | ||
| ident: Identifier, | ||
| columns: Array[Column], | ||
| partitions: Array[Transform], | ||
| properties: util.Map[String, String]): Table = { | ||
| if (tables.containsKey(ident)) { | ||
| throw new TableAlreadyExistsException(ident.asMultipartIdentifier) | ||
| } | ||
| InMemoryTableCatalog.maybeSimulateFailedTableCreation(properties) | ||
| val tableName = s"$name.${ident.quoted}" | ||
| val table = | ||
| new InMemoryScanMergingPartitionFilterTable(tableName, columns, partitions, properties) | ||
| tables.put(ident, table) | ||
| namespaces.putIfAbsent(ident.namespace.toList, Map()) | ||
| table | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * An [[InMemoryEnhancedPartitionFilterTable]] that returns the `TableCapability.SCAN_MERGING` | ||
| * capability, so [[org.apache.spark.sql.execution.planmerging.PlanMerger]] may fuse two scans of | ||
| * this table. Its scan is wrapped in a thin [[NonReportingScan]] so a partitioned table does not | ||
| * set the scan relation's `keyGroupedPartitioning` (whose preservation across a merge is a separate | ||
| * follow-up); this keeps the fixture focused on the iterative-pushdown behavior under test. | ||
| */ | ||
| class InMemoryScanMergingPartitionFilterTable( | ||
| name: String, | ||
| columns: Array[Column], | ||
| partitioning: Array[Transform], | ||
| properties: util.Map[String, String]) | ||
| extends InMemoryEnhancedPartitionFilterTable(name, columns, partitioning, properties) { | ||
|
|
||
| override def capabilities(): util.Set[TableCapability] = { | ||
| val caps = new util.HashSet[TableCapability](super.capabilities()) | ||
| caps.add(TableCapability.SCAN_MERGING) | ||
| caps | ||
| } | ||
|
|
||
| override def newScanBuilder(options: CaseInsensitiveStringMap): ScanBuilder = | ||
| new InMemoryEnhancedPartitionFilterScanBuilder(schema()) { | ||
| override def build(): Scan = NonReportingScan(super.build()) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Thin scan decorator that exposes only `readSchema`, `toBatch` and `description`, dropping the | ||
| * base scan's `SupportsReportPartitioning`/`SupportsReportStatistics`. So the scan relation carries | ||
| * no reported partitioning/ordering/statistics -- for a partitioned table this keeps | ||
| * `keyGroupedPartitioning` unset, which the scan merge requires (preserving reported partitioning | ||
| * across a merge is a separate follow-up). | ||
| */ | ||
| case class NonReportingScan(inner: Scan) extends Scan { | ||
| override def readSchema(): StructType = inner.readSchema() | ||
| override def toBatch: Batch = inner.toBatch | ||
| override def description(): String = inner.description() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config doc says equal strict filters mean both sides read the same base rows, but the strict set comes out of
filterNot(postScanFilterSet.contains), so a predicate the source hands back for a post-scan re-check never enters it, and line 115 of the same file says that overlap is normal (the parquet row group filter). A source that prunes onpand also returnsptherefore reports an empty strict set on both sides, the sets compare equal, and the criterion holds while the property the doc claims does not. I first read this as IO amplification, but following it through, most cases recover: onceOr(p='a', p='b')is rejected in the first pass it reachescreatePartitionPredicatesin the second, gets wrapped as aPartitionPredicateon the partition column and pushed, and the pruning comes back. What actually degrades is a source that is opt-in, partially pushes, cannot express OR, and has no second pass, which is a bounded regression. So the doc is the part that needs work: replace "read the same base rows" with what does hold, that the strict filters are equal and the pruning predicate is best effort with the enclosing Filter guaranteeing correctness. Whether the default should flip to false is your call; I would keep it true and fix the wording. I also withdraw the idea of requiring the post-scan filters to match. After early pushdown theFilterabove the scan is the post-scan set, so that amounts to requiring the twoFilterconditions to be equal, which lands in the identical branch and removes the feature.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworded the doc (dropped "read the same base rows" → the strict filters are equal and the pruning is best-effort, with the enclosing Filter guaranteeing correctness) (d8e600ec16e). On the default I went the other way from your lean and shipped it
falsefor the freeze (14995973b04); we can flip it on in a later release. Thanks for withdrawing the post-scan-match idea.