Repository navigation
[WriteFiles] Support direct writes of large streaming bundles to temp files (#40533) - #40543
tejasiyer-dev wants to merge 4 commits into
Conversation
|
Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #40543 +/- ##
============================================
+ Coverage 56.16% 64.98% +8.81%
- Complexity 2288 13977 +11689
============================================
Files 1124 1729 +605
Lines 178336 108641 -69695
Branches 1489 11332 +9843
============================================
- Hits 100170 70595 -29575
+ Misses 75645 33838 -41807
- Partials 2521 4208 +1687
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Assigning reviewers: R: @ahmedabu98 for label java. This pull request likely touches a core component ("core" label). Please review with scrutiny. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
|
Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment |
|
Can we move sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/BundleLifter.java into sdks/java/io/common and reuse it? |
|
cc: @tomstepp |
|
Agreed, it would be nice to move and reuse BundleLifter |
|
+1 to that, but please make sure it doesn't break streaming update compatibility for the iceberg sink |
I think moving BundleLifter to sdks/java/io/common would create a circular dependency. sdks/java/io/common already depends on sdks/java/core and WriteFiles is in sdks/java/core, so to use BundleLifter from io/common, core would also have to depend on io/common. Would moving BundleLifter to core be better? Iceberg already depends on Core so both Iceberg and WriteFiles could share it with no new dependencies |
|
@ahmedabu98 PTAL. Also, seems like |
I think that is okay. We could move it to sdks/java/core/src/main/java/org/apache/beam/sdk/util and mark it with @org.apache.beam.sdk.annotations.Internal |
Fixes #40533
Summary
In streaming pipelines,
WriteFilesshuffles every element either viaGroupByKey(when a shard count or runner-determined sharding is used) orGroupIntoBatches.withShardedKey()(when auto-sharded) before writing temporary files. When upstream bundles are already large enough to produce well-sized temporary files on their own, shuffling all elements incurs unnecessary network and state overhead.This PR adds an optional direct-write byte threshold that allows sufficiently large streaming bundles to write directly to temporary files, while routing smaller bundles through the existing grouped write path. Similar to
IcebergIO direct write (#36720).
Changes
WriteFiles:withDirectWriteByteThreshold(long)to configure the byte threshold for direct bundle writes on unbounded inputs.WriteShardedBundlesWithDirectWrite, which buffers incoming elements per bundle up tothresholdBytesusing the input coder's byte-size estimation:>= thresholdBytes): Written directly to temporary files viaDirectWriteLargeBundlesFnwithout shuffling. If a bundle touches more thanmaxNumWritersPerBundledistinct(window, destination)writers, excess records spill over to the grouped path.< thresholdBytes) and spillover records: Routed throughWriteShardedBundlesToTempFiles(when a shard count is configured) orGroupIntoBatches.withShardedKey()(when unsharded / auto-sharded), then merged with the direct-writeFileResults beforeFinalizeTempFileBundles.TextIO&FileIO:.withDirectWriteByteThreshold(long)pass-through methods toTextIO.Write,TextIO.TypedWrite, andFileIO.Write.DataflowRunner:StreamingShardedWriteFactoryto preservegetDirectWriteByteThreshold()when constructing the replacementWriteFilestransform on Dataflow Runner v1.WriteFiles:directWriteBundlesandgroupedWriteBundles.Testing
WriteFilesTest:testDirectWriteAllBundlesLarge(large bundles bypass shuffle and write directly)testDirectWriteAllBundlesSmall(small bundles fall back to the grouped write path)testDirectWriteUnshardedMixedBundles(exercises both direct-write and auto-sharded fallback paths in the same pipeline)testDirectWriteNonDefaultTriggerLargeBundles&testDirectWriteNonDefaultTriggerSmallBundles(verifies non-default user triggers across both paths)testDirectWriteDisabledWithSideInputs(verifies fallback when dynamic destinations use side inputs)DataflowRunnerTest:testStreamingWriteWithDirectWriteByteThresholdtestStreamingWriteDirectWriteThresholdDefaultNullTextIOWriteTestandFileIOTest.Benchmark
30-minute Dataflow streaming run (
5x n1-standard-4, ~80 MB/s input, 1-minuteFixedWindows,TextIO.write().withWindowedWrites()):direct-write-before):2026-10-12_12_51_21-9229133822682131587direct-write-after):2026-10-12_12_51_50-15846649412145844281directWriteByteThreshold = 1 MB)WriteFiles32.5Mrecords)4,040file paths)2.37 GBbacklog)0backlog)300files (445 MBavg)4,040files (35.2 MBavg)Before vs. After Shuffle Step Input (
125.18 GBvs.516.84 KB):https://screenshot-v2.corp.google.com/image/504bv5gm3tlro.png
https://screenshot-v2.corp.google.com/image/3ndvsk8951v6g.png
Before vs. After System Latency (
~2.5 minvs.3–5s):https://screenshot-v2.corp.google.com/image/4mv9vipuam300.png
https://screenshot-v2.corp.google.com/image/6507lv1t9mj68.png