Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
53 changes: 43 additions & 10 deletions core/src/main/scala/kafka/cluster/Partition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,20 @@ import org.apache.kafka.common.{IsolationLevel, TopicPartition}
import scala.collection.{Map, Seq}
import scala.jdk.CollectionConverters._

trait IsrChangeMetrics {
def markExpand(): Unit
def markShrink(): Unit
def markFailed(): Unit
}

trait PartitionStateStore {
def fetchTopicConfig(): Properties
def shrinkIsr(controllerEpoch: Int, leaderAndIsr: LeaderAndIsr): Option[Int]
def expandIsr(controllerEpoch: Int, leaderAndIsr: LeaderAndIsr): Option[Int]
}

class ZkPartitionStateStore(topicPartition: TopicPartition,
zkClient: KafkaZkClient,
replicaManager: ReplicaManager) extends PartitionStateStore {
zkClient: KafkaZkClient) extends PartitionStateStore {

override def fetchTopicConfig(): Properties = {
val adminZkClient = new AdminZkClient(zkClient)
Expand All @@ -62,15 +67,11 @@ class ZkPartitionStateStore(topicPartition: TopicPartition,

override def shrinkIsr(controllerEpoch: Int, leaderAndIsr: LeaderAndIsr): Option[Int] = {
val newVersionOpt = updateIsr(controllerEpoch, leaderAndIsr)
if (newVersionOpt.isDefined)
replicaManager.isrShrinkRate.mark()
newVersionOpt
}

override def expandIsr(controllerEpoch: Int, leaderAndIsr: LeaderAndIsr): Option[Int] = {
val newVersionOpt = updateIsr(controllerEpoch, leaderAndIsr)
if (newVersionOpt.isDefined)
replicaManager.isrExpandRate.mark()
newVersionOpt
}

Expand All @@ -79,10 +80,8 @@ class ZkPartitionStateStore(topicPartition: TopicPartition,
leaderAndIsr, controllerEpoch)

if (updateSucceeded) {
replicaManager.recordIsrChange(topicPartition)
Some(newVersion)
} else {
replicaManager.failedIsrUpdatesRate.mark()
None
}
}
Expand All @@ -107,10 +106,24 @@ object Partition extends KafkaMetricsGroup {
def apply(topicPartition: TopicPartition,
time: Time,
replicaManager: ReplicaManager): Partition = {

val isrChangeMetrics = new IsrChangeMetrics {
override def markExpand(): Unit = {
replicaManager.recordIsrChange(topicPartition)
Comment thread
hachikuji marked this conversation as resolved.
replicaManager.isrExpandRate.mark()
}

override def markShrink(): Unit = {
replicaManager.recordIsrChange(topicPartition)
replicaManager.isrShrinkRate.mark()
}

override def markFailed(): Unit = replicaManager.failedIsrUpdatesRate.mark()
}

val zkIsrBackingStore = new ZkPartitionStateStore(
topicPartition,
replicaManager.zkClient,
replicaManager)
replicaManager.zkClient)

val delayedOperations = new DelayedOperations(
topicPartition,
Expand All @@ -124,6 +137,7 @@ object Partition extends KafkaMetricsGroup {
localBrokerId = replicaManager.config.brokerId,
time = time,
stateStore = zkIsrBackingStore,
isrChangeMetrics = isrChangeMetrics,
delayedOperations = delayedOperations,
metadataCache = replicaManager.metadataCache,
logManager = replicaManager.logManager,
Expand Down Expand Up @@ -246,6 +260,7 @@ class Partition(val topicPartition: TopicPartition,
localBrokerId: Int,
time: Time,
stateStore: PartitionStateStore,
isrChangeMetrics: IsrChangeMetrics,
delayedOperations: DelayedOperations,
metadataCache: MetadataCache,
logManager: LogManager,
Expand Down Expand Up @@ -1325,6 +1340,9 @@ class Partition(val topicPartition: TopicPartition,
info(s"Expanding ISR from ${isrState.isr.mkString(",")} to ${newInSyncReplicaIds.mkString(",")}")
val newLeaderAndIsr = new LeaderAndIsr(localBrokerId, leaderEpoch, newInSyncReplicaIds.toList, zkVersion)
val zkVersionOpt = stateStore.expandIsr(controllerEpoch, newLeaderAndIsr)
if (zkVersionOpt.isDefined) {
isrChangeMetrics.markExpand()
}
maybeUpdateIsrAndVersionWithZk(newInSyncReplicaIds, zkVersionOpt)
}

Expand All @@ -1351,6 +1369,9 @@ class Partition(val topicPartition: TopicPartition,
private def shrinkIsrWithZk(newIsr: Set[Int]): Unit = {
val newLeaderAndIsr = new LeaderAndIsr(localBrokerId, leaderEpoch, newIsr.toList, zkVersion)
val zkVersionOpt = stateStore.shrinkIsr(controllerEpoch, newLeaderAndIsr)
if (zkVersionOpt.isDefined) {
isrChangeMetrics.markShrink()
}
maybeUpdateIsrAndVersionWithZk(newIsr, zkVersionOpt)
}

Expand All @@ -1363,6 +1384,7 @@ class Partition(val topicPartition: TopicPartition,

case None =>
info(s"Cached zkVersion $zkVersion not equal to that in zookeeper, skip updating ISR")
isrChangeMetrics.markFailed()
}
}

Expand All @@ -1378,6 +1400,7 @@ class Partition(val topicPartition: TopicPartition,
val alterIsrItem = AlterIsrItem(topicPartition, newLeaderAndIsr, handleAlterIsrResponse(proposedIsrState))

if (!alterIsrManager.enqueue(alterIsrItem)) {
isrChangeMetrics.markFailed()
throw new IllegalStateException(s"Failed to enqueue `AlterIsr` request with state " +
s"$newLeaderAndIsr for partition $topicPartition")
}
Expand Down Expand Up @@ -1406,10 +1429,13 @@ class Partition(val topicPartition: TopicPartition,
case Left(error: Errors) => error match {
case Errors.UNKNOWN_TOPIC_OR_PARTITION =>
debug(s"Controller failed to update ISR to $proposedIsrState since it doesn't know about this topic or partition. Giving up.")
isrChangeMetrics.markFailed()
case Errors.FENCED_LEADER_EPOCH =>
debug(s"Controller failed to update ISR to $proposedIsrState since we sent an old leader epoch. Giving up.")
isrChangeMetrics.markFailed()
case Errors.INVALID_UPDATE_VERSION =>
debug(s"Controller failed to update ISR to $proposedIsrState due to invalid zk version. Giving up.")
isrChangeMetrics.markFailed()
case _ =>
warn(s"Controller failed to update ISR to $proposedIsrState due to unexpected $error. Retrying.")
sendAlterIsrRequest(proposedIsrState)
Comment thread
mumrah marked this conversation as resolved.
Outdated
Expand All @@ -1418,12 +1444,19 @@ class Partition(val topicPartition: TopicPartition,
// Success from controller, still need to check a few things
if (leaderAndIsr.leaderEpoch != leaderEpoch) {
debug(s"Ignoring ISR from AlterIsr with ${leaderAndIsr} since we have a stale leader epoch $leaderEpoch.")
isrChangeMetrics.markFailed()
} else if (leaderAndIsr.zkVersion <= zkVersion) {
debug(s"Ignoring ISR from AlterIsr with ${leaderAndIsr} since we have a newer version $zkVersion.")
isrChangeMetrics.markFailed()
} else {
isrState = CommittedIsr(leaderAndIsr.isr.toSet)
zkVersion = leaderAndIsr.zkVersion
info(s"ISR updated from AlterIsr to ${isrState.isr.mkString(",")} and version updated to [$zkVersion]")
proposedIsrState match {
case PendingExpandIsr(_, _) => isrChangeMetrics.markExpand()
case PendingShrinkIsr(_, _) => isrChangeMetrics.markShrink()
case _ => // nothing to do, shouldn't get here
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import kafka.api.ApiVersion
import kafka.log.{CleanerConfig, LogConfig, LogManager}
import kafka.server.{Defaults, MetadataCache}
import kafka.server.checkpoints.OffsetCheckpoints
import kafka.utils.TestUtils.MockAlterIsrManager
import kafka.utils.TestUtils.{MockAlterIsrManager, MockIsrChangeMetrics}
import kafka.utils.{MockTime, TestUtils}
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.utils.Utils
Expand All @@ -41,6 +41,7 @@ class AbstractPartitionTest {
var logDir2: File = _
var logManager: LogManager = _
var alterIsrManager: MockAlterIsrManager = _
var isrChangeMetrics: MockIsrChangeMetrics = _
var logConfig: LogConfig = _
val stateStore: PartitionStateStore = mock(classOf[PartitionStateStore])
val delayedOperations: DelayedOperations = mock(classOf[DelayedOperations])
Expand All @@ -63,12 +64,14 @@ class AbstractPartitionTest {
logManager.startup()

alterIsrManager = TestUtils.createAlterIsrManager()
isrChangeMetrics = TestUtils.createIsrChangeListener()
partition = new Partition(topicPartition,
replicaLagTimeMaxMs = Defaults.ReplicaLagTimeMaxMs,
interBrokerProtocolVersion = ApiVersion.latestVersion,
localBrokerId = brokerId,
time,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
logManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ class PartitionLockTest extends Logging {
val brokerId = 0
val topicPartition = new TopicPartition("test-topic", 0)
val stateStore: PartitionStateStore = mock(classOf[PartitionStateStore])
val isrChangeMetrics: IsrChangeMetrics = mock(classOf[IsrChangeMetrics])
val delayedOperations: DelayedOperations = mock(classOf[DelayedOperations])
val metadataCache: MetadataCache = mock(classOf[MetadataCache])
val offsetCheckpoints: OffsetCheckpoints = mock(classOf[OffsetCheckpoints])
Expand All @@ -261,6 +262,7 @@ class PartitionLockTest extends Logging {
localBrokerId = brokerId,
mockTime,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
logManager,
Expand Down
21 changes: 19 additions & 2 deletions core/src/test/scala/unit/kafka/cluster/PartitionTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ class PartitionTest extends AbstractPartitionTest {
localBrokerId = brokerId,
time,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
logManager,
Expand Down Expand Up @@ -1164,11 +1165,20 @@ class PartitionTest extends AbstractPartitionTest {
leaderEndOffset = 6L)

assertEquals(alterIsrManager.isrUpdates.size, 1)
assertEquals(alterIsrManager.isrUpdates.dequeue().leaderAndIsr.isr, List(brokerId, remoteBrokerId))
val isrItem = alterIsrManager.isrUpdates.dequeue()
assertEquals(isrItem.leaderAndIsr.isr, List(brokerId, remoteBrokerId))
assertEquals(Set(brokerId), partition.isrState.isr)
assertEquals(Set(brokerId, remoteBrokerId), partition.isrState.maximalIsr)
assertEquals(10L, remoteReplica.logEndOffset)
assertEquals(0L, remoteReplica.logStartOffset)

// Complete the ISR expansion
isrItem.callback.apply(Right(new LeaderAndIsr(brokerId, leaderEpoch, List(brokerId, remoteBrokerId), 2)))
assertEquals(Set(brokerId, remoteBrokerId), partition.isrState.isr)

assertEquals(isrChangeMetrics.expands.get, 1)
assertEquals(isrChangeMetrics.shrinks.get, 0)
assertEquals(isrChangeMetrics.failures.get, 0)
}

@Test
Expand Down Expand Up @@ -1221,6 +1231,10 @@ class PartitionTest extends AbstractPartitionTest {
assertEquals(Set(brokerId), partition.inSyncReplicaIds)
assertEquals(Set(brokerId, remoteBrokerId), partition.isrState.maximalIsr)
assertEquals(alterIsrManager.isrUpdates.size, 0)

assertEquals(isrChangeMetrics.expands.get, 0)
assertEquals(isrChangeMetrics.shrinks.get, 0)
assertEquals(isrChangeMetrics.failures.get, 1)
}

@Test
Expand Down Expand Up @@ -1639,7 +1653,7 @@ class PartitionTest extends AbstractPartitionTest {
val topicPartition = new TopicPartition("test", 1)
val partition = new Partition(
topicPartition, 1000, ApiVersion.latestVersion, 0,
new SystemTime(), mock(classOf[PartitionStateStore]), mock(classOf[DelayedOperations]),
new SystemTime(), mock(classOf[PartitionStateStore]), mock(classOf[IsrChangeMetrics]), mock(classOf[DelayedOperations]),
mock(classOf[MetadataCache]), mock(classOf[LogManager]), mock(classOf[AlterIsrManager]))

val replicas = Seq(0, 1, 2, 3)
Expand Down Expand Up @@ -1682,6 +1696,7 @@ class PartitionTest extends AbstractPartitionTest {
localBrokerId = brokerId,
time,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
spyLogManager,
Expand Down Expand Up @@ -1717,6 +1732,7 @@ class PartitionTest extends AbstractPartitionTest {
localBrokerId = brokerId,
time,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
spyLogManager,
Expand Down Expand Up @@ -1753,6 +1769,7 @@ class PartitionTest extends AbstractPartitionTest {
localBrokerId = brokerId,
time,
stateStore,
isrChangeMetrics,
delayedOperations,
metadataCache,
spyLogManager,
Expand Down
25 changes: 24 additions & 1 deletion core/src/test/scala/unit/kafka/utils/TestUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ import java.nio.charset.{Charset, StandardCharsets}
import java.nio.file.{Files, StandardOpenOption}
import java.security.cert.X509Certificate
import java.time.Duration
import java.util.concurrent.atomic.AtomicInteger
import java.util.{Arrays, Collections, Properties}
import java.util.concurrent.{Callable, ExecutionException, Executors, TimeUnit}

import javax.net.ssl.X509TrustManager
import kafka.api._
import kafka.cluster.{Broker, EndPoint}
import kafka.cluster.{Broker, EndPoint, IsrChangeMetrics}
import kafka.log._
import kafka.security.auth.{Acl, Resource, Authorizer => LegacyAuthorizer}
import kafka.server._
Expand Down Expand Up @@ -1082,6 +1083,28 @@ object TestUtils extends Logging {
new MockAlterIsrManager()
}

class MockIsrChangeMetrics extends IsrChangeMetrics {
val expands: AtomicInteger = new AtomicInteger(0)
val shrinks: AtomicInteger = new AtomicInteger(0)
val failures: AtomicInteger = new AtomicInteger(0)

override def markExpand(): Unit = expands.incrementAndGet()

override def markShrink(): Unit = shrinks.incrementAndGet()

override def markFailed(): Unit = failures.incrementAndGet()

def reset(): Unit = {
expands.set(0)
shrinks.set(0)
failures.set(0)
}
}

def createIsrChangeListener(): MockIsrChangeMetrics = {
new MockIsrChangeMetrics()
}

def produceMessages(servers: Seq[KafkaServer],
records: Seq[ProducerRecord[Array[Byte], Array[Byte]]],
acks: Int = -1): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import kafka.api.ApiVersion$;
import kafka.cluster.BrokerEndPoint;
import kafka.cluster.DelayedOperations;
import kafka.cluster.IsrChangeMetrics;
import kafka.cluster.Partition;
import kafka.cluster.PartitionStateStore;
import kafka.log.CleanerConfig;
Expand Down Expand Up @@ -153,11 +154,12 @@ public void setup() throws IOException {

PartitionStateStore partitionStateStore = Mockito.mock(PartitionStateStore.class);
Mockito.when(partitionStateStore.fetchTopicConfig()).thenReturn(new Properties());
IsrChangeMetrics isrChangeMetrics = Mockito.mock(IsrChangeMetrics.class);
OffsetCheckpoints offsetCheckpoints = Mockito.mock(OffsetCheckpoints.class);
Mockito.when(offsetCheckpoints.fetch(logDir.getAbsolutePath(), tp)).thenReturn(Option.apply(0L));
AlterIsrManager isrChannelManager = Mockito.mock(AlterIsrManager.class);
Partition partition = new Partition(tp, 100, ApiVersion$.MODULE$.latestVersion(),
0, Time.SYSTEM, partitionStateStore, new DelayedOperationsMock(tp),
0, Time.SYSTEM, partitionStateStore, isrChangeMetrics, new DelayedOperationsMock(tp),
Mockito.mock(MetadataCache.class), logManager, isrChannelManager);

partition.makeFollower(partitionState, offsetCheckpoints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import kafka.api.ApiVersion$;
import kafka.cluster.DelayedOperations;
import kafka.cluster.IsrChangeMetrics;
import kafka.cluster.Partition;
import kafka.cluster.PartitionStateStore;
import kafka.log.CleanerConfig;
Expand Down Expand Up @@ -118,11 +119,11 @@ public void setup() throws IOException {
PartitionStateStore partitionStateStore = Mockito.mock(PartitionStateStore.class);
Mockito.when(partitionStateStore.fetchTopicConfig()).thenReturn(new Properties());
Mockito.when(offsetCheckpoints.fetch(logDir.getAbsolutePath(), tp)).thenReturn(Option.apply(0L));

IsrChangeMetrics isrChangeMetrics = Mockito.mock(IsrChangeMetrics.class);
AlterIsrManager alterIsrManager = Mockito.mock(AlterIsrManager.class);
partition = new Partition(tp, 100,
ApiVersion$.MODULE$.latestVersion(), 0, Time.SYSTEM,
partitionStateStore, delayedOperations,
partitionStateStore, isrChangeMetrics, delayedOperations,
Mockito.mock(MetadataCache.class), logManager, alterIsrManager);
partition.createLogIfNotExists(true, false, offsetCheckpoints);
executorService.submit((Runnable) () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import kafka.api.ApiVersion$;
import kafka.cluster.DelayedOperations;
import kafka.cluster.IsrChangeMetrics;
import kafka.cluster.Partition;
import kafka.cluster.PartitionStateStore;
import kafka.log.CleanerConfig;
Expand Down Expand Up @@ -116,11 +117,11 @@ public void setUp() {
.setIsNew(true);
PartitionStateStore partitionStateStore = Mockito.mock(PartitionStateStore.class);
Mockito.when(partitionStateStore.fetchTopicConfig()).thenReturn(new Properties());

IsrChangeMetrics isrChangeMetrics = Mockito.mock(IsrChangeMetrics.class);
AlterIsrManager alterIsrManager = Mockito.mock(AlterIsrManager.class);
partition = new Partition(topicPartition, 100,
ApiVersion$.MODULE$.latestVersion(), 0, Time.SYSTEM,
partitionStateStore, delayedOperations,
partitionStateStore, isrChangeMetrics, delayedOperations,
Mockito.mock(MetadataCache.class), logManager, alterIsrManager);
partition.makeLeader(partitionState, offsetCheckpoints);
}
Expand Down