decorrelate shuffle size from target_batch_size#543
Conversation
| ) -> SendableRecordBatchStream { | ||
| use futures::StreamExt as FuturesStreamExt; | ||
|
|
||
| if d_cfg.shuffle_batch_size == 0 { |
There was a problem hiding this comment.
this adds opt out behavior the users that want target_batch_size == shuffle_batch_size
| /// | ||
| /// Set to 0 (the default) to apply no override and inherit `datafusion.execution.batch_size`. | ||
| pub shuffle_batch_size: usize, default = 0 | ||
| pub shuffle_batch_size: usize, default = 8192 * 4 |
There was a problem hiding this comment.
I think this is a sane default, happy to tune it
511bae5 to
4b79da9
Compare
|
Could you share how does this perform over the remote benchmarks? |
this doesn't introduce any regressions & the differences seem to be mostly noise. |
|
If the difference is just within noise, I'd recommend to not merge this PR. The problem with this approach is that it implies double-coalescing batches: one time for adhering to standard's Citing the description:
We deliberately made the decision of not making these two values independent, mainly for avoiding double-coalescing and simplifying the setup. We prefer to keep to full pipeline under a single If you see that the benchmarks demonstrate that this decision was wrong, this is good to go, but otherwise I'd probably try to look for other perf opportunities. |
|
ah that make sense.
This was actually the main issue I wanted to address. I spoke with @jayshrivastava offline about this. The goal of this PR was mostly as a building block for #542 to make that PR smaller.
With this in mind ill implement some of the optimizations I have in mind for #542 and then open a PR if I see good results. thank you for the comments @gabotechs ill close this PR now |
if we can avoid the double-coalescing do you think it would be worth it to make |
Currently
DistributedConfig.shuffle_batch_sizesets thetarget_record_batch_sizefor all operators in a datafusion plan. This helps for buffering record batches before being sent over the network but it may cause users to unintentionally change thetarget_record_batch.target_record_batchandshuffle_batch_sizeshould be independent config values. (see other issue with this in #541)This PR re-introduces coalesce batching to provide the same buffering of arrow-flight data before being sent over the wire to minimize overhead. the target size for batches is set to
shuffle_batch_size.As @gabotechs mentions in #386, coalescing has a cost. It copies rows from source arrays into a new destination buffer. Looking at push_batch,
in_progress_arraysbriefly holds a pointer to each source array viaset_source(), andcopy_rows()is called against it on every push. Despite the name, copy_rows() does mostly zero-copy work: it takes a zero-copyslice()of the source array and pushes it into a Vec. It's cheap metadata bookkeeping, not a buffer copy.The actual copy is deferred to
finish_buffered_batch(), which calls concat over all the buffered slices to write them into a single pre-allocated, contiguous buffer. Because this is one bulk copy into pre-sized memory, we only pay for the writes not re-sizing buffers. Modern CPUs domemcpy-style bulk copies extremely fast, especially when the data stays in cache.The tuning insight is that as
shuffle_batch_sizeoutpacestarget_batch_size, the number of separate coalesce-and-send cycles shrinks.Consider 12 incoming record batches of 8k rows each. With no coalescing, that's 12 Arrow Flight writes over the wire. If we set
shuffle_batch_sizeto 32kb, we instead do 3 Flight writes, at the cost of coalescing 4 arrays into one buffer each time. That's a good trade: a handful of (possibly in-cache) CPU copies is cheaper than the per-message network overhead of sending many small batches over the wire.a tradeoff of local CPU/copy cost vs. the overhead of many small network packets.
At worst, this performs on par with what main currently does, with the added benefit of being clearer to users. A follow up to this PR is discussed in #542
context:
#541