viirya commented on code in PR #25428:
URL: https://github.com/apache/datafusion/pull/25428#discussion_r4109889402


##########
datafusion/physical-plan/src/sorts/multi_level_merge.rs:
##########
@@ -393,6 +411,35 @@ impl MultiLevelMergeBuilder {
                     }
                 };
 
+                if self.size_intermediate_merges
+                    && self.sorted_streams.is_empty()
+                    && !self.sorted_spill_files.is_empty()
+                {
+                    // If one intermediate merge can leave a final pass with 
the
+                    // admitted fan-in, merge only the runs needed to get 
there.
+                    // Keep the admitted reservation so sizing does not change

Review Comment:
   This keeps the full `k`-run grant while the pass merges only `R + 1` runs, 
then releases it when the intermediate stream is dropped after the write. The 
final pass then re-admits from an empty reservation at line 387. So the grant 
is held during the pass that doesn't need all of it, and released just before 
the pass that does.
   
   Since intermediate spill merges use `with_bypass_mempool()`, the grant is 
only accounting. For a sized pass it could stay in `self.reservation` instead 
of being attached to the intermediate stream. The pool would see exactly the 
same reserved bytes during the merge, and the next `self.reservation.take()` 
would hand them to the final admission, which already treats pre-reserved bytes 
as covering the first runs via `try_grow_reservation_to_at_least`. The final 
pass would then get its `k` slots whenever the new run is no wider than its 
inputs, which the uniform guard already expects. The other option is to leave 
one run of slack in `needed`, at the cost of some of the saving.



##########
datafusion/physical-plan/src/sorts/multi_level_merge.rs:
##########
@@ -985,6 +1033,148 @@ mod tests {
         )
     }
 
+    #[rstest::rstest]
+    #[case::full_batches(9, 128, 3)]
+    #[case::unchanged_selection(13, 128, 7)]
+    #[case::short_batches(9, 64, 7)]
+    #[tokio::test]
+    async fn intermediate_merge_sizing_preserves_final_output(

Review Comment:
   These cases run against a fresh pool with no other consumer, so the final 
admission always seats the same fan-in as the first. Could you add a case where 
a competing reservation takes part of the released grant between the 
intermediate write and the final admission, asserting that sizing rewrites no 
more rows than the unsized path? The `HandoffPool` probe from the earlier 
revision had the right shape: a `shrink` hook that lets a contender grow when 
the merge releases memory.



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