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


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/TungstenAggregationIterator.scala:
##########
@@ -191,29 +208,62 @@ class TungstenAggregationIterator(
       }
     } else {
       var i = 0
-      while (inputIter.hasNext) {
+      var processedRows = 0L
+      // The next row count at which the no-spill tier re-evaluates the 
reduction ratio. It starts
+      // at `sampleRows` and doubles after each sub-threshold check, so the 
ratio is checked only
+      // rarely once the input proves low-cardinality.
+      var nextSampleRow = 
adaptivePartialAggConfig.map(_.sampleRows.toLong).getOrElse(0L)
+      while (inputIter.hasNext && !passThrough) {
         val newInput = inputIter.next()
         val groupingKey = groupingProjection.apply(newInput)
         var buffer: UnsafeRow = null
         if (i < fallbackStartsAt._2) {
           buffer = hashMap.getAggregationBufferFromUnsafeRow(groupingKey)
         }
         if (buffer == null) {
-          val sorter = hashMap.destructAndCreateExternalSorter()
-          if (externalSorter == null) {
-            externalSorter = sorter
+          // The map is full and would normally spill. On the first spill, 
adaptive partial
+          // aggregation may instead bypass: keep the in-memory map as-is, 
pass this row and all
+          // remaining rows through, and skip the spill entirely.
+          if (adaptivePartialAggConfig.isDefined && externalSorter == null && 
processedRows > 0 &&
+              hashMap.getNumKeys().toDouble >=
+                processedRows * 
adaptivePartialAggConfig.get.spillReductionRatioThreshold) {
+            passThrough = true
+            // `newInput` could not be inserted; stash a copy as the first 
pass-through row so it
+            // is not lost when we drain the rest of `inputIter`.
+            pendingPassThroughRow = newInput.copy()
           } else {
-            externalSorter.merge(sorter)
+            val sorter = hashMap.destructAndCreateExternalSorter()
+            if (externalSorter == null) {
+              externalSorter = sorter
+            } else {
+              externalSorter.merge(sorter)
+            }
+            i = 0
+            buffer = hashMap.getAggregationBufferFromUnsafeRow(groupingKey)
+            if (buffer == null) {
+              // failed to allocate the first page
+              throw QueryExecutionErrors.aggregateOutOfMemoryError()
+            }
           }
-          i = 0
-          buffer = hashMap.getAggregationBufferFromUnsafeRow(groupingKey)
-          if (buffer == null) {
-            // failed to allocate the first page
-            throw QueryExecutionErrors.aggregateOutOfMemoryError()
+        }
+        if (!passThrough) {
+          processRow(buffer, newInput)
+          i += 1
+          processedRows += 1
+          // No-spill tier: from the sampling window on, if the map is still 
fully in memory and
+          // the reduction ratio is too high to be worthwhile, bypass partial 
aggregation for the
+          // rest. The window doubles after each sub-threshold check so 
low-cardinality input is
+          // re-evaluated only rarely while a late high-cardinality tail can 
still be caught.
+          if (adaptivePartialAggConfig.isDefined && externalSorter == null &&

Review Comment:
   good point, 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