-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-35455][SQL] Unify empty relation optimization between normal and AQE optimizer #32602
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
Changes from 4 commits
4514d27
5097247
8df68d9
165077b
2f5fa20
7b80db0
05e074c
48dd92a
8f4dc80
1220087
e26df96
f7a14cf
2c0dfb0
0e151d9
c086f72
9b78ac0
767dd92
47e0c3a
d9ca6da
2fead86
0754936
a6213ea
624e45e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* | ||
| * 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.execution.adaptive | ||
|
|
||
| import org.apache.spark.sql.catalyst.plans.logical.{LocalRelation, LogicalPlan} | ||
| import org.apache.spark.sql.catalyst.rules.Rule | ||
|
|
||
| /** | ||
| * Converts empty query stage to empty `LocalRelation` | ||
| */ | ||
| object ConvertToLocalRelation extends Rule[LogicalPlan] { | ||
| override def apply(plan: LogicalPlan): LogicalPlan = plan transform { | ||
| case l @ LogicalQueryStage(_, stage: QueryStageExec) if stage.resultOption.get().isDefined && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I made the wrong decision. This may change the output partitioning and is not always safe/beneficial (we may add extra shuffles in the planning phase later). Looking at the optimizations for empty local relations, some of them are likely beneficial and we can always do: eliminate join, aggregate, limit, repartition, sort, generate Some may not that beneficial and we shouldn't do: simplify union, eliminate project/filter/sample. My new idea:
The old
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the issue. If we worry about the
Not sure we can always do this if we don't want to introduce extra shuffle. And the issue it has already existed in current An another idea, if we plan to support extra shuffle later and don't expect introduce shuffle at AQE optimzier side, then is it better to check the physical plan
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we turn a broadcast stage into local relation without changing other plan parts, seems we will broadcast the local relation again.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's hard to avoid introduce shuffle at AQE optimizer, how about add extra shuffle check between AQE optimizer and stage preparation ? Then it won't affect the extra shuffle in stage preparation.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can do that, but it seems not worth the complexity. It's not very helpful to turn query stage to local relation if we can't use it to eliminate expensive operators like join, agg, sort, etc. I think my proposal is simpler and effective enough. The |
||
| stage.getRuntimeStatistics.rowCount.contains(0) => | ||
| LocalRelation(l.output, Seq.empty) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -236,7 +236,8 @@ class AdaptiveQueryExecSuite | |
| test("Empty stage coalesced to 1-partition RDD") { | ||
| withSQLConf( | ||
| SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true", | ||
| SQLConf.COALESCE_PARTITIONS_ENABLED.key -> "true") { | ||
| SQLConf.COALESCE_PARTITIONS_ENABLED.key -> "true", | ||
| SQLConf.ADAPTIVE_OPTIMIZER_EXCLUDED_RULES.key -> ConvertToLocalRelation.ruleName) { | ||
| val df1 = spark.range(10).withColumn("a", 'id) | ||
| val df2 = spark.range(10).withColumn("b", 'id) | ||
| withSQLConf(SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1") { | ||
|
|
@@ -1307,6 +1308,69 @@ class AdaptiveQueryExecSuite | |
| } | ||
| } | ||
|
|
||
| test("SPARK-35455: Enhance EliminateUnnecessaryJoin - single join") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's update the test name and PR title:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated it and also updated the PR title. |
||
| withSQLConf(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true", | ||
| SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1") { | ||
| Seq( | ||
| // left semi join and empty left side | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't optimize this before this PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, we cann't. Before we only check right side with And the test should use different column to do filter and join in case of |
||
| ("SELECT * FROM (SELECT * FROM testData WHERE key = 0)t1 LEFT SEMI JOIN testData2 t2 ON " + | ||
| "t1.key = t2.a", true), | ||
| // left anti join and empty left side | ||
| ("SELECT * FROM (SELECT * FROM testData WHERE key = 0)t1 LEFT ANTI JOIN testData2 t2 ON " + | ||
| "t1.key = t2.a", true), | ||
| // left outer join and empty left side | ||
| ("SELECT * FROM (SELECT * FROM testData WHERE key = 0)t1 LEFT JOIN testData2 t2 ON " + | ||
| "t1.key = t2.a", true), | ||
| // left outer join and non-empty left side | ||
| ("SELECT * FROM testData t1 LEFT JOIN testData2 t2 ON " + | ||
| "t1.key = t2.a", false), | ||
| // right outer join and empty right side | ||
| ("SELECT * FROM testData t1 RIGHT JOIN (SELECT * FROM testData2 WHERE b = 0)t2 ON " + | ||
| "t1.key = t2.a", true), | ||
| // right outer join and non-empty right side | ||
| ("SELECT * FROM testData t1 RIGHT JOIN testData2 t2 ON " + | ||
| "t1.key = t2.a", false), | ||
| // full outer join and both side empty | ||
| ("SELECT * FROM (SELECT * FROM testData WHERE key = 0)t1 FULL JOIN " + | ||
| "(SELECT * FROM testData2 WHERE b = 0)t2 ON t1.key = t2.a", true), | ||
| // full outer join and left side empty right side non-empty | ||
| ("SELECT * FROM (SELECT * FROM testData WHERE key = 0)t1 FULL JOIN " + | ||
| "testData2 t2 ON t1.key = t2.a", true) | ||
| ).foreach { case (query, isEliminated) => | ||
| val (plan, adaptivePlan) = runAdaptiveAndVerifyResult(query) | ||
| assert(findTopLevelBaseJoin(plan).size == 1) | ||
| assert(findTopLevelBaseJoin(adaptivePlan).isEmpty == isEliminated, adaptivePlan) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("SPARK-35455: Enhance EliminateUnnecessaryJoin - multi join") { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| withSQLConf(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true", | ||
| SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1") { | ||
| Seq( | ||
| """ | ||
| |SELECT * FROM testData t1 | ||
| | JOIN (SELECT * FROM testData2 WHERE b = 0) t2 ON t1.key = t2.a | ||
| | LEFT JOIN testData2 t3 ON t1.key = t3.a | ||
| |""".stripMargin, | ||
| """ | ||
| |SELECT * FROM (SELECT * FROM testData WHERE key = 0) t1 | ||
| | LEFT ANTI JOIN testData2 t2 | ||
| | FULL JOIN (SELECT * FROM testData2 WHERE b = 0) t3 ON t1.key = t3.a | ||
| |""".stripMargin, | ||
| """ | ||
| |SELECT * FROM testData t1 | ||
| | LEFT SEMI JOIN (SELECT * FROM testData2 WHERE b = 0) | ||
| | RIGHT JOIN testData2 t3 on t1.key = t3.a | ||
| |""".stripMargin | ||
| ).foreach { query => | ||
| val (plan, adaptivePlan) = runAdaptiveAndVerifyResult(query) | ||
| assert(findTopLevelBaseJoin(plan).size == 2) | ||
| assert(findTopLevelBaseJoin(adaptivePlan).isEmpty) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("SPARK-32753: Only copy tags to node with no tags") { | ||
| withSQLConf(SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true") { | ||
| withTempView("v1") { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.