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

   ## Which issue does this PR close?
   
   - N/A (design discussed offline; can file a tracking issue if preferred)
   
   ## Rationale for this change
   
   Operators bound their output batches by row count 
(`datafusion.execution.batch_size`) only. With wide rows (large string values, 
wide schemas) a batch within the row limit can still be arbitrarily large in 
bytes — multi-GB batches break memory accounting assumptions and cause OOMs. 
Conversely, streams of many tiny batches waste per-batch overhead.
   
   The existing `BatchSplitStream` on `DataSourceExec` splits by rows only, and 
splitting by zero-copy `slice` cannot release memory anyway: a slice of a 4GB 
batch still pins all 4GB of buffers (arrow's `concat` also short-circuits 
single inputs to a zero-copy slice).
   
   This PR adds a **batch normalizer**: a stream wrapper that re-chunks data 
source output towards a target row count *and* byte size, opt-in via a new 
config option.
   
   ## What changes are included in this PR?
   
   1. `BatchNormalizer` / `BatchNormalizerStream` 
(`datafusion/physical-plan/src/batch_normalizer.rs`). Per input batch, one of 
four actions:
      - **Pass through** (zero copy): rows ≥ `batch_size / 2` or logical bytes 
≥ `target / 2`, up to `2x` the byte target. The wide acceptance band means 
near-target batches are never copied.
      - **Coalesce**: small batches are buffered (via arrow `BatchCoalescer`) 
and flushed when the buffer reaches the row target **or** the byte target, 
whichever first.
      - **Split**: oversized batches (by rows or bytes) are re-emitted as 
compact ~target-sized copies (`take_record_batch` + view-array GC), produced 
incrementally (one chunk per poll) so peak memory is the parent plus ~one 
chunk. Copying is load-bearing: zero-copy slices would keep the oversized 
parent alive.
      - **Compact**: batches that retain far more memory than they logically 
use (> 2x and > 1MiB waste, e.g. a small slice pinning a huge parent, or sparse 
StringView data buffers) are copied so the backing buffers can be freed.
   2. New config `datafusion.execution.target_batch_size_bytes` (default `None` 
= unchanged behavior). When set, `DataSourceExec` wraps its output in 
`BatchNormalizerStream` instead of the row-only `BatchSplitStream`.
   3. Operator metrics: `batches_passed_through`, `batches_coalesced`, 
`batches_split`, `batches_compacted`.
   
   Planned follow-ups (intentionally not in this PR): memory-pool reservation 
for the normalizer's buffer with a spill fallback (following the 
`RepartitionExec` `OutputChannel`/`SpillPool` pattern), and byte-aware bounding 
for operator emit paths (join/aggregate output amplification is not addressable 
at the scan).
   
   ## Local benchmark observations (laptop, noisy — GKE runs requested)
   
   Interleaved off/on A/B (2 rounds, per-query minimums, same binary, 16MiB 
target via `DATAFUSION_EXECUTION_TARGET_BATCH_SIZE_BYTES`):
   
   - TPC-H SF1: total off 623ms vs on 678ms. Operator metrics confirm the 
normalizer is pure zero-copy pass-through on these scans (e.g. Q1/Q6: 742/742 
`batches_passed_through`, 0 copies), so the residual delta is per-batch size 
measurement overhead and/or laptop noise (off-vs-off noise floor spans 
0.66-1.22x per query).
   - ClickBench partitioned: total off 21.9s vs on 22.4s (+2.6%; noise floor 
p90 1.13x). String-heavy group-by queries (Q13/Q17/Q21) show 1.19-1.25x, 
slightly above the noise band — likely the StringView waste-compaction path; 
being investigated with operator metrics. Improvements to 0.81x on Q32/Q33.
   
   Laptop numbers are close to the noise floor; treating the dedicated 
benchmark runner as the source of truth.
   
   ## Are these changes tested?
   
   Yes — written test-first (TDD):
   
   - 18 unit tests covering pass-through zero-copy guarantees (pointer 
equality), coalesce flush on rows/bytes, incremental split with compaction 
guarantees (chunks must not retain the parent's buffers, including the 
StringView GC case), waste compaction and its thresholds, ordering across 
buffered/pass-through boundaries, single-giant-row and zero-column edge cases, 
metrics, and the stream adapter.
   - 3 end-to-end tests exercising the config through `SessionContext` scans.
   - Extended workspace test suite passes; config docs and 
`information_schema.slt` regenerated.
   
   ## Are there any user-facing changes?
   
   New opt-in config option `datafusion.execution.target_batch_size_bytes` 
(documented in `configs.md`). Default behavior is unchanged. No breaking API 
changes.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_01UcPTREZVLXsSZDRCae33gm
   


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