junrao commented on code in PR #24028:
URL: https://github.com/apache/kafka/pull/24028#discussion_r4445610716
##########
storage/src/test/java/org/apache/kafka/server/log/remote/storage/RemoteLogManagerTest.java:
##########
@@ -1672,15 +1672,109 @@ void testFindOffsetByTimestampWithSegmentNotReady()
throws IOException, RemoteSt
leaderEpochFileCache.assign(targetLeaderEpoch, startOffset);
leaderEpochFileCache.assign(12, 500L);
- doTestFindOffsetByTimestamp(ts, startOffset, targetLeaderEpoch,
validSegmentEpochs, RemoteLogSegmentState.COPY_SEGMENT_STARTED);
+ setUpRemoteSegmentForFindOffsetByTimestamp(ts, startOffset,
targetLeaderEpoch, validSegmentEpochs,
RemoteLogSegmentState.COPY_SEGMENT_STARTED);
Optional<FileRecords.TimestampAndOffset> maybeTimestampAndOffset =
remoteLogManager.findOffsetByTimestamp(tp, ts, startOffset,
leaderEpochFileCache);
assertEquals(Optional.empty(), maybeTimestampAndOffset);
}
- private void doTestFindOffsetByTimestamp(long ts, long startOffset, int
targetLeaderEpoch,
- TreeMap<Integer, Long>
validSegmentEpochs,
- RemoteLogSegmentState state)
throws IOException, RemoteStorageException {
+ @ParameterizedTest
+ @CsvSource({"false, false", "true, false", "true, true"})
+ void testFindOffsetByTimestampContinuesAfterEmptyRemoteSegment(boolean
nextEpoch, boolean spansEpochs) throws Exception {
Review Comment:
EmptyRemoteSegment is not accurate. Could we change the test name to match
what's in UnifiedLogTest? Ditto for
testFindOffsetByTimestampContinuesAfterEmptyLocalSegment.
##########
storage/src/test/java/org/apache/kafka/storage/internals/log/UnifiedLogTest.java:
##########
@@ -3542,6 +3542,29 @@ public void
testFetchOffsetByTimestampIncludesLeaderEpoch() throws IOException {
log.fetchOffsetByTimestamp(ListOffsetsRequest.LATEST_TIMESTAMP,
Optional.empty()));
}
+ @Test
+ public void
testFetchOffsetByTimestampSearchesNextSegmentWhenMatchIsBelowLogStartOffset()
throws IOException {
+ long targetTimestamp = mockTime.milliseconds();
+ LogConfig logConfig = new LogTestUtils.LogConfigBuilder()
+ .indexIntervalBytes(1)
+ .build();
+ log = createLog(logDir, logConfig);
+
+ // The expired record makes the first segment eligible, but its only
retained record has a lower timestamp.
Review Comment:
`expired` is not very accurate. Retention always removes the full segment.
Only deleteRecords can move the start offset into the middle of a segment.
##########
storage/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogManager.java:
##########
@@ -752,14 +757,15 @@ && isRemoteSegmentWithinLeaderEpochs(rlsMetadata,
unifiedLog.logEndOffset(), epo
List<LogSegment> segmentsCopy = unifiedLog.logSegments();
if (segmentsCopy.isEmpty() || rlsMetadata.startOffset() <
segmentsCopy.get(0).baseOffset()) {
// search in remote-log
- return lookupTimestamp(rlsMetadata, timestamp,
startingOffset, maxRecordBodySize);
- } else {
- // search in local-log
- for (LogSegment segment : segmentsCopy) {
- if (segment.largestTimestamp() >= timestamp) {
- return
segment.findOffsetByTimestamp(timestamp, startingOffset, maxRecordBodySize);
- }
+ Optional<FileRecords.TimestampAndOffset>
timestampAndOffset =
+ lookupTimestamp(rlsMetadata, timestamp,
startingOffset, maxRecordBodySize);
+ if (timestampAndOffset.isPresent()) {
+ return timestampAndOffset;
}
+ searchedThrough = rlsMetadata.endOffset();
+ } else {
+ // The local log covers this segment and all later
offsets, so its result is final.
+ return unifiedLog.searchOffsetInLocalLog(timestamp,
startingOffset);
Review Comment:
Claude found the following.
```
1. Use the same segment snapshot for the check and the search
(RemoteLogManager.java:757-768). segmentsCopy decides whether the remote
segment is covered by the local log, but searchOffsetInLocalLog takes a new
snapshot with logSegments().
If retention deletes the first local segments between the two calls, the
local log can start after rlsMetadata.startOffset(). The offsets in between are
then only in remote storage. The local search misses them and returns a later
offset, or nothing, as the final answer.
The window is tiny, but the old inline loop searched segmentsCopy, which
avoided it. An overload that takes the snapshot fixes it:
public Optional<FileRecords.TimestampAndOffset> searchOffsetInLocalLog(long
targetTimestamp, long startOffset) throws IOException {
// Cache to avoid race conditions.
return searchOffsetInLocalLog(logSegments(), targetTimestamp,
startOffset);
}
/**
* Find the first record at or after startOffset, in the given snapshot of
the local segments, whose timestamp is at
* least targetTimestamp. startOffset may be below the first segment.
*/
public Optional<FileRecords.TimestampAndOffset>
searchOffsetInLocalLog(List<LogSegment> segments, long targetTimestamp,
long
startOffset) throws IOException {
// ... the current body, without taking the snapshot
}
RemoteLogManager would then call
unifiedLog.searchOffsetInLocalLog(segmentsCopy, timestamp, startingOffset). The
two thenCallRealMethod() stubs in RemoteLogManagerTest would need to target the
three-argument overload.
```
##########
storage/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogManager.java:
##########
@@ -737,13 +737,18 @@ public Optional<FileRecords.TimestampAndOffset>
findOffsetByTimestamp(TopicParti
OptionalInt maybeEpoch =
leaderEpochCache.epochForOffset(startingOffset);
TopicIdPartition topicIdPartition = new TopicIdPartition(topicId, tp);
NavigableMap<Integer, Long> epochWithOffsets =
buildFilteredLeaderEpochMap(leaderEpochCache.epochWithOffsets());
+ long searchedThrough = startingOffset - 1;
while (maybeEpoch.isPresent()) {
int epoch = maybeEpoch.getAsInt();
// KAFKA-15802: Add a new API for RLMM to choose how to implement
the predicate.
// currently, all segments are returned and then iterated, and
filtered
Iterator<RemoteLogSegmentMetadata> iterator =
remoteLogMetadataManagerPlugin.get().listRemoteLogSegments(topicIdPartition,
epoch);
while (iterator.hasNext()) {
RemoteLogSegmentMetadata rlsMetadata = iterator.next();
+ // A segment spanning multiple leader epochs is listed once
for each epoch.
Review Comment:
Claude suggested the following.
`The skip comment in RemoteLogManager ("A segment spanning multiple leader
epochs is listed once for each epoch") explains why the skip is needed, but not
what it does. Something like "Skip segments already searched: a segment
spanning multiple leader epochs is listed once for each epoch" says both.`
--
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]