[SPARK-58210][SQL][FOLLOWUP] Extend CombineAdjacentAggregation to partial merge - #57859
[SPARK-58210][SQL][FOLLOWUP] Extend CombineAdjacentAggregation to partial merge#57859cloud-fan wants to merge 3 commits into
Conversation
|
Thank you for extending this rule, @cloud-fan. The 1. Weakening the required distribution of the existing
Concretely, with 2. The
3. The combined Minor comments:
|
| } | ||
| } | ||
|
|
||
| test("Combine adjacent partial merge and final hash aggregates") { |
There was a problem hiding this comment.
Combine PartialMerge - Final to one FInal is sound good to me. But is there a valid end to end test ? I can not find a real world query can reach this pattern that all agg functions are PartialMerge and the adjacent agg functions are Final.
There was a problem hiding this comment.
Thanks for raising this. OSS does not currently produce a pure adjacent PartialMerge -> Final HashAggregate pair from a built-in query plan. The rule is intended to handle plans produced by physical-plan extensions that add an aggregation stage, and I updated the class documentation to say that explicitly.
The test now constructs a valid executable Partial -> PartialMerge -> Final pipeline, applies the rule to the upper pair, and compares the uncombined and combined execution results. This replaces the previous plan-shape-only coverage. I also added a separate end-to-end query test for the AQE rebalance/skew distribution regression.
|
Thanks for the detailed review. Addressed in 359d3dc:
I also updated the class documentation. The sort/object-hash paths continue retaining the final node distribution through For execution coverage, the PartialMerge test now builds a valid executable Partial -> PartialMerge -> Final pipeline and compares results before and after combining, in addition to its metadata and filter checks. OSS does not currently generate this pure adjacency from a built-in query; the documented use case is a physical-plan extension that introduces an extra aggregation stage. I attempted |
dongjoon-hyun
left a comment
There was a problem hiding this comment.
Thank you, @cloud-fan. This extension looks correct to me — I verified the key correctness points:
- Buffer binding:
Final-mode aggregates bind buffers positionally frominitialInputBufferOffset, and the combined node takes the lower aggregate's offset while its child keeps the samegrouping + bufferslayout. Correct. - AQE safety: since this rule runs after
EnsureRequirements, keeping the distribution requirement on the combined node is what letsValidateRequirementsblockOptimizeSkewInRebalancePartitionsfrom splitting rebalance partitions (which would produce wrong results). The second test covers exactly this scenario. - Streaming: real streaming aggregation plans have
StateStoreSaveExecbetweenFinalandPartialMerge, so this pattern cannot match them. - Mixed-mode (distinct) aggregates are excluded by the
forallchecks, and filteredPartialMergepairs are conservatively rejected with test coverage.
A few minor comments (none blocking):
- The PR description says "preserve the lower aggregate's child distribution", but the code keeps the upper (final) aggregate's
requiredChildDistributionExpressions(and the tests assert that). The code is right; the description could be updated. The described "requires the lower aggregate output to exactly match the final aggregate inputs" check also doesn't exist explicitly — it holds implicitly from the parent-child pattern. requiredChildDistributionExpressions = finalAgg.requiredChildDistributionExpressionsinsidefinalAgg.copy(...)is a no-op. If the intent is to document that the upper node's requirement is kept, a comment would be clearer — as written it may read as a typo forpartialAgg.….- The
Completepath now takesisStreaming/numShufflePartitionsfrom the lower aggregate (previously the final's were kept). Practically equivalent for plans Spark produces, butrequiredChildDistributionis now computed from fields of two different nodes (exprs from final, the rest from partial) — a short comment on why would help future readers. - The
IllegalArgumentExceptionbranch incombineHashAggregatesis unreachable sincecombinedModeonly returnsComplete/Final; returning the newaggregateExpressionsdirectly fromcombinedModewould remove the second match entirely. - The scaladoc reads as if
PartialMerge+Finalcombining applies generally, but onlyHashAggregateExechandles it;SortAggregateExec/ObjectHashAggregateExecremainPartial-only. Worth a note in the doc.
On tests: the new path is covered by a hand-assembled plan (understandable, since Spark core never produces this shape), and the AQE test is effectively a regression test for the pre-existing Complete path — asserting the shuffle actually had skewed partitions would make it non-vacuous, but that can be a follow-up.
Thank you again for the follow-up, @cloud-fan!
What changes were proposed in this pull request?
This is a follow-up to #57363. It extends
CombineAdjacentAggregationfor adjacent hashaggregates in two ways:
PartialMergefollowed byFinalinto oneFinalaggregate;when removing that lower aggregate.
The rule also requires the lower aggregate output to exactly match the final aggregate inputs.
Filtered
Partialaggregation remains supported, while aPartialMergepair with filters is notcombined.
Why are the changes needed?
CombineAdjacentAggregationcurrently handles onlyPartialfollowed byFinal. A compatibleadjacent
PartialMergeandFinalpair can also be collapsed safely, avoiding an unnecessary hashaggregation stage. When the lower aggregate is removed, its child-facing distribution and streaming
metadata must move to the combined node because the combined node now reads that lower aggregate's
original child.
This broadens the rule to optimize another safe adjacent-aggregation pattern while keeping the
combined operator's child requirements consistent with the plan subtree it now consumes.
Does this PR introduce any user-facing change?
Yes. Spark may produce a single final hash aggregate instead of adjacent partial-merge and final
hash aggregates when their grouping, lineage, and input/output attributes are compatible. Query
results are unchanged.
How was this patch tested?
Added coverage to
CombineAdjacentAggregationSuitefor:PartialMergeandFinalhash aggregates;PartialMergepair.Ran:
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Codex (GPT-5)