-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-8237; Untangle TopicDeleteManager and add test cases #6588
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 all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
cd05e99
KAFKA-8237; Untangle TopicDeleteManager and add test cases
hachikuji f45aba5
Add additional deletion test case and address comments
hachikuji 4b85b48
Do not use option type in updateBrokerMetadata
hachikuji 64b3dc7
Declare type of `liveBrokerIdAndEpochs`
hachikuji 9e6496c
Address comments
hachikuji ee54087
Check topics undergoing deletion after receiving StopReplica response
hachikuji dd9c53b
Do not provide a response callback if partition is not being deleted
hachikuji 178eaaa
Only install stop replica callback when topic is being deleted
hachikuji e58ac48
Rename ElectionUtil to Election
hachikuji 6b68fbe
Only provide StopReplica callback if deletion has begun
hachikuji 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 |
|---|---|---|
|
|
@@ -24,85 +24,112 @@ import scala.collection.{Seq, Set, mutable} | |
|
|
||
| class ControllerContext { | ||
| val stats = new ControllerStats | ||
|
|
||
| var controllerChannelManager: ControllerChannelManager = null | ||
|
|
||
| var offlinePartitionCount = 0 | ||
| var shuttingDownBrokerIds: mutable.Set[Int] = mutable.Set.empty | ||
| private var liveBrokers: Set[Broker] = Set.empty | ||
| private var liveBrokerEpochs: Map[Int, Long] = Map.empty | ||
| var epoch: Int = KafkaController.InitialControllerEpoch | ||
| var epochZkVersion: Int = KafkaController.InitialControllerEpochZkVersion | ||
|
|
||
| var allTopics: Set[String] = Set.empty | ||
| private val partitionReplicaAssignmentUnderlying: mutable.Map[String, mutable.Map[Int, Seq[Int]]] = mutable.Map.empty | ||
| val partitionLeadershipInfo: mutable.Map[TopicPartition, LeaderIsrAndControllerEpoch] = mutable.Map.empty | ||
| val partitionsBeingReassigned: mutable.Map[TopicPartition, ReassignedPartitionsContext] = mutable.Map.empty | ||
| val partitionAssignments = mutable.Map.empty[String, mutable.Map[Int, Seq[Int]]] | ||
| val partitionLeadershipInfo = mutable.Map.empty[TopicPartition, LeaderIsrAndControllerEpoch] | ||
| val partitionsBeingReassigned = mutable.Map.empty[TopicPartition, ReassignedPartitionsContext] | ||
| val partitionStates = mutable.Map.empty[TopicPartition, PartitionState] | ||
| val replicaStates = mutable.Map.empty[PartitionAndReplica, ReplicaState] | ||
| val replicasOnOfflineDirs: mutable.Map[Int, Set[TopicPartition]] = mutable.Map.empty | ||
|
|
||
| private var liveBrokersUnderlying: Set[Broker] = Set.empty | ||
| private var liveBrokerIdAndEpochsUnderlying: Map[Int, Long] = Map.empty | ||
| val topicsToBeDeleted = mutable.Set.empty[String] | ||
|
|
||
| /** The following topicsWithDeletionStarted variable is used to properly update the offlinePartitionCount metric. | ||
| * When a topic is going through deletion, we don't want to keep track of its partition state | ||
| * changes in the offlinePartitionCount metric. This goal means if some partitions of a topic are already | ||
| * in OfflinePartition state when deletion starts, we need to change the corresponding partition | ||
| * states to NonExistentPartition first before starting the deletion. | ||
| * | ||
| * However we can NOT change partition states to NonExistentPartition at the time of enqueuing topics | ||
| * for deletion. The reason is that when a topic is enqueued for deletion, it may be ineligible for | ||
| * deletion due to ongoing partition reassignments. Hence there might be a delay between enqueuing | ||
| * a topic for deletion and the actual start of deletion. In this delayed interval, partitions may still | ||
| * transition to or out of the OfflinePartition state. | ||
| * | ||
| * Hence we decide to change partition states to NonExistentPartition only when the actual deletion have started. | ||
| * For topics whose deletion have actually started, we keep track of them in the following topicsWithDeletionStarted | ||
| * variable. And once a topic is in the topicsWithDeletionStarted set, we are sure there will no longer | ||
| * be partition reassignments to any of its partitions, and only then it's safe to move its partitions to | ||
| * NonExistentPartition state. Once a topic is in the topicsWithDeletionStarted set, we will stop monitoring | ||
| * its partition state changes in the offlinePartitionCount metric | ||
| */ | ||
| val topicsWithDeletionStarted = mutable.Set.empty[String] | ||
| val topicsIneligibleForDeletion = mutable.Set.empty[String] | ||
|
|
||
|
|
||
| def partitionReplicaAssignment(topicPartition: TopicPartition): Seq[Int] = { | ||
| partitionReplicaAssignmentUnderlying.getOrElse(topicPartition.topic, mutable.Map.empty) | ||
| partitionAssignments.getOrElse(topicPartition.topic, mutable.Map.empty) | ||
| .getOrElse(topicPartition.partition, Seq.empty) | ||
| } | ||
|
|
||
| private def clearTopicsState(): Unit = { | ||
| allTopics = Set.empty | ||
| partitionReplicaAssignmentUnderlying.clear() | ||
| partitionAssignments.clear() | ||
| partitionLeadershipInfo.clear() | ||
| partitionsBeingReassigned.clear() | ||
| replicasOnOfflineDirs.clear() | ||
| partitionStates.clear() | ||
| offlinePartitionCount = 0 | ||
| replicaStates.clear() | ||
| } | ||
|
|
||
| def updatePartitionReplicaAssignment(topicPartition: TopicPartition, newReplicas: Seq[Int]): Unit = { | ||
| partitionReplicaAssignmentUnderlying.getOrElseUpdate(topicPartition.topic, mutable.Map.empty) | ||
| partitionAssignments.getOrElseUpdate(topicPartition.topic, mutable.Map.empty) | ||
| .put(topicPartition.partition, newReplicas) | ||
| } | ||
|
|
||
| def partitionReplicaAssignmentForTopic(topic : String): Map[TopicPartition, Seq[Int]] = { | ||
| partitionReplicaAssignmentUnderlying.getOrElse(topic, Map.empty).map { | ||
| partitionAssignments.getOrElse(topic, Map.empty).map { | ||
| case (partition, replicas) => (new TopicPartition(topic, partition), replicas) | ||
| }.toMap | ||
| } | ||
|
|
||
| def allPartitions: Set[TopicPartition] = { | ||
| partitionReplicaAssignmentUnderlying.flatMap { | ||
| partitionAssignments.flatMap { | ||
| case (topic, topicReplicaAssignment) => topicReplicaAssignment.map { | ||
| case (partition, _) => new TopicPartition(topic, partition) | ||
| } | ||
| }.toSet | ||
| } | ||
|
|
||
| def setLiveBrokerAndEpochs(brokerAndEpochs: Map[Broker, Long]) { | ||
| liveBrokersUnderlying = brokerAndEpochs.keySet | ||
| liveBrokerIdAndEpochsUnderlying = | ||
| liveBrokers = brokerAndEpochs.keySet | ||
| liveBrokerEpochs = | ||
| brokerAndEpochs map { case (broker, brokerEpoch) => (broker.id, brokerEpoch)} | ||
| } | ||
|
|
||
| def addLiveBrokersAndEpochs(brokerAndEpochs: Map[Broker, Long]): Unit = { | ||
| liveBrokersUnderlying = liveBrokersUnderlying ++ brokerAndEpochs.keySet | ||
| liveBrokerIdAndEpochsUnderlying = liveBrokerIdAndEpochsUnderlying ++ | ||
| liveBrokers = liveBrokers ++ brokerAndEpochs.keySet | ||
| liveBrokerEpochs = liveBrokerEpochs ++ | ||
| (brokerAndEpochs map { case (broker, brokerEpoch) => (broker.id, brokerEpoch)}) | ||
| } | ||
|
|
||
| def removeLiveBrokersAndEpochs(brokerIds : Set[Int]): Unit = { | ||
| liveBrokersUnderlying = liveBrokersUnderlying.filter(broker => !brokerIds.contains(broker.id)) | ||
| liveBrokerIdAndEpochsUnderlying = liveBrokerIdAndEpochsUnderlying.filterKeys(id => !brokerIds.contains(id)) | ||
| def removeLiveBrokers(brokerIds: Set[Int]): Unit = { | ||
| liveBrokers = liveBrokers.filter(broker => !brokerIds.contains(broker.id)) | ||
| liveBrokerEpochs = liveBrokerEpochs.filterKeys(id => !brokerIds.contains(id)) | ||
| } | ||
|
|
||
| def updateBrokerMetadata(oldMetadata: Option[Broker], newMetadata: Option[Broker]): Unit = { | ||
| liveBrokersUnderlying = liveBrokersUnderlying -- oldMetadata ++ newMetadata | ||
| def updateBrokerMetadata(oldMetadata: Broker, newMetadata: Broker): Unit = { | ||
| liveBrokers -= oldMetadata | ||
| liveBrokers += newMetadata | ||
| } | ||
|
|
||
| // getter | ||
| def liveBrokers = liveBrokersUnderlying.filter(broker => !shuttingDownBrokerIds.contains(broker.id)) | ||
| def liveBrokerIds = liveBrokerIdAndEpochsUnderlying.keySet -- shuttingDownBrokerIds | ||
|
|
||
| def liveOrShuttingDownBrokerIds = liveBrokerIdAndEpochsUnderlying.keySet | ||
| def liveOrShuttingDownBrokers = liveBrokersUnderlying | ||
|
|
||
| def liveBrokerIdAndEpochs = liveBrokerIdAndEpochsUnderlying | ||
| def liveBrokerIds: Set[Int] = liveBrokerEpochs.keySet -- shuttingDownBrokerIds | ||
| def liveOrShuttingDownBrokerIds: Set[Int] = liveBrokerEpochs.keySet | ||
| def liveOrShuttingDownBrokers: Set[Broker] = liveBrokers | ||
| def liveBrokerIdAndEpochs: Map[Int, Long] = liveBrokerEpochs | ||
| def liveOrShuttingDownBroker(brokerId: Int): Option[Broker] = liveOrShuttingDownBrokers.find(_.id == brokerId) | ||
|
|
||
| def partitionsOnBroker(brokerId: Int): Set[TopicPartition] = { | ||
| partitionReplicaAssignmentUnderlying.flatMap { | ||
| partitionAssignments.flatMap { | ||
| case (topic, topicReplicaAssignment) => topicReplicaAssignment.filter { | ||
| case (_, replicas) => replicas.contains(brokerId) | ||
| }.map { | ||
|
|
@@ -121,7 +148,7 @@ class ControllerContext { | |
|
|
||
| def replicasOnBrokers(brokerIds: Set[Int]): Set[PartitionAndReplica] = { | ||
| brokerIds.flatMap { brokerId => | ||
| partitionReplicaAssignmentUnderlying.flatMap { | ||
| partitionAssignments.flatMap { | ||
| case (topic, topicReplicaAssignment) => topicReplicaAssignment.collect { | ||
| case (partition, replicas) if replicas.contains(brokerId) => | ||
| PartitionAndReplica(new TopicPartition(topic, partition), brokerId) | ||
|
|
@@ -131,13 +158,13 @@ class ControllerContext { | |
| } | ||
|
|
||
| def replicasForTopic(topic: String): Set[PartitionAndReplica] = { | ||
| partitionReplicaAssignmentUnderlying.getOrElse(topic, mutable.Map.empty).flatMap { | ||
| partitionAssignments.getOrElse(topic, mutable.Map.empty).flatMap { | ||
| case (partition, replicas) => replicas.map(r => PartitionAndReplica(new TopicPartition(topic, partition), r)) | ||
| }.toSet | ||
| } | ||
|
|
||
| def partitionsForTopic(topic: String): collection.Set[TopicPartition] = { | ||
| partitionReplicaAssignmentUnderlying.getOrElse(topic, mutable.Map.empty).map { | ||
| partitionAssignments.getOrElse(topic, mutable.Map.empty).map { | ||
| case (partition, _) => new TopicPartition(topic, partition) | ||
| }.toSet | ||
| } | ||
|
|
@@ -156,10 +183,9 @@ class ControllerContext { | |
| } | ||
|
|
||
| def resetContext(): Unit = { | ||
| if (controllerChannelManager != null) { | ||
| controllerChannelManager.shutdown() | ||
| controllerChannelManager = null | ||
| } | ||
| topicsToBeDeleted.clear() | ||
| topicsWithDeletionStarted.clear() | ||
| topicsIneligibleForDeletion.clear() | ||
| shuttingDownBrokerIds.clear() | ||
| epoch = 0 | ||
| epochZkVersion = 0 | ||
|
|
@@ -169,10 +195,115 @@ class ControllerContext { | |
|
|
||
| def removeTopic(topic: String): Unit = { | ||
| allTopics -= topic | ||
| partitionReplicaAssignmentUnderlying.remove(topic) | ||
| partitionAssignments.remove(topic) | ||
| partitionLeadershipInfo.foreach { | ||
| case (topicPartition, _) if topicPartition.topic == topic => partitionLeadershipInfo.remove(topicPartition) | ||
| case _ => | ||
| } | ||
| } | ||
|
|
||
| def beginTopicDeletion(topics: Set[String]): Unit = { | ||
| topicsWithDeletionStarted ++= topics | ||
| } | ||
|
|
||
| def isTopicDeletionInProgress(topic: String): Boolean = { | ||
| topicsWithDeletionStarted.contains(topic) | ||
| } | ||
|
|
||
| def isTopicQueuedUpForDeletion(topic: String): Boolean = { | ||
| topicsToBeDeleted.contains(topic) | ||
| } | ||
|
|
||
| def isTopicEligibleForDeletion(topic: String): Boolean = { | ||
| topicsToBeDeleted.contains(topic) && !topicsIneligibleForDeletion.contains(topic) | ||
| } | ||
|
|
||
| def topicsQueuedForDeletion: Set[String] = { | ||
| topicsToBeDeleted | ||
| } | ||
|
|
||
| def replicasInState(topic: String, state: ReplicaState): Set[PartitionAndReplica] = { | ||
| replicasForTopic(topic).filter(replica => replicaStates(replica) == state).toSet | ||
| } | ||
|
|
||
| def areAllReplicasInState(topic: String, state: ReplicaState): Boolean = { | ||
| replicasForTopic(topic).forall(replica => replicaStates(replica) == state) | ||
| } | ||
|
|
||
| def isAnyReplicaInState(topic: String, state: ReplicaState): Boolean = { | ||
| replicasForTopic(topic).exists(replica => replicaStates(replica) == state) | ||
| } | ||
|
|
||
| def checkValidReplicaStateChange(replicas: Seq[PartitionAndReplica], targetState: ReplicaState): (Seq[PartitionAndReplica], Seq[PartitionAndReplica]) = { | ||
| replicas.partition(replica => isValidReplicaStateTransition(replica, targetState)) | ||
| } | ||
|
|
||
| def checkValidPartitionStateChange(partitions: Seq[TopicPartition], targetState: PartitionState): (Seq[TopicPartition], Seq[TopicPartition]) = { | ||
| partitions.partition(p => isValidPartitionStateTransition(p, targetState)) | ||
| } | ||
|
|
||
| def putReplicaState(replica: PartitionAndReplica, state: ReplicaState): Unit = { | ||
| replicaStates.put(replica, state) | ||
| } | ||
|
hachikuji marked this conversation as resolved.
|
||
|
|
||
| def removeReplicaState(replica: PartitionAndReplica): Unit = { | ||
| replicaStates.remove(replica) | ||
| } | ||
|
|
||
| def putReplicaStateIfNotExists(replica: PartitionAndReplica, state: ReplicaState): Unit = { | ||
| replicaStates.getOrElseUpdate(replica, state) | ||
| } | ||
|
hachikuji marked this conversation as resolved.
|
||
|
|
||
| def putPartitionState(partition: TopicPartition, targetState: PartitionState): Unit = { | ||
| val currentState = partitionStates.put(partition, targetState).getOrElse(NonExistentPartition) | ||
| updatePartitionStateMetrics(partition, currentState, targetState) | ||
| } | ||
|
hachikuji marked this conversation as resolved.
|
||
|
|
||
| private def updatePartitionStateMetrics(partition: TopicPartition, | ||
| currentState: PartitionState, | ||
| targetState: PartitionState): Unit = { | ||
| if (!isTopicDeletionInProgress(partition.topic)) { | ||
| if (currentState != OfflinePartition && targetState == OfflinePartition) { | ||
| offlinePartitionCount = offlinePartitionCount + 1 | ||
| } else if (currentState == OfflinePartition && targetState != OfflinePartition) { | ||
| offlinePartitionCount = offlinePartitionCount - 1 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| def putPartitionStateIfNotExists(partition: TopicPartition, state: PartitionState): Unit = { | ||
| if (partitionStates.getOrElseUpdate(partition, state) == state) | ||
| updatePartitionStateMetrics(partition, NonExistentPartition, state) | ||
| } | ||
|
|
||
| def replicaState(replica: PartitionAndReplica): ReplicaState = { | ||
| replicaStates(replica) | ||
| } | ||
|
hachikuji marked this conversation as resolved.
|
||
|
|
||
| def partitionState(partition: TopicPartition): PartitionState = { | ||
| partitionStates(partition) | ||
| } | ||
|
hachikuji marked this conversation as resolved.
|
||
|
|
||
| def partitionsInState(state: PartitionState): Set[TopicPartition] = { | ||
| partitionStates.filter { case (_, s) => s == state }.keySet.toSet | ||
| } | ||
|
|
||
| def partitionsInStates(states: Set[PartitionState]): Set[TopicPartition] = { | ||
| partitionStates.filter { case (_, s) => states.contains(s) }.keySet.toSet | ||
| } | ||
|
|
||
| def partitionsInState(topic: String, state: PartitionState): Set[TopicPartition] = { | ||
| partitionsForTopic(topic).filter { partition => state == partitionState(partition) }.toSet | ||
| } | ||
|
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.
|
||
|
|
||
| def partitionsInStates(topic: String, states: Set[PartitionState]): Set[TopicPartition] = { | ||
| partitionsForTopic(topic).filter { partition => states.contains(partitionState(partition)) }.toSet | ||
| } | ||
|
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.
|
||
|
|
||
| private def isValidReplicaStateTransition(replica: PartitionAndReplica, targetState: ReplicaState): Boolean = | ||
| targetState.validPreviousStates.contains(replicaStates(replica)) | ||
|
|
||
| private def isValidPartitionStateTransition(partition: TopicPartition, targetState: PartitionState): Boolean = | ||
| targetState.validPreviousStates.contains(partitionStates(partition)) | ||
|
|
||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.