-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-14238; KRaft metadata log should not delete segment past the latest snapshot #12655
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 all commits
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 |
|---|---|---|
|
|
@@ -382,7 +382,7 @@ final class KafkaMetadataLog private ( | |
| } | ||
|
|
||
| /** | ||
| * Perform cleaning of old snapshots and log segments based on size. | ||
| * Perform cleaning of old snapshots and log segments based on size and time. | ||
| * | ||
| * If our configured retention size has been violated, we perform cleaning as follows: | ||
| * | ||
|
|
@@ -556,15 +556,29 @@ object KafkaMetadataLog { | |
| config: MetadataLogConfig | ||
| ): KafkaMetadataLog = { | ||
| val props = new Properties() | ||
| props.put(LogConfig.MaxMessageBytesProp, config.maxBatchSizeInBytes.toString) | ||
| props.put(LogConfig.SegmentBytesProp, Int.box(config.logSegmentBytes)) | ||
| props.put(LogConfig.SegmentMsProp, Long.box(config.logSegmentMillis)) | ||
| props.put(LogConfig.FileDeleteDelayMsProp, Int.box(Defaults.FileDeleteDelayMs)) | ||
| props.setProperty(LogConfig.MaxMessageBytesProp, config.maxBatchSizeInBytes.toString) | ||
| props.setProperty(LogConfig.SegmentBytesProp, config.logSegmentBytes.toString) | ||
| props.setProperty(LogConfig.SegmentMsProp, config.logSegmentMillis.toString) | ||
| props.setProperty(LogConfig.FileDeleteDelayMsProp, Defaults.FileDeleteDelayMs.toString) | ||
|
|
||
| // Disable time and byte retention when deleting segments | ||
| props.setProperty(LogConfig.RetentionMsProp, "-1") | ||
| props.setProperty(LogConfig.RetentionBytesProp, "-1") | ||
| LogConfig.validateValues(props) | ||
| val defaultLogConfig = LogConfig(props) | ||
|
|
||
| if (config.logSegmentBytes < config.logSegmentMinBytes) { | ||
| throw new InvalidConfigurationException(s"Cannot set $MetadataLogSegmentBytesProp below ${config.logSegmentMinBytes}") | ||
| throw new InvalidConfigurationException( | ||
| s"Cannot set $MetadataLogSegmentBytesProp below ${config.logSegmentMinBytes}: ${config.logSegmentBytes}" | ||
| ) | ||
| } else if (defaultLogConfig.retentionMs >= 0) { | ||
|
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. Is this really else if? Or should it be just if?
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. I guess the behavior is the same since we always throw an exception in each block, but it seems a bit odd to use else if for unrelated conditions. Feel free to ignore though. :)
Member
Author
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. It is the same. I learned to do this because it lowers the cyclomatic complexity. It looks like the static analyzer understand |
||
| throw new InvalidConfigurationException( | ||
| s"Cannot set ${LogConfig.RetentionMsProp} above -1: ${defaultLogConfig.retentionMs}." | ||
| ) | ||
| } else if (defaultLogConfig.retentionSize >= 0) { | ||
| throw new InvalidConfigurationException( | ||
| s"Cannot set ${LogConfig.RetentionBytesProp} above -1: ${defaultLogConfig.retentionSize}." | ||
| ) | ||
| } | ||
|
|
||
| val log = UnifiedLog( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,13 +66,13 @@ final class KafkaMetadataLogTest { | |
| props.put(MetadataLogSegmentMillisProp, Int.box(10 * 1024)) | ||
| assertThrows(classOf[InvalidConfigurationException], () => { | ||
| val kafkaConfig = KafkaConfig.fromProps(props) | ||
| val metadataConfig = MetadataLogConfig.apply(kafkaConfig, KafkaRaftClient.MAX_BATCH_SIZE_BYTES, KafkaRaftClient.MAX_FETCH_SIZE_BYTES) | ||
| val metadataConfig = MetadataLogConfig(kafkaConfig, KafkaRaftClient.MAX_BATCH_SIZE_BYTES, KafkaRaftClient.MAX_FETCH_SIZE_BYTES) | ||
| buildMetadataLog(tempDir, mockTime, metadataConfig) | ||
| }) | ||
|
|
||
| props.put(MetadataLogSegmentMinBytesProp, Int.box(10240)) | ||
| val kafkaConfig = KafkaConfig.fromProps(props) | ||
| val metadataConfig = MetadataLogConfig.apply(kafkaConfig, KafkaRaftClient.MAX_BATCH_SIZE_BYTES, KafkaRaftClient.MAX_FETCH_SIZE_BYTES) | ||
| val metadataConfig = MetadataLogConfig(kafkaConfig, KafkaRaftClient.MAX_BATCH_SIZE_BYTES, KafkaRaftClient.MAX_FETCH_SIZE_BYTES) | ||
| buildMetadataLog(tempDir, mockTime, metadataConfig) | ||
| } | ||
|
|
||
|
|
@@ -869,6 +869,56 @@ final class KafkaMetadataLogTest { | |
| }) | ||
| }) | ||
| } | ||
|
|
||
| @Test | ||
| def testSegmentsLessThanLatestSnapshot(): Unit = { | ||
| val config = DefaultMetadataLogConfig.copy( | ||
| logSegmentBytes = 10240, | ||
| logSegmentMinBytes = 10240, | ||
| logSegmentMillis = 10 * 1000, | ||
| retentionMaxBytes = 10240, | ||
| retentionMillis = 60 * 1000, | ||
| maxBatchSizeInBytes = 200 | ||
| ) | ||
| val log = buildMetadataLog(tempDir, mockTime, config) | ||
|
|
||
| // Generate enough data to cause a segment roll | ||
| for (_ <- 0 to 2000) { | ||
| append(log, 10, 1) | ||
| } | ||
| log.updateHighWatermark(new LogOffsetMetadata(log.endOffset.offset)) | ||
|
|
||
| // The clean up code requires that there are at least two snapshots | ||
| // Generate first snapshots that includes the first segment by using the base offset of the second segment | ||
| val snapshotId1 = new OffsetAndEpoch( | ||
| log.log.logSegments.drop(1).head.baseOffset, | ||
| 1 | ||
| ) | ||
| TestUtils.resource(log.storeSnapshot(snapshotId1).get()) { snapshot => | ||
| snapshot.freeze() | ||
| } | ||
| // Generate second snapshots that includes the second segment by using the base offset of the third segment | ||
| val snapshotId2 = new OffsetAndEpoch( | ||
| log.log.logSegments.drop(2).head.baseOffset, | ||
| 1 | ||
| ) | ||
| TestUtils.resource(log.storeSnapshot(snapshotId2).get()) { snapshot => | ||
| snapshot.freeze() | ||
| } | ||
|
|
||
| // Sleep long enough to trigger a possible segment delete because of the default retention | ||
| val defaultLogRetentionMs = Defaults.RetentionMs * 2 | ||
| mockTime.sleep(defaultLogRetentionMs) | ||
|
|
||
| assertTrue(log.maybeClean()) | ||
| assertEquals(1, log.snapshotCount()) | ||
| assertTrue(log.startOffset > 0, s"${log.startOffset} must be greater than 0") | ||
| val latestSnapshotOffset = log.latestSnapshotId().get.offset | ||
| assertTrue( | ||
| latestSnapshotOffset >= log.startOffset, | ||
| s"latest snapshot offset ($latestSnapshotOffset) must be >= log start offset (${log.startOffset})" | ||
| ) | ||
|
Comment on lines
+916
to
+920
Member
Author
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. Without this change this check fails with: |
||
| } | ||
| } | ||
|
|
||
| object KafkaMetadataLogTest { | ||
|
|
||
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.
The long term solution is to also implement this feature documented in KIP-630: https://issues.apache.org/jira/browse/KAFKA-14241