Skip to content

Conversation

0xffff-zhiyan
Copy link
Contributor

@0xffff-zhiyan 0xffff-zhiyan commented Aug 14, 2025

JMX metrics RequestHandlerAvgIdlePercent reports a value close to 2 in
combined kraft mode but it's expected to be b/w 0 and 1.

Screenshot 2025-08-14 at 12 41 42

This is an issue with kraft combined mode specifically because both
controller + broker are using the same Meter object in combined
mode, defined in RequestThreadIdleMeter#requestThreadIdleMeter, but
the controller and broker are using separate KafkaRequestHandlerPool
objects, where each object's threadPoolSize == KafkaConfig.numIoThreads. This means when calculating idle time, each
pool divides by its own numIoThreadsvalue before reporting to the
shared meter and Meter calculates the final result by accumulating
all the values reported by all threads. However, since there are
actually 2 × numIoThreads total threads contributing to the metric,
the denominator should be doubled to get the correct average.

POC in local environment: Screenshot
2025-08-14 at 14 27 32

Changes:

  1. When creating dataPlaneRequestHandlerPool, we pass
    KafkaConfig.isKRaftCombinedMode; in combined mode the pool adjusts the
    idle calculation (dividing by 2 * totalHandlerThreads), using the
    total thread count from both pools as the denominator
  2. When resizing threadpool, update the pool size each time a thread is
    created/destroyed to ensure the size is up to date
  3. add a unit test mocking the combined mode where broker & controller
    share the Meter and use separate RequestHandlerPool
  4. add unit test for threadpool resizing

https://issues.apache.org/jira/browse/KAFKA-19606

@github-actions github-actions bot added triage PRs from the community core Kafka Broker labels Aug 14, 2025
Copy link
Contributor

@kevin-wu24 kevin-wu24 left a comment

Choose a reason for hiding this comment

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

Thanks for the changes @0xffff-zhiyan. Left a couple of comments. Can you attach the screenshots showcasing this as a comment in this thread?

Comment on lines +268 to +276
assertEquals(3, pool.threadPoolSize.get)

// grow to 5
pool.resizeThreadPool(5)
assertEquals(5, pool.threadPoolSize.get)

// shrink to 2
pool.resizeThreadPool(2)
assertEquals(2, pool.threadPoolSize.get)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure this test tests anything new, since the previous implementation would pass it as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This only tests the pool’s resize, including expansion and shrinking, since that’s the part I modified. The previous tests didn’t specifically cover this, so this ensures the pool resizes to the correct size.

Thread.sleep(200)
value = sharedMeter.oneMinuteRate()
}
assertTrue(value >= 0.0 && value <= 1.05, s"idle percent should be within [0,1], got $value")
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this assert be [0,1], not [0,1.05]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I want to allow a small amount of fluctuation to tolerate minor measurement errors.

Copy link
Contributor

@kevin-wu24 kevin-wu24 Aug 15, 2025

Choose a reason for hiding this comment

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

Why would there be a measurement error? Ever having a measurement of >1 indicates a bug in our logic determining the denominator.

Copy link
Member

@jsancio jsancio left a comment

Choose a reason for hiding this comment

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

Thanks for the improvement @0xffff-zhiyan . I have one high-level suggestion.

Comment on lines +96 to +97
nodeName: String = "broker",
val isCombinedMode: Boolean = false,
Copy link
Member

Choose a reason for hiding this comment

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

This is a very ad-hoc solution which assumes one or two request handler pool(s). To solve this problem, I see two options:

  1. Keep track of the number of threads in a pool and the number of threads across all of the request handler pools. The metrics would use the global count when computing the average thread idle ratio.
  2. Have two different metrics for the broker's request handler versus the controller's request handler. This would of course require a KIP.

I am leaning toward options 1 for now. In the future we can always do option 2. which would at a high-level define 3 metrics:

  1. RequestHandlerAvgIdlePercent which reports the thread idle ratio for all of the request pools
  2. BrokerRequestHandlerAvgIdlePercent which reports the thread idle ratio for the broker request pool
  3. ControllerRequestHandlerAvgIdlePercent which reports the thread idle ration for the controller request pool.

@github-actions github-actions bot removed the triage PRs from the community label Aug 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants