Repository navigation
slt: WindowAggExec untracked allocation (failing test) - #423
Open
avantgardnerio wants to merge 1 commit into
Open
avantgardnerio wants to merge 1 commit into
avantgardnerio wants to merge 1 commit into
Conversation
avantgardnerio
force-pushed
the
brent/memory-accounting
branch
from
June 2, 2026 10:49
2ab0398 to
7455479
Compare
avantgardnerio
force-pushed
the
brent/window-agg-untracked-demo
branch
from
June 2, 2026 10:53
8c14bae to
a79aeef
Compare
WindowAggStream pushes every input batch into Vec<RecordBatch> with no MemoryReservation tracking, then concat_batches doubles it. DF's pool reports ~0 bytes from the operator while the heap holds the input twice. At memory_limit = 100M with the framework's default 10% headroom, the allocator bank fires once actual heap crosses ~110M. Test asserts the bank catches it via `statement error`. When DF closes the accounting gap, flip to `statement ok`. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> slt: flip to statement ok so the bug fails CI The point of this test is to make DataFusion's accounting gap visible — a green test catching an expected error hides the bug. `statement ok` is what DF *should* honor; the bank fires, statement fails, CI goes red. Fix WindowAgg's accounting → test passes. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
avantgardnerio
force-pushed
the
brent/window-agg-untracked-demo
branch
from
June 2, 2026 12:36
12049be to
c07f868
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a failing SLT that exposes a real DataFusion accounting bug via the framework in the parent PR.
WindowAggStreamholds references to every input batch for the whole stream without tellingMemoryPool. The bytes were allocated upstream — Window doesn't double them — but Window is the reason they aren't dropped. Because the pool never sees that retention, spill triggers and backpressure don't fire, and upstream operators keep allocating into a budget they think is free. The framework's allocator-level bank notices on the very next upstream alloc.The offending lines
Retention without accounting —
datafusion/physical-plan/src/windows/window_agg_exec.rs:429permalink
The push itself is just an
Arc::clone— the bytes are upstream's. But this is where they stop being eligible for drop. With 500 K rows × ~1 KB payload, that's ~500 MB of Arrow buffers kept alive whileMemoryPool::reserved()stays near zero.End-of-stream rebuild — same file, line 362:
permalink
concat_batchesallocates fresh buffers for primitive arrays; forUtf8Viewit can reuse the data buffers but still rebuilds the view list and null bitmap. Also unaccounted.WindowAggStreamhas no reservation field —grep -n "reservation\|try_grow\|MemoryConsumer\|MemoryReservation" window_agg_exec.rsreturns nothing.Why this matters in practice
A query with a single global window over wide rows under a tight pool runs to ~10× the declared budget on the heap.
MemoryPool::reserved()will report a small number throughout because nothing in this operator ever callstry_grow. Operators downstream of Window that do spill will never decide to, because the pool looks empty. Eventually a real allocation pushes us over the kernel's tolerance and the process dies — there's noResourcesExhaustederror to catch.Reproducer
Configuration is intentionally non-engineered: default 10 % headroom from
accounting_pool.rs::HEADROOM_FACTOR, 100 MB pool.max(msg) OVER ()references the wide payload so the optimizer can't elide the carry throughWindowAggExec.Stack trace at the firing allocation
The crashing alloc is innocent on its own — a string-concat in the projection feeding Window. It's just the alloc that happens after Window has silently consumed the budget by retaining every prior batch.
Test plan
cargo test --features memory-accounting -p datafusion-sqllogictest --test sqllogictests -- memory_accounting_demo --default-pool-size-mb 100exits non-zero with the message above.