[ 
https://issues.apache.org/jira/browse/FLINK-41060?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Martijn Visser updated FLINK-41060:
-----------------------------------
    Description: 
With the exactly-once {{JdbcSink}}, {{JdbcWriter.prepareCommit()}} prepares the 
XA branch, resets {{pendingRecords}} and returns the committable, after which 
{{JdbcWriter.snapshotState()}} sees {{pendingRecords == false}} and calls 
{{commitTxUntil(lastCheckpointId)}}, which commits the branch that was just 
prepared. The data is committed at the barrier, before the checkpoint 
completes. A checkpoint that fails after the barrier passed the sink leaves its 
records committed, and the restart replays and writes them again.

The committer does not compensate: {{JdbcCommitableSerializer}} only writes the 
{{Xid}}, so a deserialized committable carries no transaction and 
{{JdbcCommitter.commit()}} falls back to {{commitTx()}} on its own empty 
{{XaTransaction}}. With writer and committer in one chain the committable still 
carries the writer's transaction object, which is why 
{{ExactlyOnceJdbcSinkTest}} passes; with {{pipeline.operator-chaining=false}} 
the same job leaves its only branch in-doubt and commits no row (Derby: 0 of 10 
rows, 1 in-doubt branch).

The writer should only prepare the branch at the barrier and hand its xid to 
the committer, which commits it once the checkpoint completed, on its own 
connection. A prepared branch keeps its locks until then, and a writer chained 
to the committer can block on them and keep the committer from running. So the 
exactly-once committer runs in its own task: the committables pass a no-op map 
chained to the writer and go to the first committer subtask with {{global()}}, 
which keeps the writer's operator id for savepoints without a uid.

  was:
With the exactly-once {{JdbcSink}}, {{JdbcWriter.prepareCommit()}} prepares the 
XA branch, resets {{pendingRecords}} and returns the committable, after which 
{{JdbcWriter.snapshotState()}} sees {{pendingRecords == false}} and calls 
{{commitTxUntil(lastCheckpointId)}}, which commits the branch that was just 
prepared. The data is committed at the barrier, before the checkpoint 
completes. A checkpoint that fails after the barrier passed the sink leaves its 
records committed, and the restart replays and writes them again.

The committer does not compensate: {{JdbcCommitableSerializer}} only writes the 
{{Xid}}, so a deserialized committable carries no transaction and 
{{JdbcCommitter.commit()}} falls back to {{commitTx()}} on its own empty 
{{XaTransaction}}. With writer and committer in one chain the committable still 
carries the writer's transaction object, which is why 
{{ExactlyOnceJdbcSinkTest}} passes; with {{pipeline.operator-chaining=false}} 
the same job leaves its only branch in-doubt and commits no row (Derby: 0 of 10 
rows, 1 in-doubt branch).

We should let {{snapshotState}} commit only when {{prepareCommit}} found 
nothing to prepare, and let {{JdbcCommitter.commit}} commit the handed xid 
through {{xaConnectionProvider.commit(xid, true)}}. 
{{ExactlyOnceJdbcWriterTest.testCheckpoint}} asserts the barrier-time 
visibility today and needs to change with it.


> Exactly-once JdbcSink commits its XA branch at the checkpoint barrier
> ---------------------------------------------------------------------
>
>                 Key: FLINK-41060
>                 URL: https://issues.apache.org/jira/browse/FLINK-41060
>             Project: Flink
>          Issue Type: Bug
>          Components: Connectors / JDBC
>    Affects Versions: jdbc-3.2.0, jdbc-3.3.0, jdbc-4.0.0, jdbc-4.1.0, 
> jdbc-3.4.0
>            Reporter: Martijn Visser
>            Assignee: Martijn Visser
>            Priority: Critical
>             Fix For: jdbc-5.0.0
>
>
> With the exactly-once {{JdbcSink}}, {{JdbcWriter.prepareCommit()}} prepares 
> the XA branch, resets {{pendingRecords}} and returns the committable, after 
> which {{JdbcWriter.snapshotState()}} sees {{pendingRecords == false}} and 
> calls {{commitTxUntil(lastCheckpointId)}}, which commits the branch that was 
> just prepared. The data is committed at the barrier, before the checkpoint 
> completes. A checkpoint that fails after the barrier passed the sink leaves 
> its records committed, and the restart replays and writes them again.
> The committer does not compensate: {{JdbcCommitableSerializer}} only writes 
> the {{Xid}}, so a deserialized committable carries no transaction and 
> {{JdbcCommitter.commit()}} falls back to {{commitTx()}} on its own empty 
> {{XaTransaction}}. With writer and committer in one chain the committable 
> still carries the writer's transaction object, which is why 
> {{ExactlyOnceJdbcSinkTest}} passes; with {{pipeline.operator-chaining=false}} 
> the same job leaves its only branch in-doubt and commits no row (Derby: 0 of 
> 10 rows, 1 in-doubt branch).
> The writer should only prepare the branch at the barrier and hand its xid to 
> the committer, which commits it once the checkpoint completed, on its own 
> connection. A prepared branch keeps its locks until then, and a writer 
> chained to the committer can block on them and keep the committer from 
> running. So the exactly-once committer runs in its own task: the committables 
> pass a no-op map chained to the writer and go to the first committer subtask 
> with {{global()}}, which keeps the writer's operator id for savepoints 
> without a uid.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to