ranflarion opened a new pull request, #24677:
URL: https://github.com/apache/datafusion/pull/24677

   ## Which issue does this PR close?
   
   - Closes #24676.
   
   ## Rationale for this change
   
   The memory-limited fallback re-executes the build-side child: when the 
in-memory load fails with `ResourcesExhausted`, `initiate_fallback` calls 
`self.left.execute(0, ctx)` a second time and spills that stream — but the 
first execution already consumed part of the input. For a child that cannot be 
executed twice, the consumed batches are gone.
   
   Proof on current `main`, wrapping this file's own `build_left_table()` in an 
`ExecutionPlan` whose second `execute` returns an empty stream (the way an 
exhausted external reader behaves), under the 50-byte limit the existing 
memory-limited tests use:
   
   ```
   executions=2  result=Err(Internal("Left side produced no data to spill"))
   ```
   
   For a child whose stream resumes instead of restarting, the same shape loses 
the consumed prefix silently: embedding DataFusion 54.1.0 over a JVM-fed source 
we measured a 200k-row build side returning 134,464 rows with no error. 
Replayable inputs are also executed twice for no benefit.
   
   This PR moves the spill into the load: when a reservation fails inside 
`collect_left_input`, the batches buffered so far plus the remainder of the 
same stream are written to one spill file, and the shared `OnceAsync` now 
resolves to a `LeftLoad` enum (`InMemory(JoinLeftData)` or 
`Spilled(LeftSpillData)`) that every partition consumes. The build side is 
executed exactly once whether or not it fits, `SpillState::Pending` no longer 
carries the child plan, and an empty build side stays on the in-memory path 
instead of erroring as "no data to spill".
   
   ## What changes are included in this PR?
   
   `collect_left_input` takes an optional `SpillManager` (built up front, since 
by the time the limit is hit the stream is already partly consumed) and returns 
`LeftLoad`; a new `spill_left_input` writes buffered batches plus the stream 
remainder; `initiate_fallback` becomes 
`enter_memory_limited_mode(Arc<LeftSpillData>)`; the per-partition chunk reader 
opens the shared spill file directly instead of awaiting a spill future; the 
load reservation is created `with_can_spill(can_spill)` since the consumer now 
genuinely spills.
   
   ## Are these changes tested?
   
   New test `memory_limited_left_side_reads_the_child_once`: a one-shot child 
under the tight limit must produce the full result with `spill_count > 0` and 
exactly one `execute` of the build side — on unpatched `main` this scenario 
fails as shown above. All 43 `nested_loop_join` tests pass (the existing 
memory-limited tests cover every join type through the new path), 1783 crate 
tests pass, `./dev/rust_lint.sh` is clean.
   
   ## Are there any user-facing changes?
   
   Memory-limited nested loop joins over non-replayable inputs now return 
complete results instead of failing (or silently truncating), and the build 
side is no longer executed twice. No API changes; `LeftLoad`/`LeftSpillData` 
are `pub(crate)`.
   


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