[fix](nereids) Gate aggregate parent shuffle reuse by NDV stats#64892
[fix](nereids) Gate aggregate parent shuffle reuse by NDV stats#64892foxtail463 wants to merge 2 commits into
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
run buildall |
TPC-H: Total hot run time: 29420 ms |
TPC-DS: Total hot run time: 171785 ms |
019cd1b to
2de423e
Compare
ClickBench: Total hot run time: 25.24 s |
|
run buildall |
TPC-H: Total hot run time: 28727 ms |
TPC-DS: Total hot run time: 171183 ms |
ClickBench: Total hot run time: 25.15 s |
FE Regression Coverage ReportIncrement line coverage |
morrySnow
left a comment
There was a problem hiding this comment.
Thanks for this fix! The change from default-true to default-false in shouldUseParent when stats are missing is the right safety trade-off — preventing OOM is much more important than a potentially narrower shuffle. The approach of pre-resolving parent hash expressions against group-by expressions in visitPhysicalHashAggregate also simplifies shouldUseParent nicely.
I left a few inline comments for your consideration.
| PlanContext context) { | ||
| if (!context.getConnectContext().getSessionVariable().aggShuffleUseParentKey) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Good call — if there is no group expression at all, we cannot derive stats and should not gamble on the parent subset key.
| } | ||
| if (agg.hasSourceRepeat()) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
This is the core fix — previously returning true here meant the optimizer would use the narrower parent hash key even with no stats at all, which could lead to severe data skew and OOM. Returning false (fall through to the full group-by key) is the conservative and correct choice.
Consider adding a brief comment here explaining the rationale, e.g.:
// Without stats we cannot assess whether the parent subset key has enough
// NDV to avoid skew; fall back to the safe full group-by distribution.| if (exprIdSlotMap.containsKey(exprId)) { | ||
| parentHashExprs.add(exprIdSlotMap.get(exprId)); | ||
| } | ||
| return false; |
There was a problem hiding this comment.
Same pattern as above — hasUnknownStatistics returning true now correctly causes us to skip the parent subset optimization instead of blindly trying it.
| } | ||
| if (AggregateUtils.hasUnknownStatistics(parentHashExprs, aggChildStats)) { | ||
| return true; | ||
| return false; |
There was a problem hiding this comment.
Note: NDV exactly equal to LOW_NDV_THRESHOLD (1024) is treated as insufficient — this is consistent with how SplitAggMultiPhase also uses > (strictly greater), so the threshold boundary is uniform across callers. 👍
| expected.add(Lists.newArrayList(PhysicalProperties.createHash( | ||
| Lists.newArrayList(key1.getExprId(), key2.getExprId()), ShuffleType.REQUIRE))); | ||
| Assertions.assertEquals(expected, actual); | ||
| } |
There was a problem hiding this comment.
The childStatistics override returns null here, which exercises the aggChildStats == null → return false path. The test now correctly expects only the full group-by key distribution. Consider updating the comment above to reflect the new behavior (e.g., // When stats are null, parent subset should NOT be used).
| GroupExpression groupExpression = new GroupExpression(aggregate) { | ||
| @Override | ||
| public Statistics childStatistics(int idx) { | ||
| return childStats; |
There was a problem hiding this comment.
Nice boundary test — setNdv(AggregateUtils.LOW_NDV_THRESHOLD) (1024) and correctly expecting the parent key NOT to be used, since combinedNdv > LOW_NDV_THRESHOLD is false when NDV is exactly at the threshold.
| physicalHashAggregate( | ||
| physicalDistribute(any())))), | ||
| physicalDistribute( | ||
| physicalHashAggregate( |
There was a problem hiding this comment.
The physicalDistribute wrappers are now expected in the plan shape because shouldUseParent no longer returns true when stats are unknown (which is the case in this unit test). Previously, the parent subset key was blindly adopted, which could eliminate the distribute node. This test change correctly reflects the stricter stats gate — the full group-by key distribution is used, and the distribute is preserved.
This is an intended side effect of the fix, but worth confirming: is the plan shape here what you would expect to see in production queries after this change?
Problem Summary:
PhysicalHashAggregate could still enumerate a parent hash key that is only a strict subset of the group by keys when child statistics were missing or unknown. That allowed CBO to choose a narrower shuffle distribution without evidence that the parent key had enough NDV, which can concentrate data and lead to OOM.
Solution:
Require agg_shuffle_use_parent_key to pass a real stats gate before adding the parent subset distribution: child stats and parent key stats must be known, and the estimated parent-key group count must be greater than LOW_NDV_THRESHOLD. Keep the full group key distribution as the conservative fallback.