HeartSaVioR commented on code in PR #59048:
URL: https://github.com/apache/spark/pull/59048#discussion_r4162784560


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/AggUtils.scala:
##########
@@ -462,6 +463,13 @@ object AggUtils {
       val aggregateAttributes = aggregateExpressions.map(_.resultAttribute)
 
       ProjectAggregationBufferExec(
+        // A global aggregation must initialize its buffer exactly once per 
empty batch. Planning

Review Comment:
   My hunch is that we don't really need to do the projection (initializing 
buffer) in a single partition; instead, we can differentiate the process of 
global aggregation in StatefulStreamlineAggregateExec. With this, we still use 
all the partitions to initialize buffer for the data, and perform grouping 
operation in a single partition.
   
   In the other approach, the code changes of AggUtils and 
ProjectAggregationBufferExec aren't necessary. Instead, we make a code change 
in StatefulStreamlineAggregateExec to indicate the global aggregation and 
handle the data as such.
   
   This also fits with the approach of HashAggregateExec.



##########
sql/core/src/test/scala/org/apache/spark/sql/streaming/StreamlineStreamingAggregationRealTimeSuite.scala:
##########
@@ -131,4 +134,46 @@ class StreamlineStreamingAggregationRealTimeSuite extends 
StreamRealTimeModeSuit
       }
     )
   }
+
+  testWithAllStateVersions("global aggregation over empty input emits the 
initialized row") {
+    val inputData = LowLatencyMemoryStream[Int]
+    val sink = new ContinuousMemorySink()
+
+    val agg = inputData.toDF()
+      .filter($"value" < 0)
+      .agg(count("*").as("count"), sum("value").as("sum"))
+
+    // Update mode may re-emit the current row for no-data batches in 
real-time mode, so the
+    // assertions pin the value of every emitted row instead of counting rows.
+    testStream(agg, OutputMode.Update, sink = sink)(
+      StartStream(),
+      AddData(inputData, 1, 2),
+      Execute { _ =>
+        // Every row is filtered out, so the aggregation input is empty for 
this batch: only the
+        // initialized value may be emitted, as the ordinary plan does.
+        eventually(timeout(60.seconds)) {
+          assert(sink.allData.nonEmpty, "expected the initialized row for the 
empty batch")
+        }
+        assert(sink.allData.forall(_ == Row(0L, null)),
+          s"unexpected rows for the empty batch: ${sink.allData}")
+        sink.clear()

Review Comment:
   It looks like a potential race. Maybe we could logically deal with this via 
checking the result of the input for 1 and 2 (so there will be no further 
output), or some other way for synchronization.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/AggUtils.scala:
##########
@@ -462,6 +463,13 @@ object AggUtils {
       val aggregateAttributes = aggregateExpressions.map(_.resultAttribute)
 
       ProjectAggregationBufferExec(
+        // A global aggregation must initialize its buffer exactly once per 
empty batch. Planning

Review Comment:
   For sure, if this approach gets better performance we can go with it. I 
suspect this approach would/could be slower.



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