ranflarion commented on code in PR #24820:
URL: https://github.com/apache/datafusion/pull/24820#discussion_r3986296787


##########
datafusion/physical-plan/src/joins/nested_loop_join.rs:
##########
@@ -1187,45 +1245,50 @@ fn is_spillable_oom(
         )
 }
 
-/// Write the already-buffered left batches plus the remainder of the same 
stream to one spill file.
+/// Write the already-completed chunks plus the remainder of the same stream 
to one spill file.
+/// The remainder keeps flowing through the same coalescer, so the file holds 
uniformly sized
+/// chunks and the memory-limited replay reads them back at that granularity.
 /// Returns `None` when the left side carried no rows at all, which needs no 
spill file.
 async fn spill_left_input(
     spill_manager: SpillManager,
     schema: SchemaRef,
-    buffered: Vec<RecordBatch>,
-    pending: Option<RecordBatch>,
+    chunks: Vec<RecordBatch>,
+    mut coalescer: BatchCoalescer,
     mut stream: SendableRecordBatchStream,
     metrics: BuildProbeJoinMetrics,
     reservation: &MemoryReservation,
 ) -> Result<Option<LeftSpillData>> {
     let mut spill_file =
         spill_manager.create_in_progress_file("NestedLoopJoin left spill")?;
 
-    for batch in buffered {
+    for batch in chunks {
         if batch.num_rows() > 0 {
             spill_file.append_batch(&batch)?;
         }
     }
-    // The in-memory batches are spilled and dropped, so their reservation 
goes back to the pool
-    // before the rest of the stream is drained.
+    // The in-memory chunks are spilled and dropped, so their reservation goes 
back to the pool
+    // before the rest of the stream is drained; only the coalescer's one 
in-progress chunk
+    // stays resident past this point.
     reservation.free();
-
-    for batch in pending.into_iter() {
-        if batch.num_rows() > 0 {
-            metrics.build_input_batches.add(1);
-            metrics.build_input_rows.add(batch.num_rows());
-            spill_file.append_batch(&batch)?;
-        }
+    while let Some(chunk) = coalescer.next_completed_batch() {
+        spill_file.append_batch(&chunk)?;
     }
 
     while let Some(batch) = stream.next().await {
         let batch = batch?;
         if batch.num_rows() > 0 {
             metrics.build_input_batches.add(1);
             metrics.build_input_rows.add(batch.num_rows());
-            spill_file.append_batch(&batch)?;
+            coalescer.push_batch(batch)?;
+            while let Some(chunk) = coalescer.next_completed_batch() {
+                spill_file.append_batch(&chunk)?;
+            }

Review Comment:
   Yes, reproduced. Fixed in cc8badfff by finishing the coalescer's partial 
chunk when the load spills (its inputs were all reserved, so that copy is 
inside the budget the pool already granted) and writing the rest of the stream 
to the spill file batch by batch as it arrives, the way the base did.



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