-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-32995][SQL] CostBasedJoinReorder optimizer rule should be idempotent #29871
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 8 commits
3628972
d58966c
7a44562
1aec54b
c995703
c9028ad
4619acc
ee944e9
d77a024
8bd1c21
624e355
f208406
f204acb
6248e4a
fe7da48
09c5da8
f815feb
f7744db
d09abd1
5fe5f3b
d6522d9
d2a6128
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 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -206,13 +206,15 @@ object JoinReorderDP extends PredicateHelper with Logging { | |||||||||||||||||
| filters: Option[JoinGraphInfo]): JoinPlanMap = { | ||||||||||||||||||
|
|
||||||||||||||||||
| val nextLevel = new JoinPlanMap | ||||||||||||||||||
| var k = 0 | ||||||||||||||||||
| val lev = existingLevels.length - 1 | ||||||||||||||||||
| var k = lev | ||||||||||||||||||
| // Build plans for the next level from plans at level k (one side of the join) and level | ||||||||||||||||||
| // lev - k (the other side of the join). | ||||||||||||||||||
| // For the lower level k, we only need to search from 0 to lev - k, because when building | ||||||||||||||||||
| // For the higher level k, we only need to search from lev to lev - k, because when building | ||||||||||||||||||
| // a join from A and B, both A J B and B J A are handled. | ||||||||||||||||||
| while (k <= lev - k) { | ||||||||||||||||||
| // Start searching from highest level to make sure that optimally ordered input doesn't get | ||||||||||||||||||
| // reordered into another plan with the same cost. | ||||||||||||||||||
|
Member
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. Could you describe more about why we need to change this search spark/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/CostBasedJoinReorder.scala Lines 227 to 233 in d15f504
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. This was also my first idea, but I couldn't figure it out. I'll try to give two examples, where all pairwise joins have the same cost and we can join only plans with consecutive letters (AB, but bot AC) spark/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/CostBasedJoinReorder.scala Line 301 in 4619acc
Let the inital join be ((AB)C) - firstly join A and B and then join with C. Firstly if we kept the first candidate (current code)
Secondly if we kept the last one (possible change, that I considered)
Neither of these gave the original result. But lets try iterating from the largest (while keeping the first candidate). This is my proposition.
Now, if we would add another plan D, then in the 4. iteration it would be appended to the ((AB)C) and we would still have correct result. Note that if the input would be ((AB)(CD)), then the result would also be (((AB)C)D), but we must be stable only on second time this is applied and because we output left-deep trees, then we must be stable on left-deep trees, to be idempotent. Also by left-deep we mean "as left-deep" as possible. And finally, yes, I know that this dummy example is no rigorous proof, but we have 2 things going for us:
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 pondered about this a bit more. I think this is a bit more general explanaiton: After the first round of optimisation the plans are ordered: ABCD.... We know that the result is as left-deep as possible. So, if we would be iterating from the smaller ones, then we could have (K) and (LM), that we want to join, but because it has to be left-deep, we must order it as ((LM)K). But, if we start from the bigger ones, then we hit (KL) and (M) first and joining these will not reorder the letters. For stability I think, that there are at least 4 factors, that must match with each others: There might be more. Changing the order of picking levels gets us to stability in one change.
Member
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. Thanks for your detailed explanation. IMO a fix should be simple and intuitive in terms of maintainability. Another idea that I came up with after I read the description above was to simply sort input candidate plans by some values (e.g.,
Member
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. Btw, could you update the PR description based on the explanation above? That explanation looks useful for making others understood smoothly, I think.
Member
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.
Ah, I see... This rule rewrites candidate plans after reordering plans, so IIUC
okay, let's wait for the comments of other reviewers.
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, that was one issue, that complicated the approach. But, what made it even worse is that the value for
Member
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.
Yea, that's a design and why do we need to keep the same
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.
It does not allow us sort by it and then compare results in the
Member
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. Ah, I see. I only cared about the idempotence check in |
||||||||||||||||||
| while (k >= lev - k) { | ||||||||||||||||||
| val oneSideCandidates = existingLevels(k).values.toSeq | ||||||||||||||||||
| for (i <- oneSideCandidates.indices) { | ||||||||||||||||||
| val oneSidePlan = oneSideCandidates(i) | ||||||||||||||||||
|
|
@@ -236,7 +238,7 @@ object JoinReorderDP extends PredicateHelper with Logging { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| k += 1 | ||||||||||||||||||
| k -= 1 | ||||||||||||||||||
| } | ||||||||||||||||||
| nextLevel | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
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.
cc: @wzhfy