ulysses-you commented on code in PR #57742:
URL: https://github.com/apache/spark/pull/57742#discussion_r3735260186


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/aggregate/AdaptivePartialAggregationSuite.scala:
##########
@@ -0,0 +1,961 @@
+/*
+ * 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.spark.sql.execution.aggregate
+
+import org.apache.spark.sql.{DataFrame, QueryTest, Row}
+import org.apache.spark.sql.catalyst.expressions.aggregate.Partial
+import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
+import org.apache.spark.sql.functions._
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.test.SharedSparkSession
+
+/**
+ * Tests for runtime adaptive partial aggregation
+ * (see [[SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED]]). When a partial 
aggregate is not reducing
+ * rows, the operator stops aggregating and streams the remaining rows through 
as single-row partial
+ * buffers for the Final aggregate to merge. It must never change results.
+ *
+ * The suite has two halves:
+ *   1. Correctness: output is identical to the reference (feature-off) run 
across the full matrix
+ *      of codegen on/off, two-level map on/off, and spill/no-spill, over a 
range of aggregate
+ *      shapes, key types, and `Expand`-bearing plans (ROLLUP / CUBE / 
GROUPING SETS /
+ *      multi-distinct).
+ *   2. Triggering: the `numBypassingRows` metric proves the bypass actually 
fires when (and only
+ *      when) it should -- high-cardinality input bypasses, low-cardinality 
input keeps aggregating,
+ *      the feature switch and eligibility rules are honored, and both check 
points work.
+ */
+class AdaptivePartialAggregationSuite extends QueryTest with SharedSparkSession
+  with AdaptiveSparkPlanHelper {
+
+  import testImplicits._
+
+  // A `testFallbackStartsAt` setting ("fastMapCounter, regularMapCounter") 
that makes the regular
+  // map fall back (spill) periodically, exercising the spill-check decision 
path in both the
+  // codegen and interpreted aggregation paths. Kept moderate so 
low-cardinality inputs (which are
+  // never bypassed and therefore really spill) do not open an unbounded 
number of spill readers.
+  private val forceSpillFallback = "4, 16"
+
+  // The upstream `CombineAdjacentAggregation` and `ReplaceHashWithSortAgg` 
rules would change the
+  // plan of these small single-partition queries away from a Partial+Final 
`HashAggregateExec`:
+  // the former merges the two adjacent phases (no shuffle in between) into a 
single `Complete`
+  // aggregate, and the latter converts a hash aggregate to a sort aggregate 
when the input is
+  // already sorted by the grouping key (a `Range` over an ascending `id` 
key). The adaptive
+  // feature lives in the partial hash aggregation, so both rules are disabled 
to keep that
+  // structure in the tests.
+  private val fixedPlanConfs = Seq(
+    SQLConf.COMBINE_ADJACENT_AGGREGATION_ENABLED.key -> "false",
+    SQLConf.REPLACE_HASH_WITH_SORT_AGG_ENABLED.key -> "false")
+
+  /**
+   * Runs `df` with adaptive partial aggregation disabled (the reference) and 
then across the full
+   * configuration matrix with it enabled, asserting every enabled run matches 
the reference.
+   */
+  private def checkAdaptiveMatchesReference(build: () => DataFrame): Unit = {
+    val reference = withSQLConf(
+      (SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key -> "false") +: 
fixedPlanConfs: _*) {
+      build().collect().toSeq
+    }
+    for {
+      wholeStage <- Seq(true, false)
+      twoLevelMap <- Seq(true, false)
+      forceSpill <- Seq(true, false)

Review Comment:
   Confirmed and fixed in 87937dbd6d0 -- I measured the same zeros.
   
   The nullable case was 400 rows over 301 keys (1 in 4 null), a ratio of 1.33; 
it now uses 1 in 40, which bypasses in all eight cells. The GROUPING SETS case 
included a `()` grouping set, whose single grand-total key held the ratio up; 
it now uses `((k1, k2), (k1), (k2))` over distinct keys and bypasses too.
   
   ROLLUP and CUBE I got wrong at first. I assumed the grand-total set made 
them structurally unable to bypass, but the ratio for `d` distinct rollup 
columns is `(d+1)N / (dN+1)`, which tends to 1:
   
   ```
   ROLLUP d=2   ratio=1.500  bypassed=0
   ROLLUP d=3   ratio=1.333  bypassed=0
   ROLLUP d=5   ratio=1.200  bypassed=1184
   ROLLUP d=20  ratio=1.050  bypassed=4184
   ```
   
   At the two columns these tests use they do decline, which is a case worth 
covering on its own, so I documented that instead of reshaping them.
   
   One correction on the #28804 point: the argument does not rest only on these 
three. `pass-through fires for high-cardinality input below an Expand` was 
written for it and does bypass (1192 rows). Your finding still improved the 
suite -- three more tests now reach the feature.



-- 
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