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

Implement LimitPushDown for ExecutionPlan #9815

Closed
wants to merge 6 commits into from

Conversation

Lordworms
Copy link
Contributor

Which issue does this PR close?

Closes #9792

Rationale for this change

What changes are included in this PR?

Are these changes tested?

Are there any user-facing changes?

@github-actions github-actions bot added the core Core DataFusion crate label Mar 27, 2024
@github-actions github-actions bot added the sqllogictest SQL Logic Tests (.slt) label Mar 28, 2024
if let Some(global_limit) = plan.as_any().downcast_ref::<GlobalLimitExec>() {
let input = global_limit.input().as_any();
if let Some(_) = input.downcast_ref::<CoalescePartitionsExec>() {
return Ok(Transformed::yes(swap_with_coalesce_partition(global_limit)));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

add more rules then. currently only specify to the repartition.slt test

@Lordworms
Copy link
Contributor Author

Lordworms commented Mar 28, 2024

I got stuck in a plan like this
image
the panic error lies in
image
the reason that it panics is the output partition number of LocalLimitExec is 3. However, the LocalLimitExec has been through a UnionExec. I think the output partition could adjust to 1? or in this case, we could not do LimitPushdown? Could you please give me an answer? @alamb @mustafasrepo

@mustafasrepo
Copy link
Contributor

Output partition number of the UnionExec is the sum of partition number of its inputs. Hence we cannot rely on after UnionExec output partitioning is 1.

Comment on lines 124 to 125
CoalescePartitionsExec
--GlobalLimitExec: skip=0, fetch=5
Copy link
Contributor

@mustafasrepo mustafasrepo Mar 28, 2024

Choose a reason for hiding this comment

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

I think this change is not correct. We cannot push down GlobalLimitExec though CoalescePartitionsExec. However, we can convert following pattern

GlobalLimitExec: skip=0, fetch=5
--CoalescePartitionsExec

into

GlobalLimitExec: skip=0, fetch=5
--CoalescePartitionsExec
----LocalLimitExec: skip=0, fetch=5

if skip is larger than 0. LocalLimitExec should still have skip=0 where fetch is skip+global limit fetch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it so the CoalescePartitionExec will also be a pushdown terminator and we just add a new LocalLimitExec below it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't really understand the reason to add an extra LocalLimitExec, I think we could simply add a global fetch when hit the pattern.

Copy link
Contributor

@mustafasrepo mustafasrepo Apr 1, 2024

Choose a reason for hiding this comment

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

I agree, that would be better. We can have fetch support in CoalescePArtitionsExec

@@ -83,6 +83,9 @@ impl CoalesceBatchesExec {
input.execution_mode(), // Execution Mode
)
}
pub fn set_target_batch_size(&mut self, siz: usize) {
self.target_batch_size = siz;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of overwriting target_batch_size. We can add fetch: Option<usize>. CoalesceBatchesExec can emit when hit to this count also. As well as target_batch_size.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

_config: &ConfigOptions,
) -> Result<Arc<dyn ExecutionPlan>> {
// if this node is not a global limit, then directly return
if !is_global_limit(&plan) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we extend the rule to pushdown GlobalLimit's which are not at the top of the plan?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely, this is just a draft to test the exist .slt test

@@ -83,6 +83,9 @@ impl CoalesceBatchesExec {
input.execution_mode(), // Execution Mode
)
}
pub fn set_target_batch_size(&mut self, siz: usize) {
self.target_batch_size = siz;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think if the fetch count is larger than target_batch_size, this will introduce some incorrect behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll set to max()

impl LimitPushdown {}
fn new_global_limit_with_input() {}
// try to push down current limit, based on the son
fn push_down_limit(
Copy link
Contributor

@berkaysynnada berkaysynnada Mar 28, 2024

Choose a reason for hiding this comment

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

I believe we can also set a fetch count for CoalesceBatchesExec without changing the plan order. A global fetch count may be carried across the subtree until facing with breaking plan, but I don't know if it would bring more capability. Can there be plans which cannot swap with limit but also do not break the required fetch count?

@github-actions github-actions bot added sql SQL Planner logical-expr Logical plan and expressions physical-expr Physical Expressions optimizer Optimizer rules substrait labels Apr 1, 2024
@github-actions github-actions bot removed sql SQL Planner logical-expr Logical plan and expressions physical-expr Physical Expressions optimizer Optimizer rules substrait labels Apr 1, 2024
@Lordworms Lordworms marked this pull request as ready for review April 2, 2024 01:03
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @Lordworms -- I took a quick look of this PR

I am probably missing something obvious but I don't understand the need for the pushdown pass in the physical optimizer.

If the usecase is to get a limit closer to StreamingTableExec then maybe we can pushing the fetch to the CoalesceBatchesExec rather than the StreamingTableExec ?

It seems to me that a limit in the StreamingTable exec can likely be implemented more efficiently, and would already be handled by the existing Limit pushdown in the LogicalPlan.

Maybe @berkaysynnada or @mustafasrepo have some more context

@@ -167,6 +167,10 @@ impl ExecutionPlan for GlobalLimitExec {

// GlobalLimitExec requires a single input partition
if 1 != self.input.output_partitioning().partition_count() {
println!(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this println should be removed

@@ -123,7 +123,7 @@ Limit: skip=0, fetch=5
physical_plan
GlobalLimitExec: skip=0, fetch=5
--CoalescePartitionsExec
----CoalesceBatchesExec: target_batch_size=8192
----CoalesceBatchesExec: target_batch_size=8192 fetch= 5
------FilterExec: c3@2 > 0
--------RepartitionExec: partitioning=RoundRobinBatch(3), input_partitions=1
----------StreamingTableExec: partition_sizes=1, projection=[c1, c2, c3], infinite_source=true
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we also push the limit to the StreamingTableExec as well?

@@ -216,7 +238,8 @@ impl CoalesceBatchesStream {
match input_batch {
Poll::Ready(x) => match x {
Some(Ok(batch)) => {
if batch.num_rows() >= self.target_batch_size
if (batch.num_rows() >= self.target_batch_size
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need some tests of this new logic added to repartition exec

@Lordworms
Copy link
Contributor Author

Thanks @Lordworms -- I took a quick look of this PR

I am probably missing something obvious but I don't understand the need for the pushdown pass in the physical optimizer.

If the usecase is to get a limit closer to StreamingTableExec then maybe we can pushing the fetch to the CoalesceBatchesExec rather than the StreamingTableExec ?
actually it has been pushed to CoalesceBatchesExec, and then carries down to StreamingTableExec

It seems to me that a limit in the StreamingTable exec can likely be implemented more efficiently, and would already be handled by the existing Limit pushdown in the LogicalPlan.
I agree I should add this logic in LogicalPlan Phases, I will save this one to draft and work on #9873 first.

Maybe @berkaysynnada or @mustafasrepo have some more context
Thanks for your review and Sorry for my immature design, I'll complete it later.

@Lordworms Lordworms marked this pull request as draft April 4, 2024 01:10
@berkaysynnada
Copy link
Contributor

Thanks @Lordworms -- I took a quick look of this PR

I am probably missing something obvious but I don't understand the need for the pushdown pass in the physical optimizer.

If the usecase is to get a limit closer to StreamingTableExec then maybe we can pushing the fetch to the CoalesceBatchesExec rather than the StreamingTableExec ?

It seems to me that a limit in the StreamingTable exec can likely be implemented more efficiently, and would already be handled by the existing Limit pushdown in the LogicalPlan.

Maybe @berkaysynnada or @mustafasrepo have some more context

Thanks @alamb for the feedbacks. @Lordworms's strategy is actually intuitive and reasonable, but maybe we need another way to solve the problem.

If I summarize #9792, the problem is when a Limit exists above CoalesceBatches, CoalesceBatches waits until all rows are collected which are possibly not used after Limit. Therefore; we need CoalesceBatches to sense the fetch count of the Limit, and after that many rows are collected, it should be able to return them without waiting more.

@alamb
Copy link
Contributor

alamb commented Apr 4, 2024

Edit: moved conversation to #9792 (comment)

@alamb
Copy link
Contributor

alamb commented Apr 4, 2024

Maybe @berkaysynnada or @mustafasrepo have some more context
Thanks for your review and Sorry for my immature design, I'll complete it later.

No worries at all -- we are all sorting this out together @Lordworms . Thank you for helping push it along

Copy link

github-actions bot commented Jun 4, 2024

Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale PR has not had any activity for some time label Jun 4, 2024
@github-actions github-actions bot closed this Jun 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) Stale PR has not had any activity for some time
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Adding Fetch Support to CoalesceBatchesExec
4 participants