Repository navigation
[DebeziumIO] Support Transaction Metadata Events in DebeziumIO JSON Output - #40785
ilyasahsan123 wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
📋 Review Summary
This PR adds support for Debezium transaction metadata in SourceRecordJson, including mapping BEGIN/END transaction boundary events and including the transaction block in change event metadata when fullMetadata is enabled. The implementation and PostgreSQL integration test are clean and well-structured.
🔍 General Feedback
- The new
BEGIN/ENDtransaction boundary event handling (isTransactionMetadataEventandloadTransactionMetadata) is currently only exercised by the Testcontainers integration test; adding a unit test inSourceRecordJsonTestwould ensure this code path is covered during standard unit test runs.
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
| + "\"transaction\":{\"id\":\"796:48630784\",\"total_order\":1," | ||
| + "\"data_collection_order\":1}}," | ||
| + "\"before\":null,\"after\":{\"fields\":{\"id\":1001}}}", | ||
| new SourceRecordJson(transactionRecord, true).toJson()); |
There was a problem hiding this comment.
🟡 Medium: testFullMetadata verifies the transaction block on data change events, but there is no unit test in SourceRecordJsonTest covering transaction BEGIN and END events (isTransactionMetadataEvent and loadTransactionMetadata()). Consider adding a unit test with io.debezium.connector.common.TransactionMetadataKey records (both BEGIN with null event_count/data_collections and END with populated data_collections) so this code path is tested without requiring the Docker-based integration test.
There was a problem hiding this comment.
Thanks, this looks good overall, I think! Opt in through provide.transaction.metadata, the default output is unchanged, and the names match Debezium 3.5.2. A few points:
-
SourceRecordJson.java:153-158and:224-235: what is the consideration of gating thetransactionblock behind fullMetadata while BEGIN/END events are emitted regardless? The block only exists when the user opted in viaprovide.transaction.metadata, so including it whenever the envelope has it does not change the default output. Otherwise the boundary events cannot be correlated with the change events without fullMetadata. -
SourceRecordJson.java:249-269: consider iteratingvalue.schema().fields()asloadBeforeandloadAfterdo instead of hard coding the five names.transaction.metadata.factorylets users swap the struct maker, and a missing field would throwDataExceptionhere. Same for thetransactionblock inloadMetadataExtras:transaction.get(field)is not passed throughtoJsonFriendly, so a nested Struct would be reflected by Gson. -
DebeziumIOPostgresSqlConnectorIT.java:216-218: please add a one line note why the replication slot is created before the inserts, the other tests let Debezium create it. -
README.md: Currently the diff is only a trailing newline (which makes sense as it's a draft PR). You could document the BEGIN/END payload and thetransactionkey next to thewithFullMetadataparagraph. -
Please fill in the PR description and prepare a CHANGES.md line under 2.78.0 I/Os, feel free to extend the existing DebeziumIO entry from #40743. Hold the
CHANGES.mdline until it is approved (so it's not committed here yet), and please mention that it is also available from Python and Go viaconnection_properties. -
Python and Go reach this through the expansion service (
DebeziumTransformRegistrarbuildsreadAsJson()and forwardsconnection_properties), so they get the feature withprovide.transaction.metadata=trueinconnection_properties. Thefull_metadatadocs insdks/python/apache_beam/io/debezium.pyandsdks/go/pkg/beam/io/xlang/debeziumio/debezium.golist the metadata fields but not thetransactionkey or the BEGIN/END payloads. A one line update in both would help, here or as a follow up.
|
Thank you for your feedbacks, @tkaymak 🙌 |
7d2b694 to
0a5bd0a
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #40785 +/- ##
===========================================
+ Coverage 0 56.39% +56.39%
===========================================
Files 0 876 +876
Lines 0 154789 +154789
===========================================
+ Hits 0 87296 +87296
- Misses 0 65636 +65636
- Partials 0 1857 +1857
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:
|
|
Thanks for catching this. You're right, the transaction block should not depend on To support this, I added two new types:
With BEGIN event: {
"status" : "BEGIN",
"id" : "781:39950456",
"ts_ms" : 1793024470210,
"event_count" : null,
"data_collections" : null
}END event: {
"status" : "END",
"id" : "781:39951784",
"ts_ms" : 1793024470210,
"event_count" : 3,
"data_collections" : [ {
"data_collection" : "inventory.customers",
"event_count" : 3
} ]
}Change event (note the {
"metadata" : {
"connector" : "postgresql",
"version" : "3.5.2.Final",
"name" : "beam-debezium-connector",
"database" : "inventory",
"schema" : "inventory",
"table" : "customers",
"op" : "c",
"ts_ms" : 1793024873511,
"snapshot" : "false",
"sequence" : "[null,\"39950456\"]",
"ts_us" : 1793024873511455,
"ts_ns" : 1793024873511455000,
"txId" : 781,
"lsn" : 39950456,
"xmin" : null,
"origin" : null,
"origin_lsn" : null
},
"before" : null,
"after" : {
"fields" : {
"last_name" : "One",
"id" : 5001,
"first_name" : "Ann",
"email" : "ann.one@example.com"
}
},
"transaction" : {
"id" : "781:39950456",
"total_order" : 1,
"data_collection_order" : 1
}
}With No BEGIN/END events are emitted, and {
"metadata" : {
"connector" : "postgresql",
"version" : "3.5.2.Final",
"name" : "beam-debezium-connector",
"database" : "inventory",
"schema" : "inventory",
"table" : "customers",
"op" : "c",
"ts_ms" : 1793025206974,
"snapshot" : "false",
"sequence" : "[null,\"39950456\"]",
"ts_us" : 1793025206974908,
"ts_ns" : 1793025206974908000,
"txId" : 781,
"lsn" : 39950456,
"xmin" : null,
"origin" : null,
"origin_lsn" : null
},
"before" : null,
"after" : {
"fields" : {
"last_name" : "One",
"id" : 5001,
"first_name" : "Ann",
"email" : "ann.one@example.com"
}
},
"transaction" : null
} |
Thanks for the review. The flat map approach may not provide users with a stable, documented schema. Please tell me if you would prefer an iterative approach; I can shift it from a field to a flat map. |
I have addressed the concerns noted above. When you have a moment, please review this pull request again. Thank you. |
tkaymak
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround, the rework reads well and the BEGIN and END unit test is exactly what was missing.
On typed versus iterating, keep the typed Transaction and TransactionEvent classes. A stable documented schema is the better contract and the names match Debezium. Only read the fields defensively, for example transaction.schema().field("id") != null ? transaction.getString("id") : null, so a custom transaction.metadata.factory yields null instead of a DataException. Since the values are read as primitives the Gson reflection concern is gone.
One thing has to change before I can approve. With serializeNulls() every change event now ends with "transaction":null, which is a behavior change of the default output. Beam's own tests show it, the PostCommit Python runs (3.11, 3.13, 3.14) fail in test_xlang_debezium_read with an unexpected 'transaction': None, and XVR Direct 3.14 fails TestDebeziumIO_BasicRead on the exact string. User pipelines that compare against the old JSON or parse it with a strict schema would break the same way. Please omit the key unless the record has a transaction block. The smallest fix is in Event.toJson(Map): always build the tree with toJsonTree and remove("transaction") when it is null. Then the three golden strings in SourceRecordJsonTest, the MySQL IT and the Postgres IT go back to their previous values, the README sentence becomes "the key is absent" instead of null, and the Python and Go docstrings are accurate as written.
|
Everything else is addressed, thanks for the slot note and the README, Python and Go updates. The Debezium PreCommit ran the six ITs including the new one. Once the null key is gone and the two PostCommits are green I will approve, CHANGES.md stays held back until then. @ilyasahsan123 |
|
Assigning reviewers: R: @lostluck for label go. 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). |
c4d14e7 to
7754fd0
Compare
|
Thanks for the review, @tkaymak! I've addressed everything in the
|
There was a problem hiding this comment.
Thanks, the null key is gone, the fields are read defensively and the two new tests cover the missing field cases. PostCommit Python 3.11 to 3.14, XVR Direct and the Debezium PreCommit are green. LGTM.
Please add a CHANGES.md entry for the transaction metadata support under New Features / Improvements, as mentioned earlier.
|
Thank you, @tkaymak! Also, I have added a CHANGES.md for transaction metadata support under 2.78.0 I/Os and New Features / Improvements as you mentioned. Let me know if there's anything I can do to help.
|
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
…codebase Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
…tion key when there's none Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
7754fd0 to
3e8ca16
Compare
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Signed-off-by: Ilyas Ahsan <meilyasahsan@gmail.com>
Addresses #40784
Supports Debezium's transaction metadata (
provide.transaction.metadata=true) inDebeziumIO.Today, the transaction
BEGIN/ENDevents have nosourceblock, soSourceRecordJsonthrows anIllegalArgumentExceptionand the pipeline fails.With this change:
{"status":"BEGIN","id":"781:39950456","ts_ms":1792926401751,"event_count":null,"data_collections":null} {"status":"END","id":"781:39950456","ts_ms":1792926401760,"event_count":3,"data_collections":[{"data_collection":"inventory.customers","event_count":3}]}The change event now includes a "transaction" key when there's one.
{"metadata":{...},"before":{...},"after":{"fields":{...}}} {"metadata":{...},"before":{...},"after":{"fields":{...}},"transaction":{"id":"781:39950456","total_order":1,"data_collection_order":1}}Python and Go can use this with no code changes: pass
provide.transaction.metadata=trueviaconnection_properties(Python) ordebeziumio.ConnectionProperties(Go).CHANGES.md:I'll update this line https://github.com/apache/beam/blob/master/CHANGES.md?plain=1#L70 with the following content:
Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:
addresses #123orfixes #456).CHANGES.mdwith noteworthy changes.See CONTRIBUTING.md for more details.