Repository navigation
Conversation
Share bounded source splitting between the DataSet and DataStream translations in BoundedSourceSplitter. After the initial split, the splitter re-splits when there are fewer splits than readers, re-splits oversized splits when split sizes vary a lot, rounds the split count up to a multiple of the parallelism, and orders splits so round-robin assignment balances readers. The file input max split size is preserved. Batch pipelines now use static split assignment by default, in both DataSet (new StaticInputSplitAssigner) and DataStream. Lazy assignment is used when explicitly requested via sourceStaticSplitThresholdMb, and stays the default for streaming pipelines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the count-based re-split (uniform factor per split) with a single pass that re-splits only the splits much larger than the desired split size, as some sources (e.g. BigQuery) return splits way larger than requested. Share the per-split re-split logic with size balancing and express balancing without loops and breaks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrite roundToMultipleOfParallelism without break/continue, using a priority queue and a halve() helper. Stop halving once the split count is within 10% of the parallelism of a multiple of it, since re-splitting introduces some skew anyway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DataStream translation passes maxParallelism as numSplits when it is set, while the static enumerator assigns split i to reader i % currentParallelism. Pass the enumerator's current parallelism to the splitter separately, so rounding and round-robin ordering match the actual assignment. numSplits still drives the desired split size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instead of accepting split counts within 10% of the parallelism of a multiple of it, halve splits until round-robin assignment gives the busiest reader at most 10% more splits than the average. This rejects counts like 1.1x the parallelism (busiest reader does ~1.8x the average) while accepting any count once readers get many splits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lazy enumerator hands out the largest splits first by taking them from the front of its pending splits. Use an ArrayDeque instead of ArrayList.remove(0), which made assigning n splits O(n^2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixed cost of opening and reading a split dominates for small splits, and byte ranges smaller than a file's blocks may read nothing. Re-splitting of oversized splits, size balancing and halving no longer create splits smaller than min(64 MiB, estimatedSize / parallelism), so small inputs, which may feed transforms generating a lot more data, are still spread over all readers. The initial split is not affected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With static round-robin assignment, a single split per reader passes any size difference straight to the slowest reader, while two splits per reader let the ordering pair large splits with small ones. Re-split splits larger than estimatedSize / (2 x parallelism), and lower the minimum split size to min(64 MiB, estimatedSize / (2 x parallelism)) so small inputs also get two splits per reader. The initial split request is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Log each re-split, size balancing round, split count balancing and round-robin ordering with the split count, size min/mean/max and coefficient of variation, and the busiest reader's load relative to the average. Also document that fileInputSplitMaxSizeMB is rarely needed now that bounded sources are re-split by default to even out uneven file sizes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An uneven split count is not a problem when split sizes differ: the round-robin ordering gives the readers with more splits the smaller ones. Skip halving when the ordered splits already give the busiest reader at most 10% more bytes than the average, instead of judging by split count. On a real BigQuery export read, this avoids thousands of pointless halve attempts reporting a misleading 1.43 count imbalance for a 1.017 byte imbalance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Format sizes in the splitter logs with binary units and two decimals (e.g. 1.71 MiB, 12.51 GiB) instead of raw byte counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Improves how the Flink runner splits bounded sources in batch pipelines, so that work is evenly distributed over the source readers. The same logic is shared by the DataSet (
SourceInputFormat) and DataStream (FlinkSourceenumerators) translations, in a newBoundedSourceSplitter.Motivation
The runner used to call
BoundedSource.split(estimatedSize / parallelism)once and use whatever came back. Beam does not require sources to split in one pass, and many don't honour the desired size: for example a BigQuery export returns one split per exported file, whatever size is requested. The result was often too few splits for the readers, or splits of very uneven size. Combined with lazy split assignment, a few readers could end up with much more work than the others.What changes
After the initial
split()call, the splitter refines the splits in a few bounded passes:estimatedSize / (2 x parallelism)are split again, so each reader gets at least two splits, and sources returning splits far larger than requested (e.g. BigQuery exports) are cut down.ito readeri % parallelismpairs large splits with small ones.Steps 2 to 4 need per-split size estimates and are skipped when they are not available. Re-splitting never goes below
min(64 MiB, estimatedSize / (2 x parallelism)): small splits mostly add fixed per-split overhead, but small inputs, which may feed transforms that generate a lot more data, are still spread over all readers.fileInputSplitMaxSizeMBis still honoured as a cap.The DataStream enumerators now pass the actual reader count (
currentParallelism()) to the splitter, separately fromnumSplits(which may bemaxParallelism), so ordering and balancing match how splits are actually assigned.Split assignment: static by default in batch
sourceStaticSplitThresholdMbno longer has a default. When unset, batch pipelines use static assignment and streaming pipelines keep lazy assignment. Explicit values behave as before:0= lazy, negative = static, positive = size-based.StaticInputSplitAssigner(splitigoes to taski % parallelism, like the DataStream enumerator), and fall back to Flink'sDefaultInputSplitAssignerwhen lazy assignment is configured.This is a behaviour change for batch pipelines that relied on the previous lazy default. They can restore it with
--sourceStaticSplitThresholdMb=0.Observability
Each step logs its effect at INFO, in human-readable units: split count, size min/mean/max and coefficient of variation, and the busiest reader's load relative to the average. For example:
Results
On a production-sized batch job (45 TiB of Avro, parallelism 3500, DataStream translation), comparing the source stage before and after this change, per subtask:
On a BigQuery export read (714 files for 3750 readers), the export files are now re-split into 7854 splits, so all readers get work, with the busiest reader at 1.017x the average bytes. Before, about 80% of the readers had nothing to read.
Trade-offs
getEstimatedSizeBytes()on every split, andsplit()again on some of them. For file-based sources this is in-memory work once the file pattern has been expanded, but it may be slower for sources whose size estimates are remote calls.Testing
BoundedSourceSplitterTest: re-splitting of oversized splits, size balancing, split count balancing by bytes per reader, the minimum split size, small inputs, round-robin ordering for the actual reader count, unsplittable sources, assignment mode resolution and size formatting.SourceInputFormatTest: static assignment by default and lazy assignment when requested.FlinkSourceSplitEnumeratorTestupdated for the new default.Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, commentfixes #<ISSUE NUMBER>instead.CHANGES.mdwith noteworthy changes.See the Contributor Guide for more tips on how to make review process smoother.
To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md
GitHub Actions Tests Status (on master branch)
See CI.md for more information about GitHub Actions CI or the workflows README for a list of workflows and how to trigger them.