-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-15272: Fix the logic which finds candidate log segments to upload it to tiered storage #14128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
df79e31
KAFKA-15272: Fix the logic which finds candidate log segments to uplo…
kamalcph c2cd2de
Avoid calling expensive LogSegment#readNextOffset() operation.
kamalcph 3dab4ce
Addressed the review comments.
kamalcph 882f772
Addressed the review-2 comments.
kamalcph 154c525
Added toString
kamalcph 272443c
Renamed EnrichedLogSegment to CandidateLogSegment and addressed the r…
kamalcph c800c6c
Update the logger.
kamalcph 63dd63f
Update the test parameter.
kamalcph fe6ccd3
Rename CandidateLogSegment to EnrichedLogSegment
kamalcph 8fc4e2e
Update the test
kamalcph c8f06a7
Update the test
kamalcph File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,15 +269,37 @@ void testStartup() { | |
| void testCopyLogSegmentsToRemoteShouldCopyExpectedLogSegment() throws Exception { | ||
| long oldSegmentStartOffset = 0L; | ||
| long nextSegmentStartOffset = 150L; | ||
| long oldSegmentEndOffset = nextSegmentStartOffset - 1; | ||
| long lso = 250L; | ||
| long leo = 300L; | ||
| assertCopyExpectedLogSegmentsToRemote(oldSegmentStartOffset, nextSegmentStartOffset, lso, leo); | ||
| } | ||
|
|
||
| /** | ||
| * The following values will be equal when the active segment gets rotated to passive and there are no new messages: | ||
| * last-stable-offset = high-water-mark = log-end-offset = base-offset-of-active-segment. | ||
| * This test asserts that the active log segment that was rotated after log.roll.ms are copied to remote storage. | ||
| */ | ||
| @Test | ||
| void testCopyLogSegmentToRemoteForStaleTopic() throws Exception { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good test for the edge case of a stale topic with no activity for longer durations. |
||
| long oldSegmentStartOffset = 0L; | ||
| long nextSegmentStartOffset = 150L; | ||
| long lso = 150L; | ||
| long leo = 150L; | ||
| assertCopyExpectedLogSegmentsToRemote(oldSegmentStartOffset, nextSegmentStartOffset, lso, leo); | ||
| } | ||
|
|
||
| private void assertCopyExpectedLogSegmentsToRemote(long oldSegmentStartOffset, | ||
| long nextSegmentStartOffset, | ||
| long lastStableOffset, | ||
| long logEndOffset) throws Exception { | ||
| long oldSegmentEndOffset = nextSegmentStartOffset - 1; | ||
| when(mockLog.topicPartition()).thenReturn(leaderTopicIdPartition.topicPartition()); | ||
|
|
||
| // leader epoch preparation | ||
| checkpoint.write(totalEpochEntries); | ||
| LeaderEpochFileCache cache = new LeaderEpochFileCache(leaderTopicIdPartition.topicPartition(), checkpoint); | ||
| when(mockLog.leaderEpochCache()).thenReturn(Option.apply(cache)); | ||
| when(remoteLogMetadataManager.highestOffsetForEpoch(any(TopicIdPartition.class), anyInt())).thenReturn(Optional.of(0L)); | ||
| when(remoteLogMetadataManager.highestOffsetForEpoch(any(TopicIdPartition.class), anyInt())).thenReturn(Optional.of(-1L)); | ||
|
|
||
| File tempFile = TestUtils.tempFile(); | ||
| File mockProducerSnapshotIndex = TestUtils.tempFile(); | ||
|
|
@@ -287,7 +309,9 @@ void testCopyLogSegmentsToRemoteShouldCopyExpectedLogSegment() throws Exception | |
| LogSegment activeSegment = mock(LogSegment.class); | ||
|
|
||
| when(oldSegment.baseOffset()).thenReturn(oldSegmentStartOffset); | ||
| when(oldSegment.readNextOffset()).thenReturn(nextSegmentStartOffset); | ||
| when(activeSegment.baseOffset()).thenReturn(nextSegmentStartOffset); | ||
| when(activeSegment.readNextOffset()).thenReturn(logEndOffset); | ||
|
|
||
| FileRecords fileRecords = mock(FileRecords.class); | ||
| when(oldSegment.log()).thenReturn(fileRecords); | ||
|
|
@@ -302,7 +326,7 @@ void testCopyLogSegmentsToRemoteShouldCopyExpectedLogSegment() throws Exception | |
| ProducerStateManager mockStateManager = mock(ProducerStateManager.class); | ||
| when(mockLog.producerStateManager()).thenReturn(mockStateManager); | ||
| when(mockStateManager.fetchSnapshot(anyLong())).thenReturn(Optional.of(mockProducerSnapshotIndex)); | ||
| when(mockLog.lastStableOffset()).thenReturn(250L); | ||
| when(mockLog.lastStableOffset()).thenReturn(lastStableOffset); | ||
|
|
||
| LazyIndex idx = LazyIndex.forOffset(UnifiedLog.offsetIndexFile(tempDir, oldSegmentStartOffset, ""), oldSegmentStartOffset, 1000); | ||
| LazyIndex timeIdx = LazyIndex.forTime(UnifiedLog.timeIndexFile(tempDir, oldSegmentStartOffset, ""), oldSegmentStartOffset, 1500); | ||
|
|
@@ -823,6 +847,30 @@ public RemoteLogMetadataManager createRemoteLogMetadataManager() { | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testEnrichedLogSegments() { | ||
| UnifiedLog log = mock(UnifiedLog.class); | ||
| LogSegment segment1 = mock(LogSegment.class); | ||
| LogSegment segment2 = mock(LogSegment.class); | ||
| LogSegment activeSegment = mock(LogSegment.class); | ||
|
|
||
| when(segment1.baseOffset()).thenReturn(5L); | ||
| when(segment2.baseOffset()).thenReturn(10L); | ||
| when(activeSegment.baseOffset()).thenReturn(15L); | ||
|
|
||
| when(log.logSegments(5L, Long.MAX_VALUE)) | ||
| .thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(segment1, segment2, activeSegment))); | ||
|
|
||
| RemoteLogManager.RLMTask task = remoteLogManager.new RLMTask(leaderTopicIdPartition); | ||
| List<RemoteLogManager.EnrichedLogSegment> expected = | ||
| Arrays.asList( | ||
| new RemoteLogManager.EnrichedLogSegment(segment1, 10L), | ||
| new RemoteLogManager.EnrichedLogSegment(segment2, 15L) | ||
| ); | ||
| List<RemoteLogManager.EnrichedLogSegment> actual = task.enrichedLogSegments(log, 5L); | ||
| assertEquals(expected, actual); | ||
| } | ||
|
|
||
| private Partition mockPartition(TopicIdPartition topicIdPartition) { | ||
| TopicPartition tp = topicIdPartition.topicPartition(); | ||
| Partition partition = mock(Partition.class); | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a segment contains 0 records and
log.roll.mstimeout passed, then what will be thebaseOffsetof next active segment? Do you have any idea? I asked because I'm not sure ifenrichedSegment.logSegment.baseOffset() != activeSegmentBaseOffsetis necessary. If we already filtered out the active segment in theenrichedLogSegmentsmethod, should we also need this check?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the active segment is empty, then it won't be rotated.
The in-memory check
enrichedSegment.logSegment.baseOffset() != activeSegmentBaseOffsetis kind of redundant, we can remove it later when required.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Instead of doing this filtering after we have created the enriched segments list and iterating again can we not push the condition as part of creating the enriched segments list?