Xiaobing Fang created KAFKA-21367:
-------------------------------------

             Summary: Stale recovery checkpoint can overwrite truncation 
checkpoint and skip log recovery
                 Key: KAFKA-21367
                 URL: https://issues.apache.org/jira/browse/KAFKA-21367
             Project: Kafka
          Issue Type: Bug
          Components: core
            Reporter: Xiaobing Fang


h3. Problem

{\{LogManager.checkpointRecoveryOffsetsInDir}} collects recovery points before 
calling \{{OffsetCheckpointFile.write}}. The lock in \{{CheckpointFile.write}} 
serializes file writes, but does not cover recovery point sampling.

A periodic checkpoint can therefore overwrite a checkpoint written after log 
truncation:

# A checkpoint task samples \{{recoveryPoint = 10}}, then pauses before writing.
# Another thread truncates the log to offset 0, lowering its recovery point to 
0, and writes a checkpoint containing 0.
# The first task resumes and writes its previously sampled value of 10.

The persisted recovery point is now ahead of the actual flushed boundary. This 
affects checkpoints following both \{{truncateTo}} and 
\{{truncateFullyAndStartAt}}.

h3. Recovery impact

After truncation, new records can be appended and the log can roll before the 
asynchronous flush completes. If an unclean shutdown leaves a damaged batch in 
an earlier segment, the stale checkpoint can cause recovery to skip that 
segment.

For example, with segment base offsets \{0, 1} and a stale checkpoint of 10, 
\{{LogSegments.values(10, Long.MAX_VALUE)}} starts at \{{floorKey(10) = 1}}. 
Segment 0 is not recovered. With the correct checkpoint of 0, both segments are 
included.

A corrupt batch in the skipped segment can survive startup and fail subsequent 
record validation with \{{CorruptRecordException}}. The race does not itself 
corrupt record bytes; it can prevent recovery from detecting and truncating 
damaged records.

h3. Reproduction

The checkpoint overwrite was reproduced deterministically on trunk at 
\{{2bd1d20063ce7e0843bb1482f7252b67c2a40421}}. Tests for both truncation paths 
read back 10 from the checkpoint file instead of the expected 0.

The recovery consequence was separately reproduced using controlled CRC 
corruption, a segment roll, a pending asynchronous flush, and reopening the log 
without a clean-shutdown marker. With the stale checkpoint, the damaged earlier 
segment was skipped. With the correct checkpoint, recovery detected the 
corruption and truncated the log.

CRC corruption was injected to test the recovery behavior; this was not a real 
power-loss experiment.

h3. Proposed fix

Serialize recovery point sampling and checkpoint writing using the same 
per-directory checkpoint object. This prevents an older snapshot from being 
written after a newer truncation checkpoint, without changing the checkpoint 
format.



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

Reply via email to