Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 40 additions & 30 deletions core/src/main/scala/kafka/log/LogCleaner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ class LogCleaner(initialConfig: CleanerConfig,
}
val deletable: Iterable[(TopicPartition, Log)] = cleanerManager.deletableLogs()
try {
deletable.foreach { case (topicPartition, log) =>
deletable.foreach { case (_, log) =>
currentLog = Some(log)
log.deleteOldSegments()
}
Expand Down Expand Up @@ -520,8 +520,12 @@ private[log] class Cleaner(val id: Int,

// group the segments and clean the groups
info("Cleaning log %s (cleaning prior to %s, discarding tombstones prior to %s)...".format(log.name, new Date(cleanableHorizonMs), new Date(deleteHorizonMs)))
for (group <- groupSegmentsBySize(log.logSegments(0, endOffset), log.config.segmentSize, log.config.maxIndexSize, cleanable.firstUncleanableOffset))
cleanSegments(log, group, offsetMap, deleteHorizonMs, stats)
val transactionMetadata = new CleanedTransactionMetadata

val groupedSegments = groupSegmentsBySize(log.logSegments(0, endOffset), log.config.segmentSize,
log.config.maxIndexSize, cleanable.firstUncleanableOffset)
for (group <- groupedSegments)
cleanSegments(log, group, offsetMap, deleteHorizonMs, stats, transactionMetadata)

// record buffer utilization
stats.bufferUtilization = offsetMap.utilization
Expand All @@ -539,14 +543,18 @@ private[log] class Cleaner(val id: Int,
* @param map The offset map to use for cleaning segments
* @param deleteHorizonMs The time to retain delete tombstones
* @param stats Collector for cleaning statistics
* @param transactionMetadata State of ongoing transactions which is carried between the cleaning
* of the grouped segments
*/
private[log] def cleanSegments(log: Log,
segments: Seq[LogSegment],
map: OffsetMap,
deleteHorizonMs: Long,
stats: CleanerStats) {
stats: CleanerStats,
transactionMetadata: CleanedTransactionMetadata): Unit = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add transactionMetadata to the javadoc?

// create a new segment with a suffix appended to the name of the log and indexes
val cleaned = LogCleaner.createNewCleanedSegment(log, segments.head.baseOffset)
transactionMetadata.cleanedIndex = Some(cleaned.txnIndex)

try {
// clean segments into the new destination segment
Expand All @@ -561,7 +569,7 @@ private[log] class Cleaner(val id: Int,
val startOffset = currentSegment.baseOffset
val upperBoundOffset = nextSegmentOpt.map(_.baseOffset).getOrElse(map.latestOffset + 1)
val abortedTransactions = log.collectAbortedTransactions(startOffset, upperBoundOffset)
val transactionMetadata = CleanedTransactionMetadata(abortedTransactions, Some(cleaned.txnIndex))
transactionMetadata.addAbortedTransactions(abortedTransactions)

val retainDeletes = currentSegment.lastModified > deleteHorizonMs
info(s"Cleaning segment $startOffset in log ${log.name} (largest timestamp ${new Date(currentSegment.largestTimestamp)}) " +
Expand Down Expand Up @@ -870,8 +878,9 @@ private[log] class Cleaner(val id: Int,
val dirty = log.logSegments(start, end).toBuffer
info("Building offset map for log %s for %d segments in offset range [%d, %d).".format(log.name, dirty.size, start, end))

val transactionMetadata = new CleanedTransactionMetadata
val abortedTransactions = log.collectAbortedTransactions(start, end)
val transactionMetadata = CleanedTransactionMetadata(abortedTransactions)
transactionMetadata.addAbortedTransactions(abortedTransactions)

// Add all the cleanable dirty segments. We must take at least map.slots * load_factor,
// but we may be able to fit more (if there is lots of duplication in the dirty section of the log)
Expand Down Expand Up @@ -1042,29 +1051,26 @@ private case class LogToClean(topicPartition: TopicPartition, log: Log, firstDir
override def compare(that: LogToClean): Int = math.signum(this.cleanableRatio - that.cleanableRatio).toInt
}

private[log] object CleanedTransactionMetadata {
def apply(abortedTransactions: List[AbortedTxn],
transactionIndex: Option[TransactionIndex] = None): CleanedTransactionMetadata = {
val queue = mutable.PriorityQueue.empty[AbortedTxn](new Ordering[AbortedTxn] {
override def compare(x: AbortedTxn, y: AbortedTxn): Int = x.firstOffset compare y.firstOffset
}.reverse)
queue ++= abortedTransactions
new CleanedTransactionMetadata(queue, transactionIndex)
}

val Empty = CleanedTransactionMetadata(List.empty[AbortedTxn])
}

/**
* This is a helper class to facilitate tracking transaction state while cleaning the log. It is initialized
* with the aborted transactions from the transaction index and its state is updated as the cleaner iterates through
* the log during a round of cleaning. This class is responsible for deciding when transaction markers can
* be removed and is therefore also responsible for updating the cleaned transaction index accordingly.
* This is a helper class to facilitate tracking transaction state while cleaning the log. It maintains a set
* of the ongoing aborted and committed transactions as the cleaner is working its way through the log. This
* class is responsible for deciding when transaction markers can be removed and is therefore also responsible
* for updating the cleaned transaction index accordingly.
*/
private[log] class CleanedTransactionMetadata(val abortedTransactions: mutable.PriorityQueue[AbortedTxn],
val transactionIndex: Option[TransactionIndex] = None) {
val ongoingCommittedTxns = mutable.Set.empty[Long]
val ongoingAbortedTxns = mutable.Map.empty[Long, AbortedTransactionMetadata]
private[log] class CleanedTransactionMetadata {
private val ongoingCommittedTxns = mutable.Set.empty[Long]
private val ongoingAbortedTxns = mutable.Map.empty[Long, AbortedTransactionMetadata]
// Minheap of aborted transactions sorted by the transaction first offset
private var abortedTransactions = mutable.PriorityQueue.empty[AbortedTxn](new Ordering[AbortedTxn] {
Comment thread
hachikuji marked this conversation as resolved.
Outdated
override def compare(x: AbortedTxn, y: AbortedTxn): Int = x.firstOffset compare y.firstOffset
}.reverse)

// Output cleaned index to write retained aborted transactions
var cleanedIndex: Option[TransactionIndex] = None

def addAbortedTransactions(abortedTransactions: List[AbortedTxn]): Unit = {
this.abortedTransactions ++= abortedTransactions
}

/**
* Update the cleaned transaction state with a control batch that has just been traversed by the cleaner.
Expand All @@ -1081,9 +1087,11 @@ private[log] class CleanedTransactionMetadata(val abortedTransactions: mutable.P
controlType match {
case ControlRecordType.ABORT =>
ongoingAbortedTxns.remove(producerId) match {
// Retain the marker until all batches from the transaction have been removed
// Retain the marker until all batches from the transaction have been removed.
// We may retain a record from an aborted transaction if it is the last entry
// written by a given producerId.
case Some(abortedTxnMetadata) if abortedTxnMetadata.lastObservedBatchOffset.isDefined =>
transactionIndex.foreach(_.append(abortedTxnMetadata.abortedTxn))
cleanedIndex.foreach(_.append(abortedTxnMetadata.abortedTxn))
false
case _ => true
}
Expand All @@ -1103,7 +1111,7 @@ private[log] class CleanedTransactionMetadata(val abortedTransactions: mutable.P
private def consumeAbortedTxnsUpTo(offset: Long): Unit = {
while (abortedTransactions.headOption.exists(_.firstOffset <= offset)) {
val abortedTxn = abortedTransactions.dequeue()
ongoingAbortedTxns += abortedTxn.producerId -> new AbortedTransactionMetadata(abortedTxn)
ongoingAbortedTxns.getOrElseUpdate(abortedTxn.producerId, new AbortedTransactionMetadata(abortedTxn))
}
}

Expand Down Expand Up @@ -1131,4 +1139,6 @@ private[log] class CleanedTransactionMetadata(val abortedTransactions: mutable.P

private class AbortedTransactionMetadata(val abortedTxn: AbortedTxn) {
var lastObservedBatchOffset: Option[Long] = None

override def toString: String = s"(txn: $abortedTxn, lastOffset: $lastObservedBatchOffset)"
}
Loading