vishu160196 opened a new pull request, #23117:
URL: https://github.com/apache/kafka/pull/23117

   ## Summary
   
   MirrorMaker 2's default `auto.offset.reset=earliest` masks two operationally
   dangerous scenarios in PR/DR replication: aggressive source-topic retention
   purging records before they're replicated, and a source topic being deleted
   and recreated. In both cases the source consumer silently jumps to a valid
   offset and MM2 keeps running as if nothing happened, with no signal that a
   gap now exists in the replicated log.
   
   This PR adds an opt-in `offset.validation.enabled` connector config
   (default `false`, fully backward compatible) that makes `MirrorSourceTask`
   fail fast instead: a new `DataLossException` is thrown when earlier,
   unreplicated records were purged, and a new `TopicResetException` when the
   topic was deleted and recreated.
   
   ## Design Rationale
   
   **Files/classes changed:**
   - `MirrorSourceConfig`: new `offset.validation.enabled` config (default 
`false`).
   - `MirrorSourceTask`:
     - `start()` — when enabled, overrides the consumer's `auto.offset.reset` to
       `none` instead of the default `earliest`, so the underlying
       `OffsetOutOfRangeException` reaches application code instead of being
       handled transparently by the consumer's built-in reset policy.
     - `initializeConsumer()` — with no reset policy, the consumer can no longer
       resolve a starting position on its own. For partitions with no previously
       committed Connect offset (first-ever startup), this now explicitly calls
       `seekToBeginning(...)` to reproduce what `earliest` used to do for free.
       Without this, brand-new tasks would fail immediately with
       `NoOffsetForPartitionException`.
     - `poll()` — new `catch (OffsetOutOfRangeException e)` ahead of the 
existing
       generic `KafkaException` catch (which today just logs a warning and
       silently returns, the root cause of the masking behavior). When enabled,
       delegates to `classifyOffsetOutOfRange(e)` and throws; otherwise 
preserves
       today's behavior unchanged.
     - `classifyOffsetOutOfRange()` — for each affected partition, compares the
       requested (now-invalid) offset against `consumer.beginningOffsets(...)`:
       `0` means the topic was recreated (`TopicResetException`), `> 0` means
       retention purged a prefix of the log (`DataLossException`). Logs topic,
       partition, requested offset, and earliest offset at `ERROR` before
       throwing. If a batch spans partitions with mixed causes, the task treats
       it as the more severe data-loss case.
   - `DataLossException` / `TopicResetException` (new): unchecked, extend
     `ConnectException`, so throwing them from `poll()` fails the task the same
     way the file's existing `catch (Throwable e) { throw e; }` fallback already
     does — no new propagation plumbing needed.
   
   **Why this strategy:** `auto.offset.reset=none` is the only setting under
   which the consumer ever surfaces `OffsetOutOfRangeException` at all — under
   `earliest`/`latest` the reset happens inside `FetchCollector` before the
   exception type even exists, so there's no way to detect either scenario
   without this consumer config change first. Everything else follows from
   that: an explicit `seekToBeginning` to keep new-task startup behavior
   identical, and a catch/classify step to turn the raised exception into a
   specific, actionable failure.
   
   **Integration with the MM2 lifecycle:** Entirely additive and opt-in — with
   the config left at its default (`false`), `auto.offset.reset` stays
   `earliest`, `initializeConsumer()` and `poll()` are byte-for-byte unchanged 
in
   behavior, and `OffsetOutOfRangeException` (as a `KafkaException` subtype)
   continues to fall through to the existing generic catch exactly as before.
   
   **Known gap:** this adds a new public connector config, which per this
   repo's contribution guidelines generally warrants a KIP before merging
   upstream; none has been filed here since this work originated as a
   self-contained take-home exercise against a personal fork.
   
   ## Test Plan
   
   - [x] Unit tests (`MirrorSourceTaskTest`): `seekToBeginning` on enable,
         `DataLossException` thrown, `TopicResetException` thrown, disabled-flag
         fallback preserves today's warn-and-swallow behavior, mixed-partition
         classification prefers data loss.
   - [x] Config test (`MirrorSourceConfigTest`): default `false`, settable to
         `true`, registered in `CONNECTOR_CONFIG_DEF`, propagates to
         `MirrorSourceTaskConfig`.
   - [x] Integration tests (`DedicatedMirrorIntegrationTest`, embedded 
clusters):
     - `testTaskFailsOnDataLossFromPurgedRecords` — drains initial replication,
       stops MM2, writes further records and deletes them via
       `Admin#deleteRecords` before MM2 ever consumed them, restarts MM2, and
       asserts the task fails with `DataLossException` in its trace.
     - `testTaskFailsOnTopicReset` — drains initial replication, stops MM2,
       deletes and recreates the topic, restarts MM2, and asserts the task fails
       with `TopicResetException` in its trace.
   - [x] `./gradlew :connect:mirror:test` — full module suite green.
   - [x] `./gradlew :connect:mirror:spotlessApply 
:connect:mirror:checkstyleMain :connect:mirror:checkstyleTest` — clean.
   
   ## Evaluation Criteria (from project brief)
   
   - **Functional correctness**: fail-fast logic implemented for both
     truncation and reset; all new and existing tests pass.
   - **Technical excellence**: change is opt-in and additive, confined to
     `connect/mirror` (~148 LOC in `main`, excluding tests), reuses the file's
     existing "throw to fail the task" idiom rather than inventing new
     propagation machinery, and passes this repo's checkstyle/spotless gates.
   - **Test quality**: unit tests isolate the classification and consumer-seek
     logic with mocks; integration tests exercise the real embedded-cluster
     failure paths end-to-end (deterministically, via `deleteRecords` and
     topic delete/recreate rather than waiting on real retention timers).
   - **Documentation quality**: this description covers rationale, affected
     files, and lifecycle integration above.
   
   ## AI Usage Documentation
   
   Implemented with Claude Code (Claude Sonnet 5): used to map the MM2 class
   structure, analyze the consumer's 
`auto.offset.reset`/`OffsetOutOfRangeException`
   semantics in `clients`, implement the config/task/exception changes, and
   write the unit and integration tests. All design decisions (opt-in vs
   default-on, the mixed-partition data-loss-wins policy) were reviewed and
   confirmed interactively before implementation.
   


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