Skip to content

fix: reject max_buffered_batches_per_output_file values below 2 - #24204

Merged
2010YOUY01 merged 5 commits into
apache:mainfrom
DevShiba:fix/max-buffered-batches-min-two
Aug 11, 2026
Merged

fix: reject max_buffered_batches_per_output_file values below 2#24204
2010YOUY01 merged 5 commits into
apache:mainfrom
DevShiba:fix/max-buffered-batches-min-two

Conversation

@DevShiba

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

DataFusion CLI v54.1.0
> set datafusion.execution.max_buffered_batches_per_output_file = 1;
> COPY (SELECT 1 as a) TO '/tmp/x.parquet';
thread 'tokio-rt-worker' panicked at datafusion/datasource/src/write/demux.rs:287:30:
mpsc bounded channel requires buffer > 0

Two call sites (demux.rs::create_new_file_stream, orchestration.rs::spawn_writer_tasks_and_join) divide max_buffered_batches_per_output_file in half to size a bounded mpsc channel's capacity. Integer division rounds both 0 and 1 down to 0, and Tokio's mpsc::channel panics on a zero capacity. A plain non-zero check (like the existing ConfigNonZeroUsize) would not have been sufficient here, since 1 also triggers the panic.

A third call site (demux.rs::hive_style_partitions_demuxer) uses the raw value directly without dividing, so it would panic on 0 alone.

What changes are included in this PR?

Adds ConfigMinTwoUsize, mirroring the existing ConfigNonZeroUsize pattern already used for sibling fields (batch_size, meta_fetch_concurrency, minimum_parallel_output_files, etc.), and applies it to max_buffered_batches_per_output_file. Invalid values are now rejected with a clear configuration error at set-time instead of panicking later at write time. Updated the three read sites to call .get(), updated the field doc comment to explain the constraint, and regenerated docs/source/user-guide/configs.md via dev/update_config_docs.sh.

Are these changes tested?

Yes. Added two statement error cases to datafusion/sqllogictest/test_files/set_variable.slt (values 0 and 1), following the exact pattern already used for the sibling ConfigNonZeroUsize fields in that file. Verified manually with datafusion-cli that 0 and 1 now return a clean error instead of panicking, and that 2 (the default) and 3 still work correctly. Ran cargo test -p datafusion-datasource --lib (178 passed), the set_variable.slt sqllogictest suite, and cargo check --workspace --all-targets — all clean.

Are there any user-facing changes?

Yes: setting datafusion.execution.max_buffered_batches_per_output_file to 0 or 1 now returns a configuration error instead of panicking. No change for any value >= 2 (including the default of 2).

Setting datafusion.execution.max_buffered_batches_per_output_file to 0
or 1 panics at write time: two call sites (demux.rs, orchestration.rs)
halve the value to size a bounded mpsc channel's capacity, and
integer division rounds 0 or 1 down to 0, which Tokio's mpsc::channel
rejects with "mpsc bounded channel requires buffer > 0".
Reproduced directly:
set datafusion.execution.max_buffered_batches_per_output_file = 1;
COPY (SELECT 1) TO '/tmp/x.parquet';
-> panics
Add ConfigMinTwoUsize, mirroring the existing ConfigNonZeroUsize
pattern used for sibling fields (batch_size, meta_fetch_concurrency,
etc.), so the value is rejected with a clear configuration error at
set-time instead of panicking later. A plain non-zero check would not
have been sufficient here since 1 also divides down to 0.
Part of apache#17498
@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation sqllogictest SQL Logic Tests (.slt) common Related to common crate datasource Changes to the datasource crate labels Aug 9, 2026

@2010YOUY012010YOUY01 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you. I left a few minor and optional suggestions.

/// round down to a zero-capacity buffer and panic. Invalid values return a
/// configuration error through [`ConfigField`] instead.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConfigMinTwoUsize(usize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can make the min value a configurable k, but it's not necessary now, we can reuse it when there is a need in other configurations.

Comment threaddocs/source/user-guide/configs.md Outdated
| datafusion.execution.minimum_parallel_output_files | 4 | Guarantees a minimum level of output files running in parallel. RecordBatches will be distributed in round robin fashion to each parallel writer. Each writer is closed and a new file opened once soft_max_rows_per_output_file is reached. |
| datafusion.execution.soft_max_rows_per_output_file | 50000000 | Target number of rows in output files when writing multiple. This is a soft max, so it can be exceeded slightly. There also will be one file smaller than the limit if the total number of rows written is not roughly divisible by the soft max |
| datafusion.execution.max_buffered_batches_per_output_file | 2 | This is the maximum number of RecordBatches buffered for each output file being worked. Higher values can potentially give faster write performance at the cost of higher peak memory consumption |
| datafusion.execution.max_buffered_batches_per_output_file | 2 | This is the maximum number of RecordBatches buffered for each output file being worked. Higher values can potentially give faster write performance at the cost of higher peak memory consumption. Must be at least 2: this value is halved to size internal buffering channels, and a value of 0 or 1 would create a zero-capacity channel and panic at write time. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand why it need to be halved, in the implementation it seems to be shared evenly between input and output file buffering:

let(tx_file, rx_file) = mpsc::channel(max_buffered_batches / 2);

Would be nice if we can explain it better.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, you're right that just saying "halved" wasn't enough. Found the original rationale in the demuxer design (#7791) — the budget is split between two independent points in the pipeline: how many files can be in flight from the demuxer to a writer task (orchestration.rs), and how many RecordBatches are buffered for a single file's own writer (demux.rs). Updated the doc comment to explain that explicitly instead of just stating the mechanical halving.

Addresses review feedback from @2010YOUY01 on apache#24204: the field's doc
comment said the value gets halved but not why. Per the original
demuxer design (apache#7791), the configured budget is
split between two independent points in the write pipeline: how many
files can be in flight from the demuxer to a writer task
(orchestration.rs), and how many RecordBatches are buffered for a
single file's own writer (demux.rs). Explain that split explicitly
instead of just stating the mechanical halving.
The information_schema.slt SHOW ALL VERBOSE test hardcodes every
config's description text as expected output. Forgot to update it
when the max_buffered_batches_per_output_file doc comment changed,
which broke both the cargo test (amd64) and verify benchmark results
(amd64) CI jobs (both run this same sqllogictest suite). Confirmed
locally after initializing the testing/ submodules
(git submodule update --init --recursive, which this environment
hadn't done) that datafusion/sqllogictest/test_files/information_schema.slt
now passes cleanly.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.17073% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.98%. Comparing base (918013e) to head (21af09c).
⚠️ Report is 18 commits behind head on main.

Files with missing linesPatch %Lines
datafusion/common/src/config.rs70.27%10 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #24204 +/- ##
==========================================
- Coverage 81.06% 80.98% -0.08% 
==========================================
Files 1106 1106 Lines 381891 383196 +1305 Branches 381891 383196 +1305 ==========================================
+ Hits 309578 310350 +772 - Misses 54034 54524 +490 - Partials 18279 18322 +43 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@2010YOUY01
2010YOUY01 added this pull request to the merge queueAug 11, 2026
Merged via the queue into apache:main with commit a1f64e9Aug 11, 2026
38 checks passed
kosiew pushed a commit to kosiew/datafusion that referenced this pull request Aug 12, 2026
…he#24204)
## Which issue does this PR close?
- Part of apache#17498
## Rationale for this change
```
DataFusion CLI v54.1.0
> set datafusion.execution.max_buffered_batches_per_output_file = 1;
> COPY (SELECT 1 as a) TO '/tmp/x.parquet';
thread 'tokio-rt-worker' panicked at datafusion/datasource/src/write/demux.rs:287:30:
mpsc bounded channel requires buffer > 0
```
Two call sites (`demux.rs::create_new_file_stream`,
`orchestration.rs::spawn_writer_tasks_and_join`) divide
`max_buffered_batches_per_output_file` in half to size a bounded `mpsc`
channel's capacity. Integer division rounds both 0 *and* 1 down to 0,
and Tokio's `mpsc::channel` panics on a zero capacity. A plain non-zero
check (like the existing `ConfigNonZeroUsize`) would not have been
sufficient here, since 1 also triggers the panic.
A third call site (`demux.rs::hive_style_partitions_demuxer`) uses the
raw value directly without dividing, so it would panic on 0 alone.
## What changes are included in this PR?
Adds `ConfigMinTwoUsize`, mirroring the existing `ConfigNonZeroUsize`
pattern already used for sibling fields (`batch_size`,
`meta_fetch_concurrency`, `minimum_parallel_output_files`, etc.), and
applies it to `max_buffered_batches_per_output_file`. Invalid values are
now rejected with a clear configuration error at set-time instead of
panicking later at write time. Updated the three read sites to call
`.get()`, updated the field doc comment to explain the constraint, and
regenerated `docs/source/user-guide/configs.md` via
`dev/update_config_docs.sh`.
## Are these changes tested?
Yes. Added two `statement error` cases to
`datafusion/sqllogictest/test_files/set_variable.slt` (values 0 and 1),
following the exact pattern already used for the sibling
`ConfigNonZeroUsize` fields in that file. Verified manually with
`datafusion-cli` that 0 and 1 now return a clean error instead of
panicking, and that 2 (the default) and 3 still work correctly. Ran
`cargo test -p datafusion-datasource --lib` (178 passed), the
`set_variable.slt` sqllogictest suite, and `cargo check --workspace
--all-targets` — all clean.
## Are there any user-facing changes?
Yes: setting `datafusion.execution.max_buffered_batches_per_output_file`
to `0` or `1` now returns a configuration error instead of panicking. No
change for any value `>= 2` (including the default of 2).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commonRelated to common cratedatasourceChanges to the datasource cratedocumentationImprovements or additions to documentationsqllogictestSQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@DevShiba@codecov-commenter@2010YOUY01