Repository navigation
Flink: Share trackers between writers of the same key type and route promoted keys to one writer - #21
Open
abrarsher23 wants to merge 2 commits into
Conversation
DynamicWriter compared every writer of a table with the table's first inserted-row tracker, and a writer whose key type could not share it kept a private tracker. Writers after the same such change, for example after a key column becomes optional, then could not retire each other's rows either. Trackers are now kept in groups: a writer joins the first group whose key type it can share with and otherwise opens a new group, so writers with the same new key type share with each other. Applied to flink/v2.0 and flink/v2.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dynamic sink distributes rows by their equality fields in NONE mode with equality fields, in HASH mode on an unpartitioned table and in RANGE mode. The hash of the key came from Objects::hashCode per field, and Integer and Long hash a negative value differently, as do Float and Double nearly every value. After an equality field was promoted, rows of one key from before and after the promotion could therefore go to different writer subtasks, where no inserted-row tracker can be shared, and a re-write of the key within one checkpoint left both rows. The dynamic sink now hashes the key with int values widened to long and float values widened to double. A key without negative int or float values hashes as before and keeps its writer; the others move once. The writer keeps no keyed state across checkpoints, so that move is safe. EqualityFieldKeySelector, which FlinkSink and IcebergSink use, is unchanged. Applied to flink/v2.0 and flink/v2.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
abrarsher23
force-pushed
the
abrar/dynamic-sink-promoted-key-routing
branch
from
October 4, 2026 21:50
6b9afec to
89877e6
Compare
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.
Stacked on #20, which stacks on #17. This branch starts at #20's head (
5b4df4ffa), so the diff shown here is only this PR's two commits. The base will be retargeted when #20 and #17 merge.Summary
Two defensive changes to tracker sharing in the dynamic sink. Neither matters for a pipeline whose keys never change optionality or column order, and whose keys have no negative
intorfloatvalues.inttobigint,floattodouble), negativeintkeys andfloatkeys hash differently, so the old and new rows of one key can reach different parallel writers.Scope. Both changes are in
flink/v2.0andflink/v2.1, with byte-identical copies.flink/v1.20is untouched.The changes
1. Writers of the same key type share with each other
Example, in one checkpoint:
id=1whileidis required. This is writer A.idis made optional, andid=1is upserted again by writer B.id=1is upserted again by writer C.A tracker for a required key can't safely match null keys, so B and C can't share A's trackers. With #20 alone, B and C each keep a private tracker, and three rows for
id=1stay. With this PR, C shares B's tracker and retires B's row, so two rows stay: A's and C's. The A-to-B duplicate itself isn't fixed (see "Not fixed").How.
DynamicWriterkeeps a list of tracker groups per table. A writer joins the first group whose first trackercanShareWithits key type. If none fits, it opens a new group, which later writers of the same key type join. TheNot sharing …WARN is logged when a writer has to open a new group.2. A promoted key's rows go to the same writer subtask
Example at write parallelism 4, in distribution mode NONE with equality fields:
upsert id=-5withid intgoes to subtask 2.idis widened tobigint.upsert id=-5then goes to subtask 3.The key hash used Java's own hash of each value:
Integerand the same value as aLonghash differently.Floatand the same value as aDoublehash differently for nearly every value.A code probe before this change, at parallelism 4, moved all 50 keys from −50 to −1 to another subtask after the promotion. None of the keys from 0 to 50 moved.
How. A new package-private
WidenedEqualityFieldKeySelectorwidensintvalues tolongandfloatvalues todoublebefore hashing.HashKeyGenerator.equalityFieldKeySelector(...)uses it for:Keys with no negative
intand nofloatvalues hash exactly as before. Widening goes by column type, sodatevalues aren't touched. HASH mode on a partitioned table routes by the partition path, which needs no change.EqualityFieldKeySelectoritself is untouched, soFlinkSinkandIcebergSinkroute as before.What changes
flink/v2.0path;flink/v2.1is identical).../sink/dynamic/DynamicWriter.javaTrackerGroupreplaces #20's singleTableTrackersper table.trackerGroup(...)joins or opens a group. TheNot sharingWARN is logged when a group is opened..../sink/dynamic/WidenedEqualityFieldKeySelector.java.../sink/dynamic/HashKeyGenerator.javaequalityFieldKeySelector(...)builds aWidenedEqualityFieldKeySelector.TestDynamicWriter,TestHashKeyGenerator,TestWidenedEqualityFieldKeySelector(new)Not fixed in this PR
Risk
intkeys and offloatkeys move to a different writer subtask, once, at upgrade.DynamicWriterkeeps no keyed state across checkpoints: trackers are dropped at every checkpoint, and a restart begins with empty trackers.WidenedEqualityFieldKeySelectorboxes anintkey as aLong(or afloatas aDouble) before hashing. No benchmark was run.Rollout and rollback
How to tell it's working
Not sharing inserted-row tracker …is logged once per new group, and the INFO… share an inserted-row tracker …appears for later writers of that key type.Testing
This head is the code that was fully tested before the split. This PR's head has exactly the tree of the earlier
6b9afeca5, which was Core, Flink: Share inserted-row trackers across equality key promotions in the dynamic sink #20 with the grouping plus this routing commit (checked withgit rev-parse HEAD^{tree}). These full suites ran at that tree:iceberg-coreiceberg-dataiceberg-flink-2.0iceberg-flink-2.1Coverage (JaCoCo) at that tree: every line and branch changed since Core, Flink: Share the inserted-row tracker across DynamicIcebergSink writers of one table #17's head is exercised, 219 of 219 lines and 128 of 128 branches. That includes all of this PR's lines.
Tests, the same in both Flink modules:
testUpsertAcrossKeyOptionalityChangeSharesOnlyBetweenWritersOfTheSameKeyType: two groups, and the optional-key writers retire each other's row.TestHashKeyGenerator.testEqualityFieldRoutingIsStableAcrossKeyPromotion: NONE, HASH and RANGE at parallelism 2–4, with keysInteger.MIN_VALUE, −5, −1, 0 and 42.TestWidenedEqualityFieldKeySelector, 5 tests:intorfloatvalues hash as before;Mutations:
TestDynamicWriter.EqualityFieldKeySelectorfails 1 of 15TestHashKeyGenerator.Not tested end to end. In the end-to-end test of Core, Flink: Share inserted-row trackers across equality key promotions in the dynamic sink #20 every row went to one writer subtask, and no key changed optionality, so neither change was exercised there.