nahidupa opened a new pull request, #18012:
URL: https://github.com/apache/iceberg/pull/18012

   ## Problem
   
   `doCommit` carried this comment:
   
   ```java
   // we should only get here if all tables committed successfully...
   commitConsumerOffsets();
   commitState.clearResponses();
   ```
   
   The comment was not true. `commitToTable` returned `void`, and two paths 
exited
   early after only a warning:
   
   ```java
   } catch (NoSuchTableException e) {
     LOG.warn("Table not found, skipping commit: {}", tableIdentifier, e);
     return;                                      // indistinguishable from 
success
   }
   
   if (tableReference.uuid() != null && 
!tableReference.uuid().equals(table.uuid())) {
     LOG.warn("Skipping commits to table {} due to target table mismatch. ...");
     return;                                      // indistinguishable from 
success
   }
   ```
   
   Because nothing propagated, the cycle advanced its control-topic checkpoint 
past
   those responses, cleared them from the buffer, and published 
`CommitComplete` —
   as though the files had been registered.
   
   The worker had already published `DataWritten` and committed its source 
offsets
   in a single Kafka transaction, so the input records are not redelivered. The
   data files remain in object storage, referenced by no snapshot.
   
   ```mermaid
   sequenceDiagram
       autonumber
       participant W as Sink worker
       participant K as Control topic
       participant C as Coordinator
       participant T as Destination table
   
       W->>K: DataWritten(file X) + source offsets<br/>(one transaction)
       K-->>C: buffer file X
       Note over T: table renamed, dropped,<br/>or replaced with a new UUID
       C->>T: loadTable / UUID check
       T--x C: NoSuchTableException or UUID mismatch
       Note over C: warn, return -- read as success
       C->>K: commitConsumerOffsets()<br/>checkpoint advances past X
       C->>C: clearResponses()<br/>X is discarded
       C->>K: CommitComplete
       Note over C,T: file X is in storage, in no snapshot,<br/>and its source 
records are already consumed
   ```
   
   Restoring the original table afterwards does not recover the discarded 
response.
   
   Retaining the batch and returning normally is not sufficient on its own.
   Responses for an old and a new UUID under one table name can then block
   completion indefinitely, even after both tables have received their own 
files.
   Returning normally also resets the consecutive-failure counter despite the
   incomplete cycle.
   
   ## Changes
   
   - Report missing-table and UUID-mismatch outcomes to the enclosing commit.
   - Raise a private `IncompleteCommitException` before checkpointing, buffer
     cleanup, or `CommitComplete` publication when any table remains unresolved.
   - Reuse `iceberg.control.commit.max-consecutive-failures` for these failures 
in
     both full and timed-out partial commits. The existing default of `1` fails 
on
     the first incomplete attempt; higher values permit later-cycle retries.
   - Preserve failure accounting until a full commit succeeds, and increment the
     partial-commit failure counter for incomplete partial attempts.
   - Preserve UUID validation and per-table snapshot-offset deduplication.
   - Document the retry policy and operational recovery constraints.
   
   ```mermaid
   flowchart TD
       A[doCommit] --> B{every table committed?}
       B -- yes --> C[commitConsumerOffsets]
       C --> D[clearResponses]
       D --> E[publish CommitComplete]
       B -- no --> F[throw IncompleteCommitException]
       F --> G{consecutive failures<br/>below configured limit?}
       G -- yes --> H[keep buffer and checkpoint<br/>retry on a later cycle]
       G -- no --> I[terminate the coordinator<br/>operator resolves the table]
   
       classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
       classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
       class C,D,E,H ok
       class F,I bad
   ```
   
   `IncompleteCommitException` extends `CommitFailedException` so that the 
existing
   retry classification in `commit()` applies without a second code path. It is
   private to `Coordinator` and is never thrown across a public boundary.
   
   No new configuration property, dependency, or public API is introduced. Other
   partial-commit exception handling is unchanged.
   
   ## Behavior and recovery
   
   This deliberately changes missing/replaced-table handling from 
warn-and-discard
   to bounded retry followed by coordinator failure. It does not silently 
discard
   old-identity files, and it does not append them to a replacement table.
   
   The same scenario as the first diagram, after the fix:
   
   ```mermaid
   sequenceDiagram
       autonumber
       participant W as Sink worker
       participant K as Control topic
       participant C as Coordinator
       participant T as Destination table
   
       W->>K: DataWritten(file X) + source offsets<br/>(one transaction)
       K-->>C: buffer file X
       Note over T: table renamed, dropped,<br/>or replaced with a new UUID
       C->>T: loadTable / UUID check
       T--x C: NoSuchTableException or UUID mismatch
       Note over C: commitToTable reports false,<br/>doCommit throws 
IncompleteCommitException
       Note over C,K: checkpoint not advanced,<br/>buffer not cleared,<br/>no 
CommitComplete published
   
       alt original table restored within the retry budget
           Note over T: original identifier and UUID restored
           C->>T: loadTable / UUID check
           T-->>C: table
           C->>T: append file X
           T-->>C: snapshot created
           C->>K: commitConsumerOffsets()
           C->>C: clearResponses()
           C->>K: CommitComplete
           Note over C,T: file X is registered exactly once
       else retry budget exhausted
           Note over C: coordinator terminates 
with<br/>IncompleteCommitException
           Note over C,K: file X is still buffered and still<br/>announced on 
the control topic
           Note over C,T: operator resolves the table,<br/>then the task 
restarts
       end
   ```
   
   Neither branch discards file X, and neither writes it into a replacement 
table.
   
   | Situation | Before | After |
   | --- | --- | --- |
   | Table briefly unavailable | checkpoint advances, response discarded | 
response retained, commits when the table returns |
   | Table replaced with a new UUID | replacement protected, original file 
discarded | replacement protected, original file retained |
   | Table permanently gone | silent data loss | bounded retries, then the 
coordinator fails loudly |
   | Other tables in the same batch | committed | still committed, not rolled 
back |
   
   Temporary unavailability recovers when the original identifier and UUID are
   restored within the retry budget. Permanent deletion or replacement, and
   buffered responses for multiple UUIDs under one name, require operator
   intervention. Raising the retry limit cannot reconcile incompatible 
identities.
   
   Successful Iceberg commits to other tables are not rolled back. Their 
buffered
   responses stay until the batch completes; the existing snapshot-offset checks
   prevent duplicate registration when those responses are retried, provided the
   required snapshot history is still available.
   
   The retry limit bounds failed attempts, not the bytes accumulated within an
   attempt. The checkpoint is not advanced by an incomplete cycle, so recovery
   after a task restart still depends on retained control-topic records and a
   suitable consumer starting offset. This change does not alter offset-reset
   policy, nor guarantee recovery once control records or deduplication history
   have expired.
   
   The initial missing-table behavior of dynamic routing is unchanged and 
remains
   outside this change.
   
   ## Regression coverage
   
   `TestCoordinatorTableLookup`, 12 tests covering default and configured 
limits,
   same-UUID recovery, replacement protection, mixed-UUID termination, full and
   partial retry accounting, failure-counter preservation and reset, and
   deduplication when a healthy table's responses are retried. The mixed-table
   cases run with two commit threads and assert file locations, Kafka 
checkpoints,
   and completion-event behavior.
   
   The suite is not vacuous. With the `IncompleteCommitException` throw removed 
—
   restoring the old skip-and-checkpoint behavior — **11 of the 12 fail**; only 
the
   healthy-table case still passes:
   
   ```
   missingTableFailsAtDefaultRetryLimitWithoutCheckpointing()                   
 FAILED
   missingTableRetainsResponsesAndCommitsThemWhenTheTableReturns()              
 FAILED
   
replacementTableIsProtectedAndTheOriginalFileIsCommittedWhenTheTableReturns() 
FAILED
   mixedTableUUIDsFailWithinLimitEvenWhenBothFilesHaveCommitted()               
 FAILED
   missingTableRetryDoesNotDuplicateHealthyTableFiles()                         
 FAILED
   ... 12 tests completed, 11 failed
   ```
   
   ## Validation
   
   Validated on 2026-09-08 against base `af86b8399`:
   
   - Full connector unit suite: **145 tests across 19 suites**, zero failures,
     errors, or skips.
   - `spotlessCheck`, `checkstyleMain`, and `checkstyleTest` passed.
   - `git diff --check` passed.
   
   ```sh
   ./gradlew -DsparkVersions= -DflinkVersions= -DkafkaVersions=3 \
     :iceberg-kafka-connect:iceberg-kafka-connect:check -x integrationTest 
--rerun-tasks
   ```
   
   The tests use an in-memory Iceberg catalog and Kafka mock clients. No
   real-broker integration test, coordinator-restart test, or heap stress test 
was
   run.
   
   ---
   
   <!-- markdownlint-disable-next-line MD036 -->
   **AI Disclosure**
   
   - Model: GPT-6 Astra (implementation, tests, and documentation); Claude Opus 
5
     (audit that identified the defect, and review of this change).
   - Platform/Tool: GitHub Copilot (implementation); opencode (audit and 
review).
   - Human Oversight: partially reviewed.
   - Prompt Summary: An AI audit of the Kafka Connect coordinator identified 
that
     missing-table and UUID-mismatch commits were checkpointed and discarded as 
if
     they had succeeded. A second agent implemented bounded-retry handling, the
     regression suite, and the documentation. The first agent then reviewed the
     result and verified the claims independently, including confirming the 
tests
     fail when the fix is reverted. Code, tests, and this description are
     AI-generated and partially reviewed by the author.
   


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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to