Uh oh!
There was an error while loading. Please reload this page.
[fix](streamingjob) fix postgres DML silently dropped on task restart - #61481
Conversation
JNSimba
commented
Mar 18, 2026
run buildall |
Thearas
commented
Mar 18, 2026
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
JNSimba
commented
Mar 18, 2026
run buildall |
doris-robot
commented
Mar 18, 2026
TPC-H: Total hot run time: 27048 ms |
doris-robot
commented
Mar 18, 2026
TPC-DS: Total hot run time: 168708 ms |
hello-stephen
commented
Mar 18, 2026
FE UT Coverage ReportIncrement line coverage |
JNSimba
commented
Mar 19, 2026
run external |
JNSimba
commented
Mar 19, 2026
/review |
There was a problem hiding this comment.
Pull request overview
Fixes an intermittent Postgres CDC failure where the first DML after a task restart could be silently dropped due to Debezium’s WalPositionLocator behavior in pgoutput non-streaming mode.
Changes:
- Override
extractBinlogStateOffset()inPostgresSourceReaderto removelsn_proc/lsn_commitkeys before Debezium consumes the offset. - Clarify a log message in
StreamingMultiTblTaskto accurately reflect “timeout reason”.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| fs_brokers/cdc_client/src/main/java/org/apache/doris/cdcclient/source/reader/postgres/PostgresSourceReader.java | Adjusts Postgres offset passed to Debezium to avoid WalPositionLocator incorrectly filtering DML on restart. |
| fe/fe-core/src/main/java/org/apache/doris/job/extensions/insert/streaming/StreamingMultiTblTask.java | Fixes misleading error log text for timeout-reason retrieval failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Code Review SummaryPR: fix fix postgres DML silently dropped on task restart OverviewThis PR fixes a bug where the first DML of a new Postgres streaming transaction is intermittently dropped (10-20% failure rate) on task restart, with no error logged. The fix strips Critical Checkpoint Conclusions
VerdictNo blocking issues. The fix is correct, well-documented, and safe for production. The only suggestion is to consider adding a unit test for the |
JNSimba
commented
Mar 19, 2026
run external |
PR approved by at least one committer and no changes requested. |
PR approved by anyone and no changes requested. |
Uh oh!
There was an error while loading. Please reload this page.
JNSimba
commented
Mar 20, 2026
run buildall |
1 similar comment
JNSimba
commented
Mar 20, 2026
run buildall |
PR approved by at least one committer and no changes requested. |
doris-robot
commented
Mar 20, 2026
TPC-H: Total hot run time: 27069 ms |
doris-robot
commented
Mar 20, 2026
TPC-DS: Total hot run time: 167189 ms |
hello-stephen
commented
Mar 20, 2026
FE UT Coverage ReportIncrement line coverage |
Uh oh!
There was an error while loading. Please reload this page.
…#61481) ### What problem does this PR solve? #### Problem When a streaming job restarts a task, the first DML of the new transaction is occasionally silently dropped (10-20% failure rate). The affected record never appears in the Doris target table, with no error logged — only "identified as already processed" in cdc-client.log. #### Root Cause debezium 1.9.x hardcodes `proto_version=1` (non-streaming pgoutput) for all PG versions. In non-streaming mode, the walsender batches all changes of a transaction and sends them after COMMIT, and all messages (BEGIN + DML) share the same `XLogData.data_start` = the transaction's `begin_lsn`. When this `begin_lsn` equals the previous transaction's `commit_lsn` (i.e. the two transactions are adjacent in WAL with no other writes between them), `WalPositionLocator` behaves incorrectly: 1. **Find phase**: `COMMIT(T1)` at `lsn=Y` sets `storeLsnAfterLastEventStoredLsn=true`. `BEGIN(T2)` and `INSERT(T2)` both have `lsn=Y`, so they keep returning `Optional.empty()`. Only `COMMIT(T2)` at `lsn=Z` sets `startStreamingLsn=Z`, with `lsnSeen={Y, Z}`. 2. **Actual streaming**: `INSERT(T2)` arrives with `lastReceiveLsn=Y`. `skipMessage(Y)`: `Y ∈ lsnSeen` and `Y ≠ startStreamingLsn(Z)` → filtered. The bug is intermittent because it only triggers when no other WAL activity (autovacuum, other connections) occurs between the two transactions. #### Fix Override `extractBinlogStateOffset()` in `PostgresSourceReader` to strip `lsn_proc` and `lsn_commit` from the offset before it is passed to debezium. This constructs `WalPositionLocator(lastCommitStoredLsn=null, lsn=Y)`, which causes the find phase to exit immediately at the first received message (`startStreamingLsn=Y`). In actual streaming, `COMMIT(T1)` triggers switch-off (`lastReceiveLsn=Y = startStreamingLsn`), and all subsequent messages including `INSERT(T2)` pass through. See https://issues.apache.org/jira/browse/FLINK-39265. #### Test Run `test_streaming_postgres_job` multiple times. Before this fix the 'Apache' assertion fails ~10-20% of the time; after this fix it passes consistently.
…#61481) ### What problem does this PR solve? #### Problem When a streaming job restarts a task, the first DML of the new transaction is occasionally silently dropped (10-20% failure rate). The affected record never appears in the Doris target table, with no error logged — only "identified as already processed" in cdc-client.log. #### Root Cause debezium 1.9.x hardcodes `proto_version=1` (non-streaming pgoutput) for all PG versions. In non-streaming mode, the walsender batches all changes of a transaction and sends them after COMMIT, and all messages (BEGIN + DML) share the same `XLogData.data_start` = the transaction's `begin_lsn`. When this `begin_lsn` equals the previous transaction's `commit_lsn` (i.e. the two transactions are adjacent in WAL with no other writes between them), `WalPositionLocator` behaves incorrectly: 1. **Find phase**: `COMMIT(T1)` at `lsn=Y` sets `storeLsnAfterLastEventStoredLsn=true`. `BEGIN(T2)` and `INSERT(T2)` both have `lsn=Y`, so they keep returning `Optional.empty()`. Only `COMMIT(T2)` at `lsn=Z` sets `startStreamingLsn=Z`, with `lsnSeen={Y, Z}`. 2. **Actual streaming**: `INSERT(T2)` arrives with `lastReceiveLsn=Y`. `skipMessage(Y)`: `Y ∈ lsnSeen` and `Y ≠ startStreamingLsn(Z)` → filtered. The bug is intermittent because it only triggers when no other WAL activity (autovacuum, other connections) occurs between the two transactions. #### Fix Override `extractBinlogStateOffset()` in `PostgresSourceReader` to strip `lsn_proc` and `lsn_commit` from the offset before it is passed to debezium. This constructs `WalPositionLocator(lastCommitStoredLsn=null, lsn=Y)`, which causes the find phase to exit immediately at the first received message (`startStreamingLsn=Y`). In actual streaming, `COMMIT(T1)` triggers switch-off (`lastReceiveLsn=Y = startStreamingLsn`), and all subsequent messages including `INSERT(T2)` pass through. See https://issues.apache.org/jira/browse/FLINK-39265. #### Test Run `test_streaming_postgres_job` multiple times. Before this fix the 'Apache' assertion fails ~10-20% of the time; after this fix it passes consistently.
…apache#61481) ### What problem does this PR solve? #### Problem When a streaming job restarts a task, the first DML of the new transaction is occasionally silently dropped (10-20% failure rate). The affected record never appears in the Doris target table, with no error logged — only "identified as already processed" in cdc-client.log. #### Root Cause debezium 1.9.x hardcodes `proto_version=1` (non-streaming pgoutput) for all PG versions. In non-streaming mode, the walsender batches all changes of a transaction and sends them after COMMIT, and all messages (BEGIN + DML) share the same `XLogData.data_start` = the transaction's `begin_lsn`. When this `begin_lsn` equals the previous transaction's `commit_lsn` (i.e. the two transactions are adjacent in WAL with no other writes between them), `WalPositionLocator` behaves incorrectly: 1. **Find phase**: `COMMIT(T1)` at `lsn=Y` sets `storeLsnAfterLastEventStoredLsn=true`. `BEGIN(T2)` and `INSERT(T2)` both have `lsn=Y`, so they keep returning `Optional.empty()`. Only `COMMIT(T2)` at `lsn=Z` sets `startStreamingLsn=Z`, with `lsnSeen={Y, Z}`. 2. **Actual streaming**: `INSERT(T2)` arrives with `lastReceiveLsn=Y`. `skipMessage(Y)`: `Y ∈ lsnSeen` and `Y ≠ startStreamingLsn(Z)` → filtered. The bug is intermittent because it only triggers when no other WAL activity (autovacuum, other connections) occurs between the two transactions. #### Fix Override `extractBinlogStateOffset()` in `PostgresSourceReader` to strip `lsn_proc` and `lsn_commit` from the offset before it is passed to debezium. This constructs `WalPositionLocator(lastCommitStoredLsn=null, lsn=Y)`, which causes the find phase to exit immediately at the first received message (`startStreamingLsn=Y`). In actual streaming, `COMMIT(T1)` triggers switch-off (`lastReceiveLsn=Y = startStreamingLsn`), and all subsequent messages including `INSERT(T2)` pass through. See https://issues.apache.org/jira/browse/FLINK-39265. #### Test Run `test_streaming_postgres_job` multiple times. Before this fix the 'Apache' assertion fails ~10-20% of the time; after this fix it passes consistently.
What problem does this PR solve?
Problem
When a streaming job restarts a task, the first DML of the new transaction
is occasionally silently dropped (10-20% failure rate). The affected record
never appears in the Doris target table, with no error logged — only
"identified as already processed" in cdc-client.log.
Root Cause
debezium 1.9.x hardcodes
proto_version=1(non-streaming pgoutput) for allPG versions. In non-streaming mode, the walsender batches all changes of a
transaction and sends them after COMMIT, and all messages (BEGIN + DML) share
the same
XLogData.data_start= the transaction'sbegin_lsn.When this
begin_lsnequals the previous transaction'scommit_lsn(i.e.the two transactions are adjacent in WAL with no other writes between them),
WalPositionLocatorbehaves incorrectly:Find phase:
COMMIT(T1)atlsn=YsetsstoreLsnAfterLastEventStoredLsn=true.BEGIN(T2)andINSERT(T2)both havelsn=Y, so they keep returningOptional.empty(). OnlyCOMMIT(T2)atlsn=ZsetsstartStreamingLsn=Z, withlsnSeen={Y, Z}.Actual streaming:
INSERT(T2)arrives withlastReceiveLsn=Y.skipMessage(Y):Y ∈ lsnSeenandY ≠ startStreamingLsn(Z)→ filtered.The bug is intermittent because it only triggers when no other WAL activity
(autovacuum, other connections) occurs between the two transactions.
Fix
Override
extractBinlogStateOffset()inPostgresSourceReaderto striplsn_procandlsn_commitfrom the offset before it is passed to debezium.This constructs
WalPositionLocator(lastCommitStoredLsn=null, lsn=Y), whichcauses the find phase to exit immediately at the first received message
(
startStreamingLsn=Y). In actual streaming,COMMIT(T1)triggersswitch-off (
lastReceiveLsn=Y = startStreamingLsn), and all subsequentmessages including
INSERT(T2)pass through.See https://issues.apache.org/jira/browse/FLINK-39265.
Test
Run
test_streaming_postgres_jobmultiple times. Before this fix the'Apache' assertion fails ~10-20% of the time; after this fix it passes
consistently.
Release note
None
Check List (For Author)
Test
Behavior changed:
Does this need documentation?
Check List (For Reviewer who merge this PR)