mirror of https://github.com/apache/kafka.git
KAFKA-6710: Remove Thread.sleep from LogManager.deleteLogs (#4771)
`Thread.sleep` in `LogManager.deleteLogs` potentially blocks a scheduler thread for up to `log.segment.delete.delay.ms` with a default value of a minute. To avoid this, `deleteLogs` now deletes the logs for which `currentDefaultConfig.fileDeleteDelayMs` has elapsed after the delete was scheduled. Logs for which this interval has not yet elapsed are considered for deletion in the next iteration of `deleteLogs`, which is scheduled sooner if required. Reviewers: Jun Rao <junrao@gmail.com>, Dong Lin <lindong28@gmail.com>, Ted Yu <yuzhihong@gmail.com>
This commit is contained in:
parent
514936af6f
commit
f66aebff36
|
|
@ -79,13 +79,15 @@ class LogManager(logDirs: Seq[File],
|
||||||
private val logsToBeDeleted = new LinkedBlockingQueue[(Log, Long)]()
|
private val logsToBeDeleted = new LinkedBlockingQueue[(Log, Long)]()
|
||||||
|
|
||||||
private val _liveLogDirs: ConcurrentLinkedQueue[File] = createAndValidateLogDirs(logDirs, initialOfflineDirs)
|
private val _liveLogDirs: ConcurrentLinkedQueue[File] = createAndValidateLogDirs(logDirs, initialOfflineDirs)
|
||||||
@volatile var currentDefaultConfig = initialDefaultConfig
|
@volatile private var _currentDefaultConfig = initialDefaultConfig
|
||||||
@volatile private var numRecoveryThreadsPerDataDir = recoveryThreadsPerDataDir
|
@volatile private var numRecoveryThreadsPerDataDir = recoveryThreadsPerDataDir
|
||||||
|
|
||||||
def reconfigureDefaultLogConfig(logConfig: LogConfig): Unit = {
|
def reconfigureDefaultLogConfig(logConfig: LogConfig): Unit = {
|
||||||
this.currentDefaultConfig = logConfig
|
this._currentDefaultConfig = logConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def currentDefaultConfig: LogConfig = _currentDefaultConfig
|
||||||
|
|
||||||
def liveLogDirs: Seq[File] = {
|
def liveLogDirs: Seq[File] = {
|
||||||
if (_liveLogDirs.size == logDirs.size)
|
if (_liveLogDirs.size == logDirs.size)
|
||||||
logDirs
|
logDirs
|
||||||
|
|
@ -245,6 +247,9 @@ class LogManager(logDirs: Seq[File],
|
||||||
this.logsToBeDeleted.add((log, time.milliseconds()))
|
this.logsToBeDeleted.add((log, time.milliseconds()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only for testing
|
||||||
|
private[log] def hasLogsToBeDeleted: Boolean = !logsToBeDeleted.isEmpty
|
||||||
|
|
||||||
private def loadLog(logDir: File, recoveryPoints: Map[TopicPartition, Long], logStartOffsets: Map[TopicPartition, Long]): Unit = {
|
private def loadLog(logDir: File, recoveryPoints: Map[TopicPartition, Long], logStartOffsets: Map[TopicPartition, Long]): Unit = {
|
||||||
debug("Loading log '" + logDir.getName + "'")
|
debug("Loading log '" + logDir.getName + "'")
|
||||||
val topicPartition = Log.parseTopicPartitionName(logDir)
|
val topicPartition = Log.parseTopicPartitionName(logDir)
|
||||||
|
|
@ -704,17 +709,27 @@ class LogManager(logDirs: Seq[File],
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete logs marked for deletion.
|
* Delete logs marked for deletion. Delete all logs for which `currentDefaultConfig.fileDeleteDelayMs`
|
||||||
|
* has elapsed after the delete was scheduled. Logs for which this interval has not yet elapsed will be
|
||||||
|
* considered for deletion in the next iteration of `deleteLogs`. The next iteration will be executed
|
||||||
|
* after the remaining time for the first log that is not deleted. If there are no more `logsToBeDeleted`,
|
||||||
|
* `deleteLogs` will be executed after `currentDefaultConfig.fileDeleteDelayMs`.
|
||||||
*/
|
*/
|
||||||
private def deleteLogs(): Unit = {
|
private def deleteLogs(): Unit = {
|
||||||
|
var nextDelayMs = 0L
|
||||||
try {
|
try {
|
||||||
while (!logsToBeDeleted.isEmpty) {
|
def nextDeleteDelayMs: Long = {
|
||||||
val (removedLog, scheduleTimeMs) = logsToBeDeleted.take()
|
if (!logsToBeDeleted.isEmpty) {
|
||||||
|
val (_, scheduleTimeMs) = logsToBeDeleted.peek()
|
||||||
|
scheduleTimeMs + currentDefaultConfig.fileDeleteDelayMs - time.milliseconds()
|
||||||
|
} else
|
||||||
|
currentDefaultConfig.fileDeleteDelayMs
|
||||||
|
}
|
||||||
|
|
||||||
|
while ({nextDelayMs = nextDeleteDelayMs; nextDelayMs <= 0}) {
|
||||||
|
val (removedLog, _) = logsToBeDeleted.take()
|
||||||
if (removedLog != null) {
|
if (removedLog != null) {
|
||||||
try {
|
try {
|
||||||
val waitingTimeMs = scheduleTimeMs + currentDefaultConfig.fileDeleteDelayMs - time.milliseconds()
|
|
||||||
if (waitingTimeMs > 0)
|
|
||||||
Thread.sleep(waitingTimeMs)
|
|
||||||
removedLog.delete()
|
removedLog.delete()
|
||||||
info(s"Deleted log for partition ${removedLog.topicPartition} in ${removedLog.dir.getAbsolutePath}.")
|
info(s"Deleted log for partition ${removedLog.topicPartition} in ${removedLog.dir.getAbsolutePath}.")
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -730,7 +745,7 @@ class LogManager(logDirs: Seq[File],
|
||||||
try {
|
try {
|
||||||
scheduler.schedule("kafka-delete-logs",
|
scheduler.schedule("kafka-delete-logs",
|
||||||
deleteLogs _,
|
deleteLogs _,
|
||||||
delay = currentDefaultConfig.fileDeleteDelayMs,
|
delay = nextDelayMs,
|
||||||
unit = TimeUnit.MILLISECONDS)
|
unit = TimeUnit.MILLISECONDS)
|
||||||
} catch {
|
} catch {
|
||||||
case e: Throwable =>
|
case e: Throwable =>
|
||||||
|
|
|
||||||
|
|
@ -332,5 +332,10 @@ class LogManagerTest {
|
||||||
assertNotEquals("File reference was not updated in index", fileBeforeDelete.getAbsolutePath,
|
assertNotEquals("File reference was not updated in index", fileBeforeDelete.getAbsolutePath,
|
||||||
fileInIndex.get.getAbsolutePath)
|
fileInIndex.get.getAbsolutePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.sleep(logManager.InitialTaskDelayMs)
|
||||||
|
assertTrue("Logs deleted too early", logManager.hasLogsToBeDeleted)
|
||||||
|
time.sleep(logManager.currentDefaultConfig.fileDeleteDelayMs - logManager.InitialTaskDelayMs)
|
||||||
|
assertFalse("Logs not deleted", logManager.hasLogsToBeDeleted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue