Skip to content

KAFKA-20913: Snapshot CompletedBatch appendTimestamp before buffer reuse - #23311

Open
shantanushukla wants to merge 1 commit into
apache:trunkfrom
shantanushukla:KAFKA-20913
Open

KAFKA-20913: Snapshot CompletedBatch appendTimestamp before buffer reuse#23311
shantanushukla wants to merge 1 commit into
apache:trunkfrom
shantanushukla:KAFKA-20913

Conversation

@shantanushukla

@shantanushukla shantanushukla commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

JIRA: https://issues.apache.org/jira/browse/KAFKA-20913

On the Raft leader, KafkaRaftClient.appendBatch writes the batch to the log, registers an async commit callback, then release()s the pooled buffer. CompletedBatch.appendTimestamp() was reading MemoryRecords.firstBatch().maxTimestamp() from a slice of that buffer. Production uses BatchMemoryPool, which requeues the buffer; a later batch overwrites the v2 header. When HWM later completes the callback, handleCommit can deliver the newer batch's append time.

Fix

Snapshot maxTimestamp when CompletedBatch is constructed and return that field from appendTimestamp(). The timestamp is fixed at build time (BatchBuilder writes time.milliseconds() into the header). Record payloads and sizeInBytes() are not affected.

Why existing tests missed this

  • RaftClientTestContext defaults to MemoryPool.NONE, which allocates a new buffer every time and never reuses.
  • BatchAccumulatorTest called appendTimestamp() before release(), or verified release() without reading the timestamp afterward.
  • If the listener is still at the starting offset, commits come from log replay (the log copies records at append time), which hides the in-memory path.

Tests

These failed on trunk (first batch reported T2) and pass after the snapshot:

  • BatchAccumulatorTest.testAppendTimestampUnchangedAfterBufferReleasedAndReused — one-buffer BatchMemoryPool; drain/release at T1, append at T2; first batch must still report T1.
  • KafkaRaftClientTest.testLeaderHandleCommitAppendTimestampStableWhenPoolReusesBuffer — two voters so HWM lags; catch the listener up past the leader-change batch; append at T1 then T2 with a single reused buffer; after commit the listener's first data batch must still be T1.

Related existing tests still pass: full BatchAccumulatorTest, testListenerCommitCallbackAfterLeaderWrite, testAccumulatorClearedAfterBecomingFollower.

./gradlew :raft:spotlessApply :raft:checkstyleMain :raft:checkstyleTest :raft:spotlessCheck
./gradlew :raft:test --tests org.apache.kafka.raft.internals.BatchAccumulatorTest --tests org.apache.kafka.raft.KafkaRaftClientTest.testLeaderHandleCommitAppendTimestampStableWhenPoolReusesBuffer --tests org.apache.kafka.raft.KafkaRaftClientTest.testListenerCommitCallbackAfterLeaderWrite --tests org.apache.kafka.raft.KafkaRaftClientTest.testAccumulatorClearedAfterBecomingFollower

BUILD SUCCESSFUL.

CompletedBatch.appendTimestamp() read maxTimestamp from MemoryRecords that
may share a pooled buffer. After release(), a later batch can overwrite that
header, so the leader commit callback delivered the wrong append time.

Store the timestamp when the completed batch is constructed.
@github-actions github-actions Bot added triage PRs from the community kraft labels Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kraft triage PRs from the community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant