Skip to content

fix: spill a native sort's buffered batches in groups when their output merge does not fit - #22

Merged
msafonov merged 1 commit into
branch-1.1from
joom/1.1-spill-merge-degrade
Oct 17, 2026
Merged

msafonov merged 1 commit into
branch-1.1from
joom/1.1-spill-merge-degrade

Conversation

@msafonov

Copy link
Copy Markdown
Collaborator

Problem

merchants_lifecycle (em32 9/12/5) and gcs_cost_insights_v2 fail on 1.1-9/1.1-10/1.1-11 with
Additional allocation failed for ExternalSorterMerge[0] ... consumer holds ~fair limit ... 0 bytes overcommitted, 10/10 task attempts.

The failing path is the in-memory output merge of a sort that never spilled (not the spill-file merge, which bypasses the pool and already degrades). That merge keeps the sorted batches plus the rows RowCursorStream encodes each run's sort key into. With a multi-column key every buffered batch is its own run, so the rows can outgrow what sorting frees; once the sorter has filled its share there is nothing left to spill and the merge fails on a ~0.5-1.3 MB row-buffer grow (exact prod signature reproduced in a Rust test).

Fix

Before the in-memory output merge, estimate its memory (sliced size of each run + sampled row-encoding bytes/row x rows of its current chunk, +25%; nothing extra for single-column field cursors). If the pool cannot grant what exceeds the sorter's and merge's reservations, spill the buffered batches in consecutive groups, each sized so its merge fits its own reservation + merge headroom + what the pool grants, then merge the spill files with the existing multi-level merge (fan-in / read-ahead / split degradation). No infallible growth past the pool is added. Healthy sorts take the same path as before.

merge_uses_row_cursor moved to streaming_merge.rs and shared with multi_level_merge.rs.

Tests

  • New output_merge_that_does_not_fit_spills_instead_of_failing: without the fix ResourcesExhausted ... ExternalSorterMerge[0] ... 544 KB; with the fix it spills in groups, peak reserved <= pool limit, output equals the reference sort.
  • vendored datafusion-physical-plan: 1772 passed / 0 failed; core sort tests 27 passed; fmt, clippy -D warnings clean.

🤖 Generated with Claude Code

…ut merge does not fit

The in-memory output merge of a sort that never spilled holds the sorted
batches and the rows its cursor encodes the sort key of each run's batch
into. With a multi-column key every buffered batch is a run, so these rows
can outgrow what sorting frees, and the merge failed with "Additional
allocation failed for ExternalSorterMerge[...]" once the sorter had filled
its share of the pool, with nothing left to spill.

Before that merge, estimate its memory from the batches and a sampled row
encoding. If the pool cannot grant what exceeds the sorter's and the merge's
reservations, spill the batches in consecutive groups whose merges fit, and
merge the spill files with the multi-level merge, which already reduces its
fan-in and read-ahead to the memory it gets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the bug Something isn't working label Oct 17, 2026
@msafonov
msafonov merged commit c29796e into branch-1.1 Oct 17, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant