Repository navigation
Conversation
QinXi-ai
marked this pull request as ready for review
November 13, 2026 16:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Closes #25936 for eligible single-partition integer counts. This completes the spill and physical-selection steps in the issue's follow-up plan. It builds on #27299; that PR's inline-first commit is included here while it awaits merging.
Rationale for this change
The opt-in native path can run out of memory when one group's distinct values exceed the budget. Spilling its List state still requires rebuilding that group's set during replay. This change spills sorted
(group keys, value)rows and counts adjacent distinct values during merging, allowing a single group to exceed memory. NULL markers preserve groups whose count is zero.With bounded replay available, the physical optimizer can select native aggregation for a single integer distinct count whose actual input has one partition. Parallel plans retain the existing rewrite.
What changes are included in this PR?
Drain accumulator state in bounded batches while accounting for retained hash state, conversion buffers, and sorting workspace. Size fixed-width batches from their types, and use the available conversion headroom to reduce the number of spill runs. Sorting omits constant group keys in single-group runs and the value column when a batch has one row per group. Replay compares typed integers and updates its reservation at batch and group boundaries.
Add
SingleDistinctAggregationafter distribution enforcement and partial/final combination. It fuses eligible adjacent aggregates into the existingAggregateExec(Single). The existingsingle_distinct_prefer_nativesetting defaults totrue; actual input partitioning determines eligibility. Unsupported shapes, including filtered, ordered, mixed, and multiple aggregates, keep their existing plans.The cross-crate additions are
DistinctCountValues,GroupsAccumulator::supports_distinct_count_spill, andGroupsAccumulator::take_distinct_count_values. They are documented and hidden as internal APIs, and the trait methods have defaults. The execution-plan and aggregate-state formats remain unchanged. The lockfile uses the patchedquinn-proto0.11.19 andquinn-udp0.5.16 releases.Performance
Compared with main
55de8615on a shared Xeon Gold 6238T host, Rust 1.99.1,release-nonlto, CPU affinity 0–7, and four million preloaded rows. Two rounds reverse version order, discarding the first iteration of each run: eight retained samples per normal case and six per spill case. Builds and tests were idle during measurement. Times below are medians.One input partition, greedy / 1 GiB:
One input partition, greedy / 16 MiB:
Four target partitions, greedy / 1 GiB; these plans retain parallel aggregation:
The main tradeoffs are the NULL-heavy spill case and extra reservation for hash-table growth and conversion. The memory figures are pool reservations, not process RSS. Feedback on default selection would be particularly useful for that NULL-heavy case.
What is the testing strategy for this PR?
Forced-spill tests exercise groups larger than the budget, all eight integer types, signed and unsigned limits, cross-run duplicates, NULL-only groups, filters, dictionary keys, concurrent wide keys, greedy/fair pools, and resource cleanup. Planner integration tests cover actual versus configured partitions, expressions and compound keys, unsupported shapes, and ordered input. SQL tests cover the default, fallback, and low-memory execution paths.
The extended workspace suite passed 13,366 Rust tests, with 8 ignored, and all 305 SQL files. All 115 CLI tests passed. Validation also includes strict all-targets/all-features Clippy, the full lint suite, and 88 successful benchmark executions.
Are there any user-facing changes?
Eligible single-input-partition integer distinct counts use native aggregation by default and can spill a group larger than memory. Setting
single_distinct_prefer_native=falsedisables the physical fusion. Aggregates that already have a native shape, such as Q12, still use the bounded spill implementation.