spark-test commented on code in PR #59823:
URL: https://github.com/apache/spark/pull/59823#discussion_r4392846978


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/HashAggregateExec.scala:
##########
@@ -1148,10 +1178,12 @@ case class HashAggregateExec(
            |  if (!$adaptivePassThroughTerm) {
            |    $processedRowsTerm += 1;
            |    if ($processedRowsTerm == $adaptiveNextCheckRowTerm) {
-           |      if ($adaptiveIneffective) {
+           |      if ($epochIneffective) {
            |        $adaptivePassThroughTerm = true;
            |      } else {
            |        $adaptiveNextCheckRowTerm += ${adaptiveMinRows}L;
+           |        $adaptiveWindowKeysTerm = $adaptiveCheckKeysTerm;
+           |        $adaptiveCheckKeysTerm = $adaptiveEpochKeys;

Review Comment:
   **Nit (P3):** Two transitions of the new key snapshots have no assertion 
that depends on them.
   
   1. The reset at a spill: `$adaptiveCheckKeysTerm = 0L` at L1079, and 
`checkKeys = 0L` in TungstenAggregationIterator L275. It matters only when a 
post-spill epoch fills between its first and second periodic checks, because 
`windowKeys` takes the value `checkKeys` held when the first check rotated it. 
Every forced-spill test refills at `2 * minRows` rows or later: the `4, 16` 
cells with `minRows = 8`, the 1.3 cell filling at 30 with `minRows = 10`, and 
the post-spill test filling at 40 with `minRows = 8`. So dropping the reset 
passes the suite, although such an epoch would then subtract the previous 
epoch's key count from its own.
   
   2. The fast-map term that `adaptiveEpochKeys` adds to the snapshot here. 
Every window test disables the two-level map. The one two-level exact-count 
test ('a new in-memory map epoch after a spill can still bypass') fills its 
4-key fast map within the first 4 rows, before any check. So snapshotting only 
`$hashMapTerm.getNumKeys()` changes its first spill check (id 44) from 12 rows 
over 0 keys to 12 over 4, and the `400 - 92` count still holds. With the 
default 2^16-key fast map and `minRows = 100000`, the fast map can still be 
gaining keys inside a window, so this term matters on the default codegen 
configuration.
   
   Could the suite cover a post-spill epoch that fills after exactly one 
periodic check, and a two-level codegen case whose fast map gains keys inside 
the judged window?
   
   **Verification:**
   - **Regression:** If the window's key snapshot were not reset at a spill, a 
run whose next epoch fills between its first and second periodic checks would 
produce a different passed-through count on both execution paths, and the 
coverage fails.
   - **Regression:** If the generated-code snapshot counted only regular-map 
keys, or counted fast-map keys twice, a two-level-map run with fast-map keys 
added inside the judged window would produce a different passed-through count, 
and the coverage fails.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/aggregate/AdaptivePartialAggregationSuite.scala:
##########
@@ -1310,6 +1423,42 @@ class AdaptivePartialAggregationSuite extends QueryTest 
with SharedSparkSession
     }
   }
 
+  test("the spill check judges the recent rows at their exact ratio boundary") 
{
+    // With `minRows = 10`, ids 0-9 add 9 keys and ids 10-29 add 10 more, two 
rows each, so when
+    // the forced fallback fills the map at 30 rows, the rows since the check 
before last (at 10

Review Comment:
   **Nit (P3):** When id 30 finds the map full, the periodic check at 30 rows 
has already run on id 29. So `nextCheckRow` is 40, and the spill check judges 
the rows since the check at 20 rows: ids 20-29, 10 rows over 5 keys (`10 < 5 * 
2.0` is false). It does not judge 20 rows over 10 keys since the check at 10.
   
   Ids 10-29 all add one key per two rows, so a window starting at 10 also 
gives exactly 2.0. Both assertions (0 and 10 passed-through rows) hold either 
way. The repeat-keys and 'compacts too little' tests do not tell those two 
starts apart either, so a regression that moved the window start one interval 
earlier would pass the suite.
   
   Could the comment describe rows 20-29, and could the input make ids 10-19 
add keys at a different rate from ids 20-29? For example:
   - ids 0-9 over 5 keys (two rows each);
   - ids 10-19 over 10 new keys (one per row);
   - ids 20-29 over 5 new keys (two rows each).
   
   The periodic checks still keep aggregating (2.0, 1.33 and 1.5), and the 
judged window stays at exactly 2.0. But a window from the check at 10 would see 
20 rows over 15 keys and pass through at 2.0, while the whole epoch (30 over 
20) passes through at both thresholds.
   
   This extends an [existing 
discussion](https://github.com/apache/spark/pull/59823#discussion_r4389483460).
   
   **Verification:**
   - **Behavior:** On both the codegen and interpreted paths, a task whose 
recent rows compact exactly at the spill threshold keeps aggregating and 
spills, and one held to a slightly higher threshold passes its remaining rows 
through, with exact passed-through and fallback counts.
   - **Regression:** If the spill-check window started one periodic interval 
earlier than the rows since the check before last, the boundary coverage fails 
on at least one execution path.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/TungstenAggregationIterator.scala:
##########
@@ -189,14 +190,18 @@ class TungstenAggregationIterator(
   // When adaptive partial aggregation is enabled 
(`adaptivePartialAggEnabled`), the processing may
   // stop early and switch to pass-through mode: the remaining input rows are 
not added to the map
   // but are instead emitted as single-row partial buffers by the output stage 
(see
-  // `nextPassThroughOutput`). One predicate decides both check points -- the 
aggregation is
-  // ineffective when `processedRows < distinctKeys * minCompaction`, i.e. it 
does not collapse
-  // `minCompaction` rows into one key:
-  //   - periodically, every `minRows` processed rows;
-  //   - when the map is about to spill, in which case the spill is skipped 
entirely and the row
-  //     that could not be inserted becomes the first pass-through row. 
Pass-through cannot be
-  //     undone within the task, so like the periodic check this waits until 
the current epoch has
-  //     processed `minRows` rows; a map that fills sooner spills as usual.
+  // `nextPassThroughOutput`). The aggregation is ineffective when `rows` 
processed rows do not
+  // collapse into their `keys` keys by at least `threshold`, checked at two 
points:
+  //   - periodically, every `minRows` processed rows, over the whole epoch 
against
+  //     `minCompaction`;
+  //   - when the map is about to spill, over the recent rows (those since the 
periodic check
+  //     before last) against the higher `minCompactionToSpill`. Keeping the 
aggregation then
+  //     means aggregating the rest of the input through spills, which pays 
off only if that input
+  //     keeps repeating keys; when keys repeat across the input, the ratio of 
the recent rows
+  //     climbs above the epoch's as the map fills. If the check passes, the 
map does not spill

Review Comment:
   **Nit (P3):** Right after the sentence about the recent ratio climbing as 
the map fills, 'If the check passes' reads as the ratio meeting 
`minCompactionToSpill`. But that is the case where the map spills and keeps 
aggregating. The code at L256-259 passes the rest through when 
`ineffective(...)` holds, i.e. when the recent rows compact by less than the 
threshold. The codegen comment and the tuning guide both state it that way 
round. Merge-base wording was unambiguous. A same-length rewording:
   
   ```suggestion
     //     climbs above the epoch's as the map fills. Below the threshold, the 
map does not spill
   ```
   
   **Verification:**
   - **Inspection:** The interpreted path's comment states that the map is not 
spilled and the rest of the input is passed through exactly when the recent 
rows compact by less than the spill threshold, matching the predicate that sets 
pass-through.



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