-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Do not repartition inputs whose sort order is required #4885
Conversation
let can_reorder_child = (can_reorder | ||
&& plan.required_input_ordering().is_empty()) |
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 change is to add && plan.required_input_ordering().is_empty())
to the check if the child can be reordered
fn repartition_does_not_destroy_sort_more_complex() -> Result<()> { | ||
// model a more complicated scenario where one child of a union can be repartitioned for performance | ||
// but the other can not be | ||
// |
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.
This plan is close to what we have in IOx
FYI, we were aware of this and actually are currently exploring sort-preserving repartitions -- if we are successful, we will have both ways soon 🙂 This fix LGTM in the meantime (or if we fail). |
FYI, we were aware of this and actually are currently exploring sort-preserving repartitions -- if we are successful, we will have both ways soon 🙂 This fix LGTM in the meantime (or if we fail). Thank you @ozankabak -- you may be interested in #4867 from @crepererum which proposes some other improvements to partitioning |
fix case where repartitioning destroys output order add tests coverage
d9b1601
to
eaa8349
Compare
let required_input_ordering = | ||
plan_has_required_input_ordering(plan.as_ref()); | ||
|
||
let can_reorder_child = if can_reorder { | ||
// parent of `plan` will not use any particular order | ||
|
||
// if `plan` itself doesn't need order OR | ||
!required_input_ordering || | ||
// child has no order to preserve | ||
child.output_ordering().is_none() | ||
} else { | ||
// parent would like to use the `plan`'s output | ||
// order. | ||
|
||
// if `plan` doesn't maintain the input order and | ||
// doesn't need the child's output order itself | ||
(!plan.maintains_input_order() && !required_input_ordering) || | ||
// child has no ordering to preserve | ||
child.output_ordering().is_none() | ||
}; |
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.
This is the logic I eventually arrived at after a lot of thought. I tried to document the rationale as best I could -- perhaps @ozankabak and @mustafasrepo might have a few minutes to double check this. I believe this was first introduced by #4691
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.
Sure; will take a look, discuss with the team and get back to you.
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.
@alamb I checked the snippet, and tried a couple of alternatives with no success :). I think this logic is correct.
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.
Looks good to me as well
/// way for correctness. If this is true, its output should not be | ||
/// repartitioned if it would destroy the required order. | ||
fn plan_has_required_input_ordering(plan: &dyn ExecutionPlan) -> bool { | ||
// NB: checking `is_empty()` is not the right check! |
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.
I lost easily an hour trying to figure this out
Benchmark runs are scheduled for baseline = 8ab3a91 and contender = 9301133. 9301133 is a master commit associated with this PR. Results will be available as each benchmark for each run completes. |
fix case where repartitioning destroys output order add tests coverage
fix case where repartitioning destroys output order add tests coverage
Which issue does this PR close?
Fixes #4883
the fix is small logic fix to a condition. The rest of this PR is documentation and tests
Rationale for this change
The repartition optimizer pass is destroying a pre-existing sort order by repartitioning the data. The plan is actually producing correct answers (which is good) because the sort is added back afterwards, but it was doing so by resorting the data :(
There is much more backstory on #4883
What changes are included in this PR?
Are these changes tested?
yes, new tests
Without this change the tests fail like (added a repartition above the parquet exec)
Are there any user-facing changes?
I am not sure if this bug is hittable for other users. We hit it in IOx and I think UnboundedWindowExec and MergeJoin are susceptible to the same problem, but I am not sure how widely used they are