Skip to content
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

[SPARK-37193][SQL] DynamicJoinSelection.shouldDemoteBroadcastHashJoin should not apply to outer joins #34464

Closed
wants to merge 1 commit into from

Conversation

ekoifman
Copy link
Contributor

@ekoifman ekoifman commented Nov 2, 2021

What changes were proposed in this pull request?

DynamicJoinSelection.shouldDemoteBroadcastHashJoin will prevent AQE from converting Sort merge join into a broadcast join because SMJ is faster when the side that would be broadcast has a lot of empty partitions.
This makes sense for inner joins which can short circuit if one side is empty.
For (left,right) outer join, the streaming side still has to be processed so demoting broadcast join doesn't have the same advantage.

Does this PR introduce any user-facing change?

Yes, it may cause AQE to choose BHJ more often than before with better performance

How was this patch tested?

Spark UTs
Also empirical evidence

@github-actions github-actions bot added the SQL label Nov 2, 2021
@AmplabJenkins
Copy link

Can one of the admins verify this patch?

val demoteBroadcastHash = joinType match {
// doesn't make sense for outer joins since one side is preserved and join is not
// short circuited if the other side is empty
case Inner | LeftSemi | LeftAnti => shouldDemoteBroadcastHashJoin (stage.mapStats.get)
Copy link

@singhpk234 singhpk234 Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also include Existence Joins as well

  • LeftExistence pattern to match all LeftSemi / LeftAnti / Existence in one shot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed it a bit - seems more natural

withSQLConf(SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "80") {
val (plan, adaptivePlan) = runAdaptiveAndVerifyResult(
"SELECT * FROM (select * from testData where value = '1') td" +
" right outer join testData2 ON key = a")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[optional] how about adding all outerJoinTypes here and making it more rigid
can be done with minimal changes

        Seq("right outer", "left outer", "full outer").foreach { joinType =>
          val (plan, adaptivePlan) = runAdaptiveAndVerifyResult(
            "SELECT * FROM (select * from testData where value = '1') td" +
              s" $joinType join testData2 ON key = a")
          val smj = findTopLevelSortMergeJoin(plan)
          assert(smj.size == 1)
          val bhj = findTopLevelBroadcastHashJoin(adaptivePlan)
          assert(bhj.size == 1)
        }

Copy link
Contributor Author

@ekoifman ekoifman Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback but changing right outer here to a left outer won't have the same semantics w/o changing the sql. You can only broadcast the inner side.
I don't believe full outer supports BHJ at all

@ekoifman
Copy link
Contributor Author

@HyukjinKwon do you have any other suggestions?

@ekoifman
Copy link
Contributor Author

Hi
@cloud-fan @ulysses-you @maryannxue could one of you take a look please

val demoteBroadcastHash = joinType match {
// doesn't make sense for outer joins since one side is preserved and join is not
// short circuited if the other side is empty
case LeftOuter | RightOuter | FullOuter => false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does BHJ support FullOuter ? what about LeftSemi and LeftAnti ?

Copy link
Contributor Author

@ekoifman ekoifman Nov 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BHJ can't support FullOuter - I put it in the list since it seems odd (though not wrong) to add no_broadcast_hash to a join that doesn't support BHJ anyway. Makes logs harder to read.
LeftSemi & LeftAnti could in principle support it - I don't know if they do today.

@ekoifman
Copy link
Contributor Author

ping - is there anything else I can do to move this along?

@cloud-fan
Copy link
Contributor

The last tests pass is long time ago, @ekoifman can you rebase this PR and run the test again? Sorry for the late review.

@ekoifman
Copy link
Contributor Author

@cloud-fan done

@cloud-fan
Copy link
Contributor

thanks, merging to master!

@cloud-fan cloud-fan closed this in 66c2d1f Dec 23, 2021
var newHint = hint
if (!hint.leftHint.exists(_.strategy.isDefined)) {
selectJoinStrategy(left).foreach { strategy =>
selectJoinStrategy(left, joinType).foreach { strategy =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For left outer join, and the left side has many empty partitions, shall we still demote?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For LOJ with many empty partitions on the left, the local join can short-circuit whether you broadcast or shuffle. I'm not sure how to determine which strategy will send less data around. Is there another heuristic that can be used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the local join can short-circuit whether you broadcast or shuffle.

I think there is a misunderstanding here. We see many empty partitions on the shuffled left side, it doesn't mean the original left side before shuffle also has many empty partitions. I think we need to demote.

@ekoifman can you open a followup PR to fix this issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. filed SPARK-37753

@cloud-fan
Copy link
Contributor

I'm reverting this to avoid mistakenly releasing a performance regression in Spark 3.3. Please resubmit this PR with SPARK-37753 resolved.

cloud-fan pushed a commit that referenced this pull request Mar 8, 2022
…ynamicJoinSelection

### What changes were proposed in this pull request?

### Why are the changes needed?

In the current implementation of DynamicJoinSelection the logic checks if one side of the join has high ratio of empty partitions and adds a NO_BROADCAST hint on that side since a shuffle join can short-circuit the local joins where one side is empty.

This logic is doesn't make sense for all join type. For example, a Left Outer Join cannot short circuit if RHS is empty so we should not inhibit BHJ. On the other hand a LOJ executed as a shuffle join where the LHS has many empty can short circuit the local join so we should inhibit the BHJ because BHJ will use OptimizeShuffleWithLocalRead which will re-assemble LHS partitions as the were before the shuffle and thus may not have many empty ones any more.

This supersedes [SPARK-37193](https://issues.apache.org/jira/browse/SPARK-37193)
Also see previous discussion in #34464 (comment)

### Does this PR introduce _any_ user-facing change?

It may change which joins run as BHJ vs shuffle joins

### How was this patch tested?

Unit Tests

Closes #35715 from ekoifman/SPARK-37753-enhance-DynamiJoinSelection.

Authored-by: Eugene Koifman <eugene.koifman@workday.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
@thomasg19930417
Copy link

I'm reverting this to avoid mistakenly releasing a performance regression in Spark 3.3. Please resubmit this PR with SPARK-37753 resolved.

can you tell me ,What does short circuit local join mean,thanks

@ekoifman
Copy link
Contributor Author

@thomasg19930417
Imagine you have an inner join where the one side has 0 rows. As soon as you know that one side is empty, you don't have evaluate any operators on the other side (or stop executing them if they started already) since you know the join won't produce any rows.

@thomasg19930417
Copy link

@ekoifman
Thank you for reply

@thomasg19930417
Copy link

thomasg19930417 commented Sep 19, 2022

@ekoifman hi, when Inner Join and LHS has many empty partition ,why inner join not demote broadcast

   else if (manyEmptyInOther && canBroadcastPlan) {
         join.joinType match {
        case LeftOuter | RightOuter | LeftAnti => true
        case _ => false
      }

wangyum pushed a commit that referenced this pull request May 26, 2023
…ynamicJoinSelection

In the current implementation of DynamicJoinSelection the logic checks if one side of the join has high ratio of empty partitions and adds a NO_BROADCAST hint on that side since a shuffle join can short-circuit the local joins where one side is empty.

This logic is doesn't make sense for all join type. For example, a Left Outer Join cannot short circuit if RHS is empty so we should not inhibit BHJ. On the other hand a LOJ executed as a shuffle join where the LHS has many empty can short circuit the local join so we should inhibit the BHJ because BHJ will use OptimizeShuffleWithLocalRead which will re-assemble LHS partitions as the were before the shuffle and thus may not have many empty ones any more.

This supersedes [SPARK-37193](https://issues.apache.org/jira/browse/SPARK-37193)
Also see previous discussion in #34464 (comment)

It may change which joins run as BHJ vs shuffle joins

Unit Tests

Closes #35715 from ekoifman/SPARK-37753-enhance-DynamiJoinSelection.

Authored-by: Eugene Koifman <eugene.koifman@workday.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants