-
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
Changes from 2 commits
df79e31
c2cd2de
3dab4ce
882f772
154c525
272443c
c800c6c
63dd63f
fe6ccd3
8fc4e2e
c8f06a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,15 +269,38 @@ 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 when there are no new messages: | ||
|
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. nit: 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 +310,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); | ||
|
|
@@ -297,12 +322,13 @@ void testCopyLogSegmentsToRemoteShouldCopyExpectedLogSegment() throws Exception | |
|
|
||
| when(mockLog.activeSegment()).thenReturn(activeSegment); | ||
| when(mockLog.logStartOffset()).thenReturn(oldSegmentStartOffset); | ||
| when(mockLog.logSegments(anyLong(), anyLong())).thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment, activeSegment))); | ||
| when(mockLog.nonActiveLogSegmentsFrom(anyLong())) | ||
| .thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment))); | ||
|
|
||
| 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); | ||
|
|
@@ -349,7 +375,7 @@ void testCopyLogSegmentsToRemoteShouldCopyExpectedLogSegment() throws Exception | |
| assertEquals(remoteLogSegmentMetadataArg.getValue(), remoteLogSegmentMetadataArg2.getValue()); | ||
| // The old segment should only contain leader epoch [0->0, 1->100] since its offset range is [0, 149] | ||
| verifyLogSegmentData(logSegmentDataArg.getValue(), idx, timeIdx, txnIndex, tempFile, mockProducerSnapshotIndex, | ||
| Arrays.asList(epochEntry0, epochEntry1)); | ||
| Arrays.asList(epochEntry0, epochEntry1)); | ||
|
|
||
| // verify remoteLogMetadataManager did add the expected RemoteLogSegmentMetadataUpdate | ||
| ArgumentCaptor<RemoteLogSegmentMetadataUpdate> remoteLogSegmentMetadataUpdateArg = ArgumentCaptor.forClass(RemoteLogSegmentMetadataUpdate.class); | ||
|
|
@@ -480,7 +506,7 @@ void testMetricsUpdateOnCopyLogSegmentsFailure() throws Exception { | |
|
|
||
| when(mockLog.activeSegment()).thenReturn(activeSegment); | ||
| when(mockLog.logStartOffset()).thenReturn(oldSegmentStartOffset); | ||
| when(mockLog.logSegments(anyLong(), anyLong())).thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment, activeSegment))); | ||
| when(mockLog.nonActiveLogSegmentsFrom(anyLong())).thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment))); | ||
|
|
||
| ProducerStateManager mockStateManager = mock(ProducerStateManager.class); | ||
| when(mockLog.producerStateManager()).thenReturn(mockStateManager); | ||
|
|
@@ -544,7 +570,7 @@ void testCopyLogSegmentsToRemoteShouldNotCopySegmentForFollower() throws Excepti | |
|
|
||
| when(mockLog.activeSegment()).thenReturn(activeSegment); | ||
| when(mockLog.logStartOffset()).thenReturn(oldSegmentStartOffset); | ||
| when(mockLog.logSegments(anyLong(), anyLong())).thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment, activeSegment))); | ||
| when(mockLog.nonActiveLogSegmentsFrom(anyLong())).thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(oldSegment))); | ||
| when(mockLog.lastStableOffset()).thenReturn(250L); | ||
|
|
||
| RemoteLogManager.RLMTask task = remoteLogManager.new RLMTask(leaderTopicIdPartition); | ||
|
|
@@ -823,6 +849,56 @@ public RemoteLogMetadataManager createRemoteLogMetadataManager() { | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testEnrichedLogSegments() { | ||
| UnifiedLog log = mock(UnifiedLog.class); | ||
| LogSegment segment1 = mock(LogSegment.class); | ||
| LogSegment segment2 = mock(LogSegment.class); | ||
| LogSegment segment3 = mock(LogSegment.class); | ||
|
|
||
| when(segment1.baseOffset()).thenReturn(5L); | ||
| when(segment2.baseOffset()).thenReturn(10L); | ||
| when(segment3.baseOffset()).thenReturn(15L); | ||
| when(segment3.readNextOffset()).thenReturn(22L); | ||
|
|
||
| when(log.nonActiveLogSegmentsFrom(5L)) | ||
| .thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(segment1, segment2, segment3))); | ||
|
|
||
| RemoteLogManager.RLMTask task = remoteLogManager.new RLMTask(leaderTopicIdPartition); | ||
| List<RemoteLogManager.EnrichedLogSegment> expected = | ||
| Arrays.asList( | ||
| new RemoteLogManager.EnrichedLogSegment(segment1, 10L), | ||
| new RemoteLogManager.EnrichedLogSegment(segment2, 15L), | ||
| new RemoteLogManager.EnrichedLogSegment(segment3, 22L) | ||
| ); | ||
|
|
||
| assertEquals(expected, task.enrichedLogSegments(log, 5L, 20L)); | ||
| } | ||
|
|
||
| @Test | ||
| public void testEnrichedLogSegmentsSkipsNextReadOffsetCall() { | ||
| UnifiedLog log = mock(UnifiedLog.class); | ||
| LogSegment segment1 = mock(LogSegment.class); | ||
| LogSegment segment2 = mock(LogSegment.class); | ||
| LogSegment segment3 = mock(LogSegment.class); | ||
|
|
||
| when(segment1.baseOffset()).thenReturn(5L); | ||
| when(segment2.baseOffset()).thenReturn(10L); | ||
| when(segment3.baseOffset()).thenReturn(15L); | ||
|
|
||
| when(log.nonActiveLogSegmentsFrom(5L)) | ||
| .thenReturn(JavaConverters.collectionAsScalaIterable(Arrays.asList(segment1, segment2, segment3))); | ||
|
|
||
| RemoteLogManager.RLMTask task = remoteLogManager.new RLMTask(leaderTopicIdPartition); | ||
| List<RemoteLogManager.EnrichedLogSegment> expected = | ||
| Arrays.asList( | ||
| new RemoteLogManager.EnrichedLogSegment(segment1, 10L), | ||
| new RemoteLogManager.EnrichedLogSegment(segment2, 15L) | ||
| ); | ||
|
|
||
| assertEquals(expected, task.enrichedLogSegments(log, 5L, 15L)); | ||
| } | ||
|
|
||
| private Partition mockPartition(TopicIdPartition topicIdPartition) { | ||
| TopicPartition tp = topicIdPartition.topicPartition(); | ||
| Partition partition = mock(Partition.class); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
Why can't we use the next segment's baseOffset here like the above did?
Also, I'm not very familiar with this, what's the difference between
readNextOffsetandbaseOffset of next segment?Uh oh!
There was an error while loading. Please reload this page.
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.
For a given LogSegment, we know about start-offset (base-offset) but not the end offset.
readNextOffsetdenotesend-offset-of-that-segment+ 1.To exclude the active segment, we are using
log.nonActiveLogSegmentsFrom. With this, we cannot use the active-segment-base-offset as the operations are not done atomically. (the active segment might gets rotated in the mean time).If we want to avoid
LogSegment#nextReadOffsetoperation altogether, then we can list all the segments including the active and discard/filter the final entry.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.
Yes, that's my thought, given the
LogSegment#nextReadOffsetis expensive. Any drawbacks for this solution?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.
Updated