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


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/HashAggregateExec.scala:
##########
@@ -663,46 +851,123 @@ case class HashAggregateExec(
       case _ => ("true", "", "")
     }
 
-    val findOrInsertRegularHashMap: String =
+    // The compaction ratio is measured at the operator level: all processed 
rows against the keys
+    // held by both maps, so two-level-map routing does not change the 
decision. The same predicate
+    // decides both check points -- periodically every `minRows` rows, and 
right before the map
+    // would spill (in which case the spill is skipped entirely). `minRows = 
0` disables the
+    // periodic check: the row count is only ever compared after being 
incremented past 0, so it
+    // never matches and only the spill check remains.
+    val adaptiveIneffective = if (adaptivePartialAggEnabled) {
+      val totalKeys = if (isFastHashMapEnabled) {
+        s"($fastHashMapTerm.getNumKeys() + $hashMapTerm.getNumKeys())"
+      } else {
+        s"$hashMapTerm.getNumKeys()"
+      }
+      s"$processedRowsTerm < (double) $totalKeys * ${adaptiveMinCompaction}D"
+    } else {
+      ""
+    }
+
+    // After a spill the map starts a new in-memory epoch, so the counters 
restart and the ratio of
+    // that epoch alone decides whether the remaining rows are passed through.
+    val adaptiveResetEpoch = if (adaptivePartialAggEnabled) {
       s"""
-         |// generate grouping key
-         |${unsafeRowKeyCode.code}
-         |int $unsafeRowKeyHash = ${unsafeRowKeyCode.value}.hashCode();
-         |if ($checkFallbackForBytesToBytesMap) {
-         |  // try to get the buffer from hash map
-         |  $unsafeRowBuffer =
-         |    $hashMapTerm.getAggregationBufferFromUnsafeRow($unsafeRowKeys, 
$unsafeRowKeyHash);
-         |}
-         |// Can't allocate buffer from the hash map. Spill the map and 
fallback to sort-based
-         |// aggregation after processing all input rows.
-         |if ($unsafeRowBuffer == null) {
-         |  if ($sorterTerm == null) {
-         |    $sorterTerm = $hashMapTerm.destructAndCreateExternalSorter();
-         |  } else {
-         |    
$sorterTerm.merge($hashMapTerm.destructAndCreateExternalSorter());
-         |  }
-         |  $resetCounter
-         |  // the hash map had be spilled, it should have enough memory now,
-         |  // try to allocate buffer again.
-         |  $unsafeRowBuffer = $hashMapTerm.getAggregationBufferFromUnsafeRow(
-         |    $unsafeRowKeys, $unsafeRowKeyHash);
-         |  if ($unsafeRowBuffer == null) {
-         |    // failed to allocate the first page
-         |    throw QueryExecutionErrors.aggregateOutOfMemoryError();
-         |  }
-         |}
+         |$processedRowsTerm = 0L;

Review Comment:
   Addressed. You are right that the two sides of the ratio were on different 
definitions: the processed-row
   count restarts after a spill and the regular map's keys go with it, but the 
fast map neither
   spills nor clears, so its keys carried into the next epoch's denominator. 
The fast-map key count
   is now snapshotted at each spill and subtracted, putting both sides on the 
same epoch. Once the
   fast map fills it stops accepting keys, so the difference settles at zero 
and the denominator is
   the regular map's alone.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala:
##########
@@ -4156,6 +4156,48 @@ object SQLConf {
     .booleanConf
     .createWithDefault(false)
 
+  val ADAPTIVE_PARTIAL_AGGREGATION_ENABLED =
+    
buildConf("spark.sql.execution.aggregate.adaptivePartialAggregation.enabled")
+      .doc("When true, hash aggregation adaptively bypasses the pre-shuffle 
partial aggregation " +
+        "at runtime when it observes that the partial aggregation is not 
reducing the number of " +
+        "rows enough to be worthwhile. Once bypassed, the remaining input rows 
are passed " +
+        "through as single-row partial aggregation buffers for the final 
aggregation to merge, " +
+        "which avoids the cost of maintaining and spilling a large aggregation 
map with little " +
+        "reduction benefit. This applies only to hash aggregation with 
grouping keys.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(true)
+
+  val ADAPTIVE_PARTIAL_AGGREGATION_MIN_ROWS =
+    
buildConf("spark.sql.execution.aggregate.adaptivePartialAggregation.minRows")
+      .doc("The number of rows to process before adaptive partial aggregation 
(see " +
+        s"'${ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key}') evaluates the 
compaction ratio. The " +
+        "ratio is evaluated once this many rows have been processed since the 
previous " +
+        "evaluation, so a decision is never made on too few rows. A value of 0 
disables the " +
+        "periodic evaluation entirely, leaving only the check made when the 
aggregation map is " +
+        "about to spill.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .longConf
+      .checkValue(_ >= 0, "The minimum row count must not be negative.")
+      .createWithDefault(100000)
+
+  val ADAPTIVE_PARTIAL_AGGREGATION_MIN_COMPACTION =
+    
buildConf("spark.sql.execution.aggregate.adaptivePartialAggregation.minCompaction")
+      .doc("The minimum compaction ratio required to keep the pre-shuffle 
partial aggregation " +
+        s"(see '${ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key}'). The compaction 
ratio is the " +
+        "number of processed rows divided by the number of keys held in the 
aggregation maps, " +
+        "so a ratio of 10 means the partial aggregation collapses ten rows 
into one. When the " +
+        s"ratio is below this value after 
'${ADAPTIVE_PARTIAL_AGGREGATION_MIN_ROWS.key}' rows, " +
+        "or when the aggregation map is about to spill, the partial 
aggregation is bypassed for " +
+        "the rest of the input. A larger value bypasses more aggressively.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .doubleConf
+      .checkValue(_ >= 1.0, "The minimum compaction ratio must be at least 
1.0.")

Review Comment:
   Addressed



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