Skip to content
16 changes: 10 additions & 6 deletions core/src/main/scala/kafka/coordinator/group/GroupCoordinator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import kafka.zk.KafkaZkClient
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.message.JoinGroupResponseData.JoinGroupResponseMember
import org.apache.kafka.common.metrics.Metrics
import org.apache.kafka.common.message.LeaveGroupRequestData.MemberIdentity
import org.apache.kafka.common.protocol.{ApiKeys, Errors}
import org.apache.kafka.common.record.RecordBatch.{NO_PRODUCER_EPOCH, NO_PRODUCER_ID}
Expand Down Expand Up @@ -55,7 +56,8 @@ class GroupCoordinator(val brokerId: Int,
val groupManager: GroupMetadataManager,
val heartbeatPurgatory: DelayedOperationPurgatory[DelayedHeartbeat],
val joinPurgatory: DelayedOperationPurgatory[DelayedJoin],
time: Time) extends Logging {
time: Time,
metrics: Metrics) extends Logging {
import GroupCoordinator._

type JoinCallback = JoinGroupResult => Unit
Expand Down Expand Up @@ -1084,10 +1086,11 @@ object GroupCoordinator {
def apply(config: KafkaConfig,
zkClient: KafkaZkClient,
replicaManager: ReplicaManager,
time: Time): GroupCoordinator = {
time: Time,
metrics: Metrics): GroupCoordinator = {
val heartbeatPurgatory = DelayedOperationPurgatory[DelayedHeartbeat]("Heartbeat", config.brokerId)
val joinPurgatory = DelayedOperationPurgatory[DelayedJoin]("Rebalance", config.brokerId)
apply(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, time)
apply(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, time, metrics)
}

private[group] def offsetConfig(config: KafkaConfig) = OffsetConfig(
Expand All @@ -1108,16 +1111,17 @@ object GroupCoordinator {
replicaManager: ReplicaManager,
heartbeatPurgatory: DelayedOperationPurgatory[DelayedHeartbeat],
joinPurgatory: DelayedOperationPurgatory[DelayedJoin],
time: Time): GroupCoordinator = {
time: Time,
metrics: Metrics): GroupCoordinator = {
val offsetConfig = this.offsetConfig(config)
val groupConfig = GroupConfig(groupMinSessionTimeoutMs = config.groupMinSessionTimeoutMs,
groupMaxSessionTimeoutMs = config.groupMaxSessionTimeoutMs,
groupMaxSize = config.groupMaxSize,
groupInitialRebalanceDelayMs = config.groupInitialRebalanceDelay)

val groupMetadataManager = new GroupMetadataManager(config.brokerId, config.interBrokerProtocolVersion,
offsetConfig, replicaManager, zkClient, time)
new GroupCoordinator(config.brokerId, groupConfig, offsetConfig, groupMetadataManager, heartbeatPurgatory, joinPurgatory, time)
offsetConfig, replicaManager, zkClient, time, metrics)
new GroupCoordinator(config.brokerId, groupConfig, offsetConfig, groupMetadataManager, heartbeatPurgatory, joinPurgatory, time, metrics)
}

def joinError(memberId: String, error: Errors): JoinGroupResult = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import kafka.zk.KafkaZkClient
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.{KafkaException, TopicPartition}
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.metrics.stats.{Avg, Max}
import org.apache.kafka.common.metrics.{MetricConfig, Metrics}
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.protocol.types.Type._
import org.apache.kafka.common.protocol.types._
Expand All @@ -53,7 +55,8 @@ class GroupMetadataManager(brokerId: Int,
config: OffsetConfig,
replicaManager: ReplicaManager,
zkClient: KafkaZkClient,
time: Time) extends Logging with KafkaMetricsGroup {
time: Time,
metrics: Metrics) extends Logging with KafkaMetricsGroup {

private val compressionType: CompressionType = CompressionType.forId(config.offsetsTopicCompressionCodec.codec)

Expand Down Expand Up @@ -82,6 +85,15 @@ class GroupMetadataManager(brokerId: Int,
* We use this structure to quickly find the groups which need to be updated by the commit/abort marker. */
private val openGroupsForProducer = mutable.HashMap[Long, mutable.Set[String]]()

/* setup metrics*/
private val metricConfig: MetricConfig = new MetricConfig().samples(1)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
val partitionLoadSensor = metrics.sensor("GroupLoadTime", metricConfig)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated

private val partitionMaxMetricName = metrics.metricName("group-load-time-max", "group-metadata-manager-metrics")
partitionLoadSensor.add(partitionMaxMetricName, new Max())
private val partitionAvgMetricName = metrics.metricName("group-load-time-avg", "group-metadata-manager-metrics")
partitionLoadSensor.add(partitionAvgMetricName, new Avg())

this.logIdent = s"[GroupMetadataManager brokerId=$brokerId] "

private def recreateGauge[T](name: String, gauge: Gauge[T]): Gauge[T] = {
Expand Down Expand Up @@ -498,7 +510,9 @@ class GroupMetadataManager(brokerId: Int,
try {
val startMs = time.milliseconds()
doLoadGroupsAndOffsets(topicPartition, onGroupLoaded)
info(s"Finished loading offsets and group metadata from $topicPartition in ${time.milliseconds() - startMs} milliseconds.")
val endMs = time.milliseconds()
partitionLoadSensor.record(endMs - startMs, endMs, false)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
info(s"Finished loading offsets and group metadata from $topicPartition in ${endMs - startMs} milliseconds.")
} catch {
case t: Throwable => error(s"Error loading offsets from $topicPartition", t)
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object TransactionCoordinator {
// we do not need to turn on reaper thread since no tasks will be expired and there are no completed tasks to be purged
val txnMarkerPurgatory = DelayedOperationPurgatory[DelayedTxnMarker]("txn-marker-purgatory", config.brokerId,
reaperEnabled = false, timerEnabled = false)
val txnStateManager = new TransactionStateManager(config.brokerId, zkClient, scheduler, replicaManager, txnConfig, time)
val txnStateManager = new TransactionStateManager(config.brokerId, zkClient, scheduler, replicaManager, txnConfig, time, metrics)

val logContext = new LogContext(s"[TransactionCoordinator id=${config.brokerId}] ")
val txnMarkerChannelManager = TransactionMarkerChannelManager(config, metrics, metadataCache, txnStateManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import kafka.utils.{Logging, Pool, Scheduler}
import kafka.zk.KafkaZkClient
import org.apache.kafka.common.{KafkaException, TopicPartition}
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.metrics.stats.{Avg, Max}
import org.apache.kafka.common.metrics.{MetricConfig, Metrics}
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.record.{FileRecords, MemoryRecords, SimpleRecord}
import org.apache.kafka.common.requests.ProduceResponse.PartitionResponse
Expand Down Expand Up @@ -71,7 +73,8 @@ class TransactionStateManager(brokerId: Int,
scheduler: Scheduler,
replicaManager: ReplicaManager,
config: TransactionConfig,
time: Time) extends Logging {
time: Time,
metrics: Metrics) extends Logging {

this.logIdent = "[Transaction State Manager " + brokerId + "]: "

Expand All @@ -95,6 +98,15 @@ class TransactionStateManager(brokerId: Int,
/** number of partitions for the transaction log topic */
private val transactionTopicPartitionCount = getTransactionTopicPartitionCount

/** setup metrics*/
private val metricConfig: MetricConfig = new MetricConfig().samples(1)
private val partitionLoadSensor = metrics.sensor("TransactionLoadTime", metricConfig)

private val partitionMaxMetricName = metrics.metricName("transaction-load-time-max", "transaction-state-manager-metrics")
partitionLoadSensor.add(partitionMaxMetricName, new Max())
private val partitionAvgMetricName = metrics.metricName("transaction-load-time-avg", "transaction-state-manager-metrics")
partitionLoadSensor.add(partitionAvgMetricName, new Avg())

// visible for testing only
private[transaction] def addLoadingPartition(partitionId: Int, coordinatorEpoch: Int): Unit = {
val partitionAndLeaderEpoch = TransactionPartitionAndLeaderEpoch(partitionId, coordinatorEpoch)
Expand Down Expand Up @@ -339,8 +351,9 @@ class TransactionStateManager(brokerId: Int,
currOffset = batch.nextOffset
}
}

info(s"Finished loading ${loadedTransactions.size} transaction metadata from $topicPartition in ${time.milliseconds() - startMs} milliseconds")
val endMs = time.milliseconds()
partitionLoadSensor.record(endMs - startMs, endMs, false)
info(s"Finished loading ${loadedTransactions.size} transaction metadata from $topicPartition in ${endMs - startMs} milliseconds")
}
} catch {
case t: Throwable => error(s"Error loading transactions from transaction log $topicPartition", t)
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/kafka/server/KafkaServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class KafkaServer(val config: KafkaConfig, time: Time = Time.SYSTEM, threadNameP

/* start group coordinator */
// Hardcode Time.SYSTEM for now as some Streams tests fail otherwise, it would be good to fix the underlying issue
groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, Time.SYSTEM)
groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, Time.SYSTEM, metrics)
groupCoordinator.startup()

/* start transaction coordinator, with a separate background thread scheduler for transaction expiration and log loading */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kafka.coordinator.group.GroupCoordinatorConcurrencyTest._
import kafka.server.{DelayedOperationPurgatory, KafkaConfig}
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.metrics.Metrics
import org.apache.kafka.common.message.LeaveGroupRequestData.MemberIdentity
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.requests.JoinGroupRequest
Expand Down Expand Up @@ -84,7 +85,7 @@ class GroupCoordinatorConcurrencyTest extends AbstractCoordinatorConcurrencyTest
val heartbeatPurgatory = new DelayedOperationPurgatory[DelayedHeartbeat]("Heartbeat", timer, config.brokerId, reaperEnabled = false)
val joinPurgatory = new DelayedOperationPurgatory[DelayedJoin]("Rebalance", timer, config.brokerId, reaperEnabled = false)

groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, timer.time)
groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, timer.time, new Metrics())
groupCoordinator.startup(false)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import java.util.concurrent.locks.ReentrantLock
import kafka.cluster.Partition
import kafka.zk.KafkaZkClient
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.metrics.Metrics
import org.apache.kafka.common.message.LeaveGroupRequestData.MemberIdentity
import org.junit.Assert._
import org.junit.{After, Assert, Before, Test}
Expand Down Expand Up @@ -111,7 +112,7 @@ class GroupCoordinatorTest {
val heartbeatPurgatory = new DelayedOperationPurgatory[DelayedHeartbeat]("Heartbeat", timer, config.brokerId, reaperEnabled = false)
val joinPurgatory = new DelayedOperationPurgatory[DelayedJoin]("Rebalance", timer, config.brokerId, reaperEnabled = false)

groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, timer.time)
groupCoordinator = GroupCoordinator(config, zkClient, replicaManager, heartbeatPurgatory, joinPurgatory, timer.time, new Metrics())
groupCoordinator.startup(enableMetadataExpiration = false)

// add the partition into the owned partition list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package kafka.coordinator.group

import com.yammer.metrics.Metrics
import com.yammer.metrics.core.Gauge
import java.lang.management.ManagementFactory
import java.nio.ByteBuffer
import java.util.Collections
import java.util.Optional
import java.util.concurrent.locks.ReentrantLock
import javax.management.ObjectName
import kafka.api._
import kafka.cluster.Partition
import kafka.common.OffsetAndMetadata
Expand All @@ -35,6 +37,7 @@ import org.apache.kafka.clients.consumer.ConsumerPartitionAssignor.Subscription
import org.apache.kafka.clients.consumer.internals.ConsumerProtocol
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.internals.Topic
import org.apache.kafka.common.metrics.{JmxReporter, Metrics => kMetrics}
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.record._
import org.apache.kafka.common.requests.OffsetFetchResponse
Expand All @@ -55,6 +58,7 @@ class GroupMetadataManagerTest {
var zkClient: KafkaZkClient = null
var partition: Partition = null
var defaultOffsetRetentionMs = Long.MaxValue
var metrics: kMetrics = null

val groupId = "foo"
val groupInstanceId = Some("bar")
Expand Down Expand Up @@ -86,9 +90,10 @@ class GroupMetadataManagerTest {
EasyMock.expect(zkClient.getTopicPartitionCount(Topic.GROUP_METADATA_TOPIC_NAME)).andReturn(Some(2))
EasyMock.replay(zkClient)

metrics = new kMetrics()
time = new MockTime
replicaManager = EasyMock.createNiceMock(classOf[ReplicaManager])
groupMetadataManager = new GroupMetadataManager(0, ApiVersion.latestVersion, offsetConfig, replicaManager, zkClient, time)
groupMetadataManager = new GroupMetadataManager(0, ApiVersion.latestVersion, offsetConfig, replicaManager, zkClient, time, metrics)
partition = EasyMock.niceMock(classOf[Partition])
}

Expand Down Expand Up @@ -2052,4 +2057,59 @@ class GroupMetadataManagerTest {
group.transitionTo(CompletingRebalance)
expectMetrics(groupMetadataManager, 1, 0, 1)
}

def addDelay(durationMs: Long)(groupMetadata: GroupMetadata): Unit ={
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
time.sleep(durationMs)
}

@Test
def testPartitionLoadMetric(): Unit = {
Comment thread
anatasiavela marked this conversation as resolved.
val server = ManagementFactory.getPlatformMBeanServer
val mBeanName = "kafka.coordinator.group:type=group-metadata-manager-metrics"
val reporter = new JmxReporter("kafka.coordinator.group")
metrics.addReporter(reporter)

assertTrue(server.isRegistered(new ObjectName(mBeanName)))
assertEquals(Double.NaN, server.getAttribute(new ObjectName(mBeanName), "group-load-time-max"))
assertEquals(Double.NaN, server.getAttribute(new ObjectName(mBeanName), "group-load-time-avg"))
assertTrue(reporter.containsMbean(mBeanName))
Comment thread
anatasiavela marked this conversation as resolved.

val groupMetadataTopicPartition = groupTopicPartition
val startOffset = 15L
val memberId = "98098230493"
val committedOffsets = Map(
new TopicPartition("foo", 0) -> 23L,
new TopicPartition("foo", 1) -> 455L,
new TopicPartition("bar", 0) -> 8992L
)

val offsetCommitRecords = createCommittedOffsetRecords(committedOffsets)
val groupMetadataRecord = buildStableGroupRecordWithMember(generation = 15,
protocolType = "consumer", protocol = "range", memberId)
val records = MemoryRecords.withRecords(startOffset, CompressionType.NONE,
offsetCommitRecords ++ Seq(groupMetadataRecord): _*)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated

def loadWithDelay(duration: Int): Unit = {
EasyMock.reset(replicaManager)
expectGroupMetadataLoad(groupMetadataTopicPartition, startOffset, records)
EasyMock.replay(replicaManager)
groupMetadataManager.loadGroupsAndOffsets(groupMetadataTopicPartition, addDelay(duration))
}

// max of one 30sec window
val durationMs = List(9000, 3000, 7000, 7000, 7000)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
durationMs.foreach(loadWithDelay)
assertEquals(9000.0, server.getAttribute(new ObjectName(mBeanName), "group-load-time-max"))

// last window was complete, so compute new max of this window
val durationMs2 = List(6000, 2000, 4000)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
durationMs2.foreach(loadWithDelay)
assertEquals(6000.0, server.getAttribute(new ObjectName(mBeanName), "group-load-time-max"))

// even if a window records no new value, the max is the same as the previous window
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
time.sleep(31000)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated
assertEquals(6000.0, server.getAttribute(new ObjectName(mBeanName), "group-load-time-max"))

assertTrue(server.getAttribute(new ObjectName(mBeanName), "group-load-time-avg").asInstanceOf[Double] >= 0.0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import kafka.utils.{Pool, TestUtils}
import org.apache.kafka.clients.{ClientResponse, NetworkClient}
import org.apache.kafka.common.{Node, TopicPartition}
import org.apache.kafka.common.internals.Topic.TRANSACTION_STATE_TOPIC_NAME
import org.apache.kafka.common.metrics.Metrics
import org.apache.kafka.common.protocol.{ApiKeys, Errors}
import org.apache.kafka.common.record.{CompressionType, FileRecords, MemoryRecords, SimpleRecord}
import org.apache.kafka.common.requests._
Expand Down Expand Up @@ -68,7 +69,7 @@ class TransactionCoordinatorConcurrencyTest extends AbstractCoordinatorConcurren
.anyTimes()
EasyMock.replay(zkClient)

txnStateManager = new TransactionStateManager(0, zkClient, scheduler, replicaManager, txnConfig, time)
txnStateManager = new TransactionStateManager(0, zkClient, scheduler, replicaManager, txnConfig, time, new Metrics())
for (i <- 0 until numPartitions)
txnStateManager.addLoadedTransactionsToCache(i, coordinatorEpoch, new Pool[String, TransactionMetadata]())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/
package kafka.coordinator.transaction

import java.lang.management.ManagementFactory
import java.nio.ByteBuffer
import java.util.concurrent.locks.ReentrantLock
import javax.management.ObjectName

import kafka.log.Log
import kafka.server.{FetchDataInfo, LogOffsetMetadata, ReplicaManager}
Expand All @@ -26,6 +28,7 @@ import org.scalatest.Assertions.fail
import kafka.zk.KafkaZkClient
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.internals.Topic.TRANSACTION_STATE_TOPIC_NAME
import org.apache.kafka.common.metrics.{JmxReporter, Metrics}
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.record._
import org.apache.kafka.common.requests.ProduceResponse.PartitionResponse
Expand Down Expand Up @@ -59,9 +62,10 @@ class TransactionStateManagerTest {
.anyTimes()

EasyMock.replay(zkClient)
val metrics = new Metrics()

val txnConfig = TransactionConfig()
val transactionManager: TransactionStateManager = new TransactionStateManager(0, zkClient, scheduler, replicaManager, txnConfig, time)
val transactionManager: TransactionStateManager = new TransactionStateManager(0, zkClient, scheduler, replicaManager, txnConfig, time, metrics)

val transactionalId1: String = "one"
val transactionalId2: String = "two"
Expand Down Expand Up @@ -628,4 +632,33 @@ class TransactionStateManagerTest {

EasyMock.replay(replicaManager)
}

@Test
def testPartitionLoadMetric(): Unit = {
val server = ManagementFactory.getPlatformMBeanServer
val mBeanName = "kafka.coordinator.transaction:type=transaction-state-manager-metrics"
val reporter = new JmxReporter("kafka.coordinator.transaction")
metrics.addReporter(reporter)

assertTrue(server.isRegistered(new ObjectName(mBeanName)))
assertEquals(Double.NaN, server.getAttribute(new ObjectName(mBeanName), "transaction-load-time-max"))
assertEquals(Double.NaN, server.getAttribute(new ObjectName(mBeanName), "transaction-load-time-avg"))
assertTrue(reporter.containsMbean(mBeanName))

txnMetadata1.state = Ongoing
txnMetadata1.addPartitions(Set[TopicPartition](new TopicPartition("topic1", 1),
new TopicPartition("topic1", 1)))

txnRecords += new SimpleRecord(txnMessageKeyBytes1, TransactionLog.valueToBytes(txnMetadata1.prepareNoTransit()))

val startOffset = 15L
val records = MemoryRecords.withRecords(startOffset, CompressionType.NONE, txnRecords: _*)
Comment thread
anatasiavela marked this conversation as resolved.
Outdated

prepareTxnLog(topicPartition, startOffset, records)
transactionManager.loadTransactionsForTxnTopicPartition(partitionId, 0, (_, _, _, _, _) => ())
scheduler.tick()

assertTrue(server.getAttribute(new ObjectName(mBeanName), "transaction-load-time-max").asInstanceOf[Double] >= 0)
assertTrue(server.getAttribute(new ObjectName(mBeanName), "transaction-load-time-avg").asInstanceOf[Double] >= 0)
}
}
Loading