morrySnow commented on code in PR #64892:
URL: https://github.com/apache/doris/pull/64892#discussion_r3765927149


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/util/AggregateUtils.java:
##########
@@ -145,6 +148,33 @@ public static boolean 
hasUnknownStatistics(Collection<Expression> expressions,
         return false;
     }
 
+    /**
+     * Whether collected hot-value or null statistics prove that the combined 
shuffle keys are skewed.
+     * Missing hot-value statistics mean "no known skew", rather than 
disabling an NDV-based optimization.
+     */
+    public static boolean hasKnownSkewOnShuffleKeys(List<Expression> 
shuffleKeys, Statistics inputStatistics) {
+        for (int i = 0; i < shuffleKeys.size(); i++) {
+            ColumnStatistic columnStatistic = 
inputStatistics.findColumnStatistics(shuffleKeys.get(i));
+            if (columnStatistic == null || columnStatistic.isUnKnown) {
+                continue;
+            }
+            boolean hasHotValue = 
StatisticsUtil.getHotValuesWithOriginalThreshold(
+                    columnStatistic.getHotValues(), columnStatistic.ndv) != 
null;
+            double nullRatio = columnStatistic.numNulls / 
inputStatistics.getRowCount();
+            boolean hasHotNull = columnStatistic.numNulls > 0

Review Comment:
   `isHotValueWithOriginalThreshold(nullRatio, ndv)` applies the `ratio * ndv 
>= skewValueThreshold` clause to the null ratio. For nulls this clause fires at 
extremely small null fractions: since the caller only reaches this check when 
`combinedNdv > LOW_NDV_THRESHOLD` holds, any key with null ratio >= 10/ndv is 
flagged hot (e.g. ~0.5% nulls at ndv=2000, or 10 nulls per 1M rows at ndv=1M). 
Unlike `hotValues` (which the BE only collects when significant values exist), 
`numNulls` is always present in column stats, so this makes the rejection the 
common case rather than the exception. And when the parent key is a single 
column, `otherShuffleKeys` is empty, `estimateGroupByRowCount` returns 1 <= 
LOW_NDV_THRESHOLD, so the key is always treated as skewed. The actual 
concentration of the null bucket is exactly `nullRatio`, not `nullRatio * ndv` 
- note that the existing null convention in `hasSignificantHotValues` 
(StatisticsUtil.java:1086) uses only the plain ratio. As written, `aggShuffleU
 seParentKey` (default on) will be effectively disabled for any single-column 
parent hash key that contains nulls, forcing an extra shuffle. This is a 
performance regression rather than a correctness issue, but it is likely much 
broader than the OOM scenario described in the PR. Consider restricting the 
null check to the ratio clause or adding a minimum null-ratio floor.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildrenPropertiesRegulator.java:
##########
@@ -180,37 +175,7 @@ private boolean skewOnShuffleExpr(PhysicalHashAggregate<? 
extends Plan> agg) {
             return true;
         }
         // 2. There is a hot value, and the ndv of other keys is very low
-        return isSkew(agg.getGroupByExpressions(), inputStatistics);
-    }
-
-    // if one group by key has hot value, and others ndv is low -> skew
-    private boolean isSkew(List<Expression> groupBy, Statistics 
inputStatistics) {
-        for (int i = 0; i < groupBy.size(); ++i) {
-            Expression expr = groupBy.get(i);
-            ColumnStatistic colStat = 
inputStatistics.findColumnStatistics(expr);
-            if (colStat == null || colStat.isUnKnown) {
-                continue;
-            }
-            if 
(StatisticsUtil.getHotValuesWithOriginalThreshold(colStat.getHotValues(), 
colStat.ndv) == null) {
-                continue;
-            }
-            List<Expression> otherExpr = excludeElement(groupBy, i);
-            double otherNdv = 
StatsCalculator.estimateGroupByRowCount(otherExpr, inputStatistics);
-            if (otherNdv <= AggregateUtils.LOW_NDV_THRESHOLD) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static <T> List<T> excludeElement(List<T> list, int index) {
-        List<T> newList = new ArrayList<>();
-        for (int i = 0; i < list.size(); i++) {
-            if (index != i) {
-                newList.add(list.get(i));
-            }
-        }
-        return newList;
+        return 
AggregateUtils.hasKnownSkewOnShuffleKeys(agg.getGroupByExpressions(), 
inputStatistics);

Review Comment:
   This call site changes behavior beyond the parent-key reuse gating described 
in the PR description. The old `isSkew` here only considered collected hot 
values; the new `hasKnownSkewOnShuffleKeys` additionally treats a *hot-null* 
key as skew evidence. For a one-phase agg over a CTE consumer whose group-by 
keys all have `hotValues` collected (the `hasUnknownStatistics(..., true)` gate 
at line 168 passes only then), a key with >=10% nulls (or nulls >= 10x the 
average value frequency) plus other keys' NDV <= LOW_NDV_THRESHOLD will now ban 
the one-phase agg. If the null-based skew detection is only intended for the 
`shouldUseParent` decision in RequestPropertyDeriver, sharing the helper makes 
it leak into this path. Please confirm this is intended (and worth documenting 
in the PR description), or keep the null check in the deriver path only.



##########
fe/fe-core/src/test/java/org/apache/doris/nereids/util/AggregateUtilsTest.java:
##########
@@ -0,0 +1,107 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.util;
+
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.qe.SessionVariable;
+import org.apache.doris.statistics.ColumnStatisticBuilder;
+import org.apache.doris.statistics.Statistics;
+import org.apache.doris.statistics.StatisticsBuilder;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+class AggregateUtilsTest {
+
+    @Test
+    void testKnownSkewOnSingleHotValueKey() {
+        SlotReference key = new SlotReference("key", IntegerType.INSTANCE);
+        Statistics statistics = new StatisticsBuilder()
+                .setRowCount(10000)
+                .putColumnStatistics(key, new ColumnStatisticBuilder(10000)
+                        .setNdv(2000)
+                        .setHotValues(ImmutableMap.of(Literal.of(1), 0.2f))
+                        .build())
+                .build();
+
+        Assertions.assertTrue(AggregateUtils.hasKnownSkewOnShuffleKeys(
+                ImmutableList.of(key), statistics));
+    }
+
+    @Test
+    void testKnownSkewOnSingleNullKey() {
+        SlotReference key = new SlotReference("key", IntegerType.INSTANCE);
+        Statistics statistics = new StatisticsBuilder()
+                .setRowCount(10000)
+                .putColumnStatistics(key, new ColumnStatisticBuilder(10000)
+                        .setNdv(2000)
+                        .setNumNulls(5000)
+                        .setHotValues(ImmutableMap.of())
+                        .build())
+                .build();
+
+        Assertions.assertTrue(AggregateUtils.hasKnownSkewOnShuffleKeys(
+                ImmutableList.of(key), statistics));
+    }
+
+    @Test
+    void testKnownSkewDilutedByAnotherHighNdvKey() {
+        SlotReference hotKey = new SlotReference("hot_key", 
IntegerType.INSTANCE);
+        SlotReference highNdvKey = new SlotReference("high_ndv_key", 
IntegerType.INSTANCE);
+        Statistics statistics = new StatisticsBuilder()
+                .setRowCount(10000)
+                .putColumnStatistics(hotKey, new ColumnStatisticBuilder(10000)
+                        .setNdv(2000)
+                        .setNumNulls(5000)
+                        .setHotValues(ImmutableMap.of())
+                        .build())
+                .putColumnStatistics(highNdvKey, new 
ColumnStatisticBuilder(10000)
+                        .setNdv(2000)
+                        .setHotValues(ImmutableMap.of())
+                        .build())
+                .build();
+
+        Assertions.assertFalse(AggregateUtils.hasKnownSkewOnShuffleKeys(
+                ImmutableList.of(hotKey, highNdvKey), statistics));
+    }
+
+    @Test
+    void testMissingHotValueStatsAndNoNullAreNotKnownSkew() {
+        SlotReference key = new SlotReference("key", IntegerType.INSTANCE);
+        Statistics statistics = new StatisticsBuilder()
+                .setRowCount(10000)
+                .putColumnStatistics(key, new ColumnStatisticBuilder(10000)
+                        .setNdv(2000)
+                        .build())
+                .build();
+
+        try (MockedStatic<SessionVariable> mockedSessionVariable = 
Mockito.mockStatic(SessionVariable.class)) {

Review Comment:
   The mocked thresholds (0.0) do not affect the outcome of this test: the 
column has no nulls and no hot values, so `hasKnownSkewOnShuffleKeys` returns 
false regardless of the threshold values. The test passes trivially and does 
not exercise the 'hot null below/above threshold' boundary implied by the name 
and the mock. Consider adding `numNulls` to the column and asserting the result 
flips with a threshold change (e.g. mock skewValueThreshold to a tiny value so 
`nullRatio * ndv >= skewValueThreshold` becomes decisive), or drop the mock and 
state that the assertion is threshold-independent.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to