Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ControllerChannelManager(controllerContext: ControllerContext, config: Kaf
}
)

controllerContext.liveBrokers.foreach(addNewBroker)
controllerContext.liveOrShuttingDownBrokers.foreach(addNewBroker)

def startup() = {
brokerLock synchronized {
Expand Down Expand Up @@ -351,13 +351,23 @@ class ControllerBrokerRequestBatch(controller: KafkaController, stateChangeLogge
addUpdateMetadataRequestForBrokers(controllerContext.liveOrShuttingDownBrokerIds.toSeq, Set(topicPartition))
}

def addStopReplicaRequestForBrokers(brokerIds: Seq[Int], topicPartition: TopicPartition, deletePartition: Boolean,
callback: (AbstractResponse, Int) => Unit) {
def addStopReplicaRequestForBrokers(brokerIds: Seq[Int],
topicPartition: TopicPartition,
deletePartition: Boolean): Unit = {
brokerIds.filter(_ >= 0).foreach { brokerId =>
def topicDeletionCallback(stopReplicaResponse: AbstractResponse): Unit = {
controller.eventManager.put(controller.TopicDeletionStopReplicaResponseReceived(stopReplicaResponse, brokerId))
Comment thread
hachikuji marked this conversation as resolved.
}

val responseReceivedCallback = if (deletePartition && controllerContext.isTopicDeletionInProgress(topicPartition.topic))
topicDeletionCallback _
else
null

stopReplicaRequestMap.getOrElseUpdate(brokerId, Seq.empty[StopReplicaRequestInfo])
val v = stopReplicaRequestMap(brokerId)
stopReplicaRequestMap(brokerId) = v :+ StopReplicaRequestInfo(PartitionAndReplica(topicPartition, brokerId),
deletePartition, (r: AbstractResponse) => callback(r, brokerId))
deletePartition, responseReceivedCallback)
}
}

Expand Down Expand Up @@ -394,7 +404,7 @@ class ControllerBrokerRequestBatch(controller: KafkaController, stateChangeLogge

updateMetadataRequestBrokerSet ++= brokerIds.filter(_ >= 0)
partitions.foreach(partition => updateMetadataRequestPartitionInfo(partition,
beingDeleted = controller.topicDeletionManager.topicsToBeDeleted.contains(partition.topic)))
beingDeleted = controllerContext.topicsToBeDeleted.contains(partition.topic)))
}

def sendRequestsToBrokers(controllerEpoch: Int) {
Expand Down Expand Up @@ -525,4 +535,3 @@ case class ControllerBrokerStateInfo(networkClient: NetworkClient,

case class StopReplicaRequestInfo(replica: PartitionAndReplica, deletePartition: Boolean, callback: AbstractResponse => Unit)

class Callbacks(val stopReplicaResponseCallback: (AbstractResponse, Int) => Unit = (_, _ ) => ())
207 changes: 169 additions & 38 deletions core/src/main/scala/kafka/controller/ControllerContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -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)
}
Comment thread
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)
}
Comment thread
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)
}
Comment thread
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)
}
Comment thread
hachikuji marked this conversation as resolved.

def partitionState(partition: TopicPartition): PartitionState = {
partitionStates(partition)
}
Comment thread
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
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

partitionsInTopicAndState?


def partitionsInStates(topic: String, states: Set[PartitionState]): Set[TopicPartition] = {
partitionsForTopic(topic).filter { partition => states.contains(partitionState(partition)) }.toSet
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

partitionsInTopicAndStates?


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))

}
Loading