fix(physical-plan): honor distinct soft limits in SingleHashAggregateStream - #25158
fix(physical-plan): honor distinct soft limits in SingleHashAggregateStream#25158TinyMurky wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25158 +/- ##
==========================================
- Coverage 81.93% 81.93% -0.01%
==========================================
Files 1133 1133
Lines 423529 423546 +17
Branches 423529 423546 +17
==========================================
+ Hits 347028 347038 +10
- Misses 55910 55916 +6
- Partials 20591 20592 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
2010YOUY01
left a comment
There was a problem hiding this comment.
Thank you for working on this. I've left several suggestions.
| return Self::break_with_err(e); | ||
| } | ||
|
|
||
| // Soft limit optimization: |
There was a problem hiding this comment.
Can we move this comment to SingleHashAggregateStream, and here we can comment 'see comments at xxx for details'
Additionally we can follow the comment pattern in (first explain how the SQL get optimized to soft limit, and next the internal early termination mechanism)
There was a problem hiding this comment.
I added documentation to SingleHashAggregateStream explaining how the soft-limit optimization is planned and how early termination works:
datafusion/datafusion/physical-plan/src/aggregates/single_stream.rs
Lines 92 to 118 in e92aee7
I also changed the field documentation to:
datafusion/datafusion/physical-plan/src/aggregates/single_stream.rs
Lines 136 to 138 in e92aee7
to
/// See the "Optimization: DISTINCT LIMIT Soft Limit" section in
/// [`SingleHashAggregateStream`] for details.
| // Reuse the input-exhausted transition to merge any existing spills | ||
| // before producing output. The downstream limit operator enforces | ||
| // the exact output row count. | ||
| if self.hit_soft_group_limit(&hash_table) { |
There was a problem hiding this comment.
I suggest to skip this optimization if we have spilled before
Here is the pattern to follow, and also the explanaiton
datafusion/datafusion/physical-plan/src/aggregates/hash_stream.rs
Lines 857 to 865 in 7e5f40a
There was a problem hiding this comment.
I added the skip of spilling.
datafusion/datafusion/physical-plan/src/aggregates/single_stream.rs
Lines 485 to 498 in e92aee7
| return Self::break_with_err(e); | ||
| } | ||
|
|
||
| // Soft limit optimization: |
There was a problem hiding this comment.
and we can update control flow comment at poll_next to briefly mention this optimization change
There was a problem hiding this comment.
I changed the comment of poll_next as following:
datafusion/datafusion/physical-plan/src/aggregates/single_stream.rs
Lines 793 to 805 in e92aee7
| Ok(()) | ||
| } | ||
|
|
||
| #[tokio::test] |
There was a problem hiding this comment.
I recommend to write this test differently (follow the pattern in 40a6454#diff-02af0439a3df656429990b220b80e50d8df259ce45c47e008460c1ca3781aca3)
The main difference is
- Try to exercise this feature end-to-end, from
select distinctquery, and get it optimized to aggregate with soft limit - Also assert the internal metric of
AggregateExec, otherwise we can't ensure if this soft limit optimization is applied -- limit can also be enforced by the downstreamLimitExecoperator.
(I think only such e2e test is enough, we don't have to test it individually on AggregateExec, since this optimization is only useful from such SQL patterns, and should not be directly used on the AggregateExec)
There was a problem hiding this comment.
I removed the test in single_stream.rs, and add an e2e test in datafusion/core/tests/physical_optimizer/limited_distinct_aggregation.rs
| /// When set, there are no aggregate expressions: AggregateExec routes | ||
| /// limited non-DISTINCT aggregates to a different stream. |
There was a problem hiding this comment.
Maybe we can also change it to 'see top comments for details'.
There was a problem hiding this comment.
I changed the comment of group_values_soft_limit to
datafusion/datafusion/physical-plan/src/aggregates/single_stream.rs
Lines 136 to 138 in e92aee7
|
Thanks for the review! |
…Stream ## Which issue does this PR close? - Closes [apache#24980](apache#24980) ## Rationale for this change `SingleHashAggregateStream` ignores the distinct soft limit pushed into `AggregateExec`. As a result, single-stage `SELECT DISTINCT ... LIMIT n` queries consume all input even after enough distinct groups have been collected. This change stops input consumption once the in-memory hash table contains at least the requested number of distinct groups. Any existing spills are merged before producing output, and the downstream limit operator enforces the exact output row count. ## What changes are included in this PR? * Add a distinct soft-limit check to `SingleHashAggregateStream` after processing each input batch. * Reuse the input-exhausted transition when the soft limit is reached, preserving spill merging and output preparation. * Add unit tests covering early termination with and without spilling. - Update the `AggregateExec::limit_options` documentation to list `SingleHash` as supporting distinct soft limits. ## Are these changes tested? Added unit tests covering: * Reaching the soft limit without spilling. * Reaching the soft limit after spilling, preserving spilled groups and deduplicating overlapping groups. * Rejecting further input consumption after the soft limit is reached in the spill case. Following test commands have been executed and passed - `cargo test --profile=ci --test sqllogictests` - `cargo test -p datafusion` - `cargo test -p datafusion-cli` ## Are there any user-facing changes? Single-stage `DISTINCT` queries with a limit can stop consuming input earlier, reducing unnecessary work. Query semantics are unchanged.
- Add an end-to-end SQL test that verifies `SingleHashAggregateStream` stops consuming input when the distinct soft limit is reached before spilling. - Add e2e test to `limited_distinct_aggregation.rs`, in order to test whether distinct aggregate will truely stop reading input after it hit soft limit.
f7a7acb to
e92aee7
Compare
|
@2010YOUY01 Hi! Sorry for the delay. I've pushed a new commit addressing your review comments. When you have a chance, could you please take another look? Thanks!! |
Which issue does this PR close?
SingleHashAggregateStreamignores the distinct soft limit (lim=[n]) and reads all input #24980Rationale for this change
SingleHashAggregateStreamignores the distinct soft limit pushed intoAggregateExec. As a result, single-stageSELECT DISTINCT ... LIMIT nqueries consume all input even after enough distinct groups have been collected.This change stops input consumption once the in-memory hash table contains at least the requested number of distinct groups. Any existing spills are merged before producing output, and the downstream limit operator enforces the exact output row count.
If spilling has already occurred, the optimization is skipped to keep the spill and replay path simple. In that case, the stream consumes the remaining input and follows the existing spill merge path.
What changes are included in this PR?
SingleHashAggregateStreamafter processing each input batch.before spilling.
distinct aggregation.
AggregateExec::limit_optionsdocumentation to listSingleHashas supporting distinct soft limits.AggregateExec;aggregate;
reaching the soft limit;
Are these changes tested?
Added unit tests covering:
Following test commands have been executed and passed
cargo test --profile=ci --test sqllogictestscargo test -p datafusioncargo test -p datafusion-cliAre there any user-facing changes?
Single-stage
DISTINCTqueries with a limit can stop consuming input earlier, reducing unnecessary work. Query semantics are unchanged.