zhuxiangyi commented on code in PR #9667:
URL: https://github.com/apache/paimon/pull/9667#discussion_r4000068987


##########
paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/commands/PaimonSparkWriter.scala:
##########
@@ -472,14 +499,38 @@ case class PaimonSparkWriter(
       postCommit(finalMessages)
       return
     }
-    val activeWriteBuilder =
-      Option(directPostponeWriteBuilder).getOrElse(writeBuilder)
-    val tableCommit = activeWriteBuilder.newCommit()
+    val tableCommit: InnerTableCommit =
+      if (directPostponeWriteBuilder != null) {
+        directPostponeWriteBuilder.newCommit()
+      } else {
+        writeBuilder.asInstanceOf[BatchWriteBuilderImpl].newCommit()
+      }
     if (operation != null) {
       tableCommit.withOperation(operation)
     }
     try {
-      tableCommit.commit(commitMessages.toList.asJava)
+      idempotentCommitIdentifier match {
+        case Some(identifier) =>
+          // Structured Streaming replays a micro-batch with its original 
batch id after a failure.
+          // Committing under a stable commit user lets Paimon skip a replay 
it already committed,
+          // instead of duplicating the whole batch, while still retrying the 
commit callbacks
+          // that may have failed after the snapshot was published. Either 
builder was given the
+          // stable user, so its lookup of the previous commit is not bounded 
by strict mode.
+          //
+          // The files being committed were written by this very batch, so 
there is no need to
+          // list them to prove that they still exist, nor to scan the base 
files they could
+          // conflict with. And this committer is closed right after the 
batch, so maintenance
+          // cannot be left to an executor that is about to be shut down, nor 
a failure to a
+          // commit that never comes.
+          tableCommit
+            .checkFilesExistence(false)
+            .checkAppendFiles(false)
+            .inlineMaintenance(true)
+            .filterAndCommit(
+              Collections.singletonMap(Long.box(identifier), 
commitMessages.toList.asJava))

Review Comment:
   Confirmed, and reproduced first at the core level: after an overwrite of the 
delta branch whose cleanup failed once the snapshot was published, the replay 
was recognised — no second snapshot — but the chain table still read `value-1` 
from the snapshot branch. The empty `retry` predates this PR, as you say, and 
it was empty on purpose: its comment relied on the replay being committed 
again. Recognising the replay removed that.
   
   Fixed in bb81c169d, along the lines of `IcebergCommitCallback.retry`: the 
callback now gets its commit user, and on retry resolves the overwrite snapshot 
of the identifier with `findSnapshotsForIdentifiers` and redoes the cleanup 
from the delta manifest changes of that snapshot, so partitions the overwrite 
only removed files from are covered too. Truncating the partitions again is 
idempotent. One choice worth your look: a snapshot that can no longer be found 
is logged and skipped rather than failed on, unlike the Iceberg callback — it 
has either expired, in which case a later snapshot has superseded it, or the 
replay would fail forever.
   
   The callback had no test of its own before, so 6a96de471 covers its normal 
path, the failure/replay case, the same for a partition the overwrite only 
removed files from — a mutant that takes partitions from ADD entries only fails 
it — and a retry for an identifier no snapshot carries. The failure is a real 
one: the delta table is told to clean a snapshot branch that does not exist, so 
the commit publishes its snapshot and then throws, which is the same window as 
a transient failure of the snapshot branch, without any file system fault 
injection. All chain table suites and the core regression pass.
   
   While surveying every `CommitCallback.retry` for this class of problem I 
found one more thing you should decide on. `VisibilityWaitCallback.call` only 
acts when the identifier is `BatchWriteBuilder.COMMIT_IDENTIFIER`. Before this 
PR the sink committed micro-batches as batch commits, so a query with 
`visibility-callback.enabled` waited for visibility after each one; now the 
identifier is the batch id and the wait does not run. That matches the option's 
stated scope, batch mode or bounded stream, and the Flink streaming behaviour, 
so I have left it, but it is a behaviour change for such a query and I would 
rather you call it than have it surface later. The other callbacks 
(`AddPartitionCommitCallback`, `TagPreviewCommitCallback`, 
`IcebergCommitCallback`) already implement retry.



-- 
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