jayzhan211 opened a new issue, #26707:
URL: https://github.com/apache/datafusion/issues/26707

   ### Describe the bug
   
   `WindowAggExec` and `BoundedWindowAggExec` never reserve memory: there is no 
`MemoryConsumer` or `MemoryReservation` anywhere under 
`datafusion/physical-plan/src/windows/`. Both hold input rows for as long as 
their window needs them. `WindowAggExec` buffers its whole input before it 
computes anything, and `BoundedWindowAggExec` buffers the rows its frames still 
need.
   
   So a memory limit has no effect on them. A window query neither spills nor 
fails with `ResourcesExhausted`; it uses as much memory as its input needs, and 
in a container with a memory limit the process can be killed instead. The 
contract in `memory_pool/mod.rs` asks an operator whose memory grows with its 
input to reserve that memory before allocating it, and to spill or return an 
error when the pool refuses.
   
   ### To Reproduce
   
   Run each command in its own process (`/usr/bin/time -l` is macOS; use `-v` 
on Linux). Drop `-m 200m` for the "no limit" column.
   
   ```sh
   # Query 1: WindowAggExec, the whole input is one window partition
   /usr/bin/time -l datafusion-cli -m 200m \
     -c "SELECT max(s) FROM (SELECT sum(v) OVER () AS s FROM generate_series(1, 
50000000) AS t(v));"
   
   # Query 2: BoundedWindowAggExec, a frame that reaches 20M rows ahead
   /usr/bin/time -l datafusion-cli -m 200m \
     -c "SET datafusion.execution.batch_size = 1000000;" \
     -c "SELECT max(s) FROM (SELECT sum(v) OVER (ORDER BY v ROWS BETWEEN 
CURRENT ROW AND 20000000 FOLLOWING) AS s FROM generate_series(1, 40000000) AS 
t(v));"
   ```
   
   Query 2 uses a large batch size only to keep the run short: 
`BoundedWindowAggExec` re-concatenates its buffered rows for every input batch.
   
   ### Expected behavior
   
   | Query | Plan | Peak RSS, no limit | Peak RSS, `-m 200m` | Expected with 
`-m 200m` |
   |---|---|---|---|---|
   | 1 | `WindowAggExec` | 1186 MiB (3 runs) | 1193 to 1226 MiB, succeeds (6 
runs) | Fails with `ResourcesExhausted` once the buffered rows exceed the limit 
(or spills, after #22946) |
   | 2 | `BoundedWindowAggExec` | 2.8 to 3.2 GiB (5 runs) | 2.9 to 3.7 GiB, 
succeeds (10 runs) | Same as query 1 |
   
   Measured with `datafusion-cli` built from `main` at b93ef03be4 (release 
profile without LTO) on macOS arm64.
   
   ### Additional context
   
   - `WindowAggStream` keeps every input batch and concatenates them when its 
input ends: 
[window_agg_exec.rs#L502-L511](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/physical-plan/src/windows/window_agg_exec.rs#L502-L511),
 
[#L610](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/physical-plan/src/windows/window_agg_exec.rs#L610),
 
[#L545](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/physical-plan/src/windows/window_agg_exec.rs#L545).
 Query 1's peak is about three copies of its 381 MiB input (50M `Int64` 
values): the buffered batches, their concatenation, and the result column.
   - `BoundedWindowAggExec` keeps `input_buffer` and a buffer per window 
partition, and re-concatenates both for every input batch: 
[bounded_window_agg_exec.rs#L666-L707](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs#L666-L707),
 
[window_state.rs#L336-L341](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/expr/src/window_state.rs#L336-L341).
 Query 2's peak grows faster than its frame: 222 to 295 MiB with a 1-row frame, 
602 to 691 MiB at 5M rows, 1.1 GiB at 10M, and 2.9 to 3.7 GiB at 20M, while the 
frame itself is 8 bytes a row (38 to 153 MiB). Only the window's buffering 
depends on the frame, so at its peak the operator holds many times the frame. 
The two buffers and their re-concatenation explain about four copies; I haven't 
found the rest. A reservation has to count all of it.
   - The contract: 
[memory_pool/mod.rs#L68-L85](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/execution/src/memory_pool/mod.rs#L68-L85).
   - One direction: each window stream registers a `MemoryConsumer` that cannot 
spill. `WindowAggExec` grows its reservation for every buffered batch and for 
the concatenated copy; `BoundedWindowAggExec` resizes its reservation to its 
buffers after each batch and after pruning. A test in 
`datafusion/core/tests/memory_limit` that expects query 1 to return 
`ResourcesExhausted` at a small limit would fail today, because the query 
succeeds.
   - Trade-off to decide: queries that pass today with a limit that is too 
small would start failing with `ResourcesExhausted`, and in a shared pool the 
window operators would take memory that spilling operators use now. Comet 
closed a change that charged more memory to Spark's pool, because without a way 
to reclaim it, native spills turned into task failures 
(apache/datafusion-comet#5998).
   - Separately, `WindowAggExec` keeps one result array per window partition 
until a final `concat` 
([window_agg_exec.rs#L558-L570](https://github.com/apache/datafusion/blob/b93ef03be4d1a5c7d95330dbb971e49804e542be/datafusion/physical-plan/src/windows/window_agg_exec.rs#L558-L570)),
 so many small partitions cost far more than the input: `sum(v) OVER (PARTITION 
BY v)` over the same 50M rows peaks at 7.8 to 9.7 GiB. That needs its own fix.
   - Related: #22946 (spilling for `WindowAggExec`, which needs this accounting 
first), #23197 (window EPIC, "WindowAggExec Memory / Spilling"), #25637 
(spilling coverage gaps), #25650 (processes OOM killed due to inaccurate memory 
accounting), #23207 (an earlier accounting PR for `WindowAggExec`, closed 
without review).
   
   ### Willingness to contribute
   
   I'm not planning to fix this myself, so anyone is welcome to pick it up. 
Feel free to ping me with questions or when you'd like a review.
   


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