dataengineervishal opened a new pull request, #4607:
URL: https://github.com/apache/flink-cdc/pull/4607

   ## What is the purpose of this pull request?
   
   Follow-up to FLINK-40640 (#4536), built on top of FLINK-41057 (#4588).
   
   Since FLINK-40640, every `UPDATE` on a table with equality fields is written 
as `DELETE(before)` + `INSERT(after)`. The upsert `INSERT` already makes 
Iceberg write an equality delete for the after-image key in the after-image 
partition. When neither the identifier key nor a partition source column 
changes, that is exactly the row the explicit `DELETE` targets, so every such 
update writes a duplicate equality-delete record.
   
   This PR skips the before-image `DELETE` only when the update leaves **both** 
of these unchanged:
   
   - **Partition source columns.** Equality deletes are partition-scoped, so a 
partition move still needs the `DELETE` in the old partition. This is the 
FLINK-40640 case.
   - **Identifier (equality) fields of the target table.** The upsert delete 
covers only the *new* key. Debezium splits a change to the *source* PK into 
DELETE + INSERT, but the sink key can differ from it: a transform with 
`primary-keys` ("Reassign primary key"), or a pre-existing table whose 
identifier fields differ from the CDC PK (FLINK-41057). In those cases a key 
change arrives as a plain `UPDATE`, and skipping the `DELETE` would leave the 
old row behind. This follows the review suggestion on #4536.
   
   ## Brief change log
   
   - `RowDataUtils`:
     - New `convertDataChangeEventToRowData(event, fieldGetters, 
supportsEqualityDeletes, deleteKeyGetters)` overload. It skips the before-image 
`DELETE` when none of the `deleteKeyGetters` columns changed. `null` keeps the 
current behavior, and the existing 3-arg method delegates with `null`.
     - New `createDeleteKeyGetters(schema, columns)`. It builds CDC-native 
getters, so the comparison works on `BinaryStringData` etc. without the Iceberg 
conversions. It returns `null` if a column can't be resolved in the CDC schema 
(e.g. a nested identifier field), and the `DELETE` is then always kept.
   - `IcebergWriter`: the `TableWriterFactory` cache from #4588 now also holds 
the getters for the target table's identifier fields plus partition source 
columns. They are computed once per factory and rebuilt with it on schema 
change.
   - Append-only tables are unchanged.
   
   ## Verifying this change
   
   - `RowDataUtilsTest`: added tests for:
     - same / changed partition, a multi-column partition, and null ↔ value
     - unpartitioned tables
     - key change within a partition, and an unpartitioned key change
     - binary keys compared by content
     - unresolved column → always `DELETE`
     - append-only tables ignoring the getters
     - INSERT/REPLACE/DELETE are unchanged
   - `IcebergWriterTest`:
     - several updates of the same key within one checkpoint and across 
checkpoints, followed by a delete (partitioned and unpartitioned)
     - an update that changes the key keeps only the new row (partitioned and 
unpartitioned)
   - `IcebergWriterDeleteCapabilityTest`: the target table is keyed on a 
different column than the CDC PK, and an update changes the target key but not 
the CDC key. Only the new row remains.
   - I checked that the 4 key-change writer tests **fail** when only partition 
columns are compared (the old row survives as a duplicate).
   - All 84 tests in the Iceberg connector module pass, including 
`IcebergSinkITCase`. 
`IcebergMetadataApplierTest#testApplySchemaChangeHiveCatalog` was excluded 
because it fails the same way on master in my local Docker environment.
   
   ### Benchmarks (JMH, JDK 21, local; harness not included in this PR)
   
   Conversion only (`RowDataUtils`, ns/op, 5 forks):
   
   | Scenario | 3 columns | 20 columns |
   |---|---|---|
   | UPDATE, same partition | 89.5 → 62.5 | 805 → 403 (alloc 5136 → 2664 B/op) |
   | UPDATE, unpartitioned table | 91.5 → 41.9 | 827 → 391 |
   | UPDATE, partition changed | 90.6 → 97.5 | 819 → 800 |
   | UPDATE, key changed | ≈ baseline | ≈ baseline |
   | INSERT / DELETE | unchanged | unchanged |
   
   Iceberg write path (100k updates through the upsert `TaskWriter` up to 
`complete()`, identity-partitioned table, format v2):
   
   | % updates changing partition | equality-delete records | equality-delete 
bytes | write time |
   |---|---|---|---|
   | 0% | 200,000 → 100,000 | 462 KB → 161 KB | within noise |
   | 10% | 200,000 → 110,000 | 493 KB → 176 KB | within noise |
   | 100% | unchanged | unchanged | unchanged |
   
   Fewer equality deletes also means less merge work for readers and compaction.
   
   ## Documentation
   
   - Does this pull request introduce a new feature? no
   - If yes, how is the feature documented? not applicable
   
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   <!--
   If generative AI tooling has been used in the process of authoring this PR, 
please
   change the checkbox below to `[X]` followed by the name of the tool, and 
uncomment the
   "Generated-by" line. See the ASF Generative Tooling Guidance for details:
   https://www.apache.org/legal/generative-tooling.html
   
   You are responsible for the quality and correctness of every change in this 
PR
   regardless of the tooling used. Low-effort AI-generated PRs will be closed.
   -->
   
   - [ ] Yes (please specify the tool below)
   
   <!--
   Generated-by: [Tool Name and Version]
   -->
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to