fix(physical-plan): honor distinct soft limits in SingleHashAggregateStream - #25158
fix(physical-plan): honor distinct soft limits in SingleHashAggregateStream#25158TinyMurky wants to merge 1 commit into
Conversation
…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.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25158 +/- ##
==========================================
- Coverage 81.90% 81.90% -0.01%
==========================================
Files 1132 1132
Lines 420155 420296 +141
Branches 420155 420296 +141
==========================================
+ Hits 344126 344239 +113
- Misses 55742 55756 +14
- Partials 20287 20301 +14 ☔ 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)
| // 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
| 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
| 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)
| /// 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'.
|
Thanks for the review! |
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.
What changes are included in this PR?
SingleHashAggregateStreamafter processing each input batch.AggregateExec::limit_optionsdocumentation to listSingleHashas supporting distinct soft limits.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.