Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #40653 +/- ##
============================================
+ Coverage 56.16% 65.07% +8.90%
- Complexity 2288 13993 +11705
============================================
Files 1124 1729 +605
Lines 178336 108515 -69821
Branches 1489 11313 +9824
============================================
- Hits 100170 70611 -29559
+ Misses 75645 33694 -41951
- Partials 2521 4210 +1689
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:
|
|
Checks are failing. Will not request review until checks are succeeding. If you'd like to override that behavior, comment |
|
Assigning reviewers: R: @ahmedabu98 for label iceberg. 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). |
Summary
Adds end-to-end support for sorting records on write according to an Iceberg table's
SortOrder(.withSortedWrites()/ Managed IOsort_on_write: true) as well asDistributionMode.RANGE(.withDistributionMode(DistributionMode.RANGE)/ Managed IOdistribution_mode: "RANGE") across both Batch and Streaming pipelines:Order-Preserving Binary Key Encoding & External Spill-to-Disk Sorting (
IcebergRowSorter):SortOrderkeys into lexicographically comparablebyte[]arrays supportingASC/DESC,NULLS_FIRST/NULLS_LAST, all Iceberg primitive/logical types (BOOLEAN,INTEGER,LONG,FLOAT,DOUBLE,DECIMAL,STRING,UUID,FIXED,BINARY,DATE,TIME,TIMESTAMP,TIMESTAMPTZ,EnumerationType), and non-identity sort transforms (month,day,hour,year,bucket,truncate).BufferedExternalSorter(NATIVE) with bounded per-instance memory (64 MBfor single-partition grouped writes,16 MBper destination in multi-destination bundle writes) to spill to local disk when a partition or bundle exceeds memory.Zero-Shuffle Local Bundle Sorting for
DistributionMode.NONE+withSortedWrites()(RecordWriterManager):IcebergRowSorterkeyed by(partitionPath, sortKey)without opening anyRecordWriters during@ProcessElement.@FinishBundle, flushes sorted rows one partition at a time—keeping only a single activeRecordWriteropen at any instant—eliminating any network shuffle forDistributionMode.NONEin both Batch and Streaming.Dynamic Quantile Sharding for
DistributionMode.RANGE(AssignDestinationsAndPartitions):withDistributionFunction(SerializableFunction<Row, Integer>)and automatic quantile sharding in Batch whendistributionFunctionis omitted.A-Res) weighted reservoir sampling without replacement (2,048samples per(destination, partition)) combined with estimated partition byte volume to compute balanced quantile split boundaries sized toDEFAULT_BYTES_PER_FILE(512 MiB).byte[]sort keys across the stage barrier for un-sampled rows; only encodes sort keys during shard assignment for partitions that split into multiple shards.Distribution & Sorting Modes Matrix
NONE(Unsorted).withDistributionMode(NONE)GroupByKeyonly if open writers overflowMAX_NUM_WRITERS)directWriteByteLimit) or 1 (GroupByKeyacrossshardingKey % 50)NONE(Sorted).withDistributionMode(NONE).withSortedWrites()(dest, partition))directWriteByteLimit) or 1 (GroupByKeyacrossshardingKey % 50)table.sortOrder(); min/max key ranges across different worker bundles may overlap.HASH.withDistributionMode(HASH)(+ optional.withSortedWrites(),.withAutosharding())GroupByKeyby(dest, partition))GroupIntoBatchesby(dest, partition)).withSortedWrites()(without autosharding), files within each partition have strictly non-overlapping sort-key ranges.RANGE(Dynamic Quantiles).withDistributionMode(RANGE)(nodistributionFunction, Batch)GroupByKeyby(dest, partition, quantileShard)+ lightweight quantile side-inputCombine.perKey)withDistributionFunctionor.withAutosharding())totalPartitionBytes / targetFileSizeusing weighted reservoir quantiles; files within each partition have non-overlapping sort-key ranges.RANGE(Custom Function).withDistributionMode(RANGE).withDistributionFunction(fn)GroupByKeyby(dest, partition, fn(row)))GroupIntoBatchesby(dest, partition, fn(row)))fn(row)is monotonic with respect totable.sortOrder().Testing & Verification
265tests in:sdks:java:io:iceberg:test):IcebergRowSorterTest: Order-preserving byte encoding across all primitive/logical types,ASC/DESCinversion,NULLS_FIRST/NULLS_LASTmatrix, non-identity transforms (month,hour,truncate), and external disk spill verification.IcebergIOWriteTest: End-to-end sorted writes acrossNONE,HASH,HASH + autosharding,RANGE(custom function and dynamic quantile sharding), post-sort file rolling without range overlap, and streamingdirectWriteByteLimitbundle sorting.AssignDestinationsAndPartitionsTest: Side-input metadata resolution and Efraimidis–Spirakis weighted reservoir sampling under extreme bundle skew.IcebergIOSideInputTableCacheTest&IcebergWriteSchemaTransformProviderTest: Parameterized acrossNONE,HASH,HASH + autosharding, andRANGE.17.32Mbatch rows /10.31Mstreaming rows with 80% elephant partition volume skew, 20%nullsort keys, 30% duplicate heavy-hitter keys, 21x bundle payload fanout skew, and 3 dynamic destinations0internal sort violations,0overlapping key ranges across dynamicRANGEshards (23shards onELEPHANT_HOT_PARTITION,3shards on medium partitions,1file on each of30mouse partitions),0worker errors/OOMs, andgc thrashing=falsethroughout external disk spills.