Skip to content

Implement LimitPushDown for ExecutionPlan - #9815

Closed
Lordworms wants to merge 6 commits into
apache:mainfrom
Lordworms:issue_9792
Closed

Lordworms wants to merge 6 commits into
apache:mainfrom
Lordworms:issue_9792

Conversation

@Lordworms

Copy link
Copy Markdown
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
Copy Markdown
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

Lordworms commented Mar 28, 2024 •

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

@mustafasrepo mustafasrepo Mar 28, 2024 •

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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.

@mustafasrepo mustafasrepo Apr 1, 2024 •

Copy link
Copy Markdown
Contributor

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

)
}
pub fn set_target_batch_size(&mut self, siz: usize) {
self.target_batch_size = siz;

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

)
}
pub fn set_target_batch_size(&mut self, siz: usize) {
self.target_batch_size = siz;

Copy link
Copy Markdown
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
Copy Markdown
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(

@berkaysynnada berkaysynnada Mar 28, 2024 •

Copy link
Copy Markdown
Contributor

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 Changes to the physical-expr crates optimizer Optimizer rules substrait Changes to the substrait crate labels Apr 1, 2024
@github-actions github-actions Bot removed sql SQL Planner logical-expr Logical plan and expressions physical-expr Changes to the physical-expr crates optimizer Optimizer rules substrait Changes to the substrait crate labels Apr 1, 2024
@Lordworms
Lordworms marked this pull request as ready for review April 2, 2024 01:03

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

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


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

Copy link
Copy Markdown
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

----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
Copy Markdown
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?

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

alamb commented Apr 4, 2024 •

Copy link
Copy Markdown
Contributor

Edit: moved conversation to #9792 (comment)

@alamb

alamb commented Apr 4, 2024

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jun 4, 2024

Copy link
Copy Markdown

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