Add dynamic num.stream.threads sizing for Kafka Streams apps#119
Open
suresh-prakash wants to merge 2 commits into
Open
Add dynamic num.stream.threads sizing for Kafka Streams apps#119suresh-prakash wants to merge 2 commits into
suresh-prakash wants to merge 2 commits into
Conversation
Introduce StreamThreadsCountResolver and DynamicStreamThreadsCountCalculator under a new `threading` package, and wire them into KafkaStreamsApp via an overrideable getStreamThreadsCountResolver() hook. When num.stream.threads is set to the sentinel "DYNAMIC", the framework sums the maximum partition count across each sub-topology's source topics and divides by the configured replica count, producing a per-instance thread count that keeps every task active without idle threads. Apps that don't opt in are unaffected: isDynamic() short-circuits unless the value is literally "DYNAMIC", and the resolution path is wrapped in try/catch so any unexpected failure logs and leaves the streams config untouched. Bad broker calls or absent topics fall back to a safe default of 8 threads instead of preventing startup. Also fixes a typo'd `import Optional;` left from an earlier refactor. Tests cover the partition math, multi-subtopology summation, absent-topic handling, replica-count edge cases, sentinel detection, and the resolver's fallback behavior. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Test Results 7 files - 8 7 suites - 8 14s ⏱️ -15s For more details on these failures, see this check. Results for commit 9187236. ± Comparison against base commit 0aa8c44. This pull request removes 41 and adds 1 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Adds a concrete code snippet showing how a consumer app wires its own replica-count source through StreamThreadsCountResolver, so adopters don't have to read the calculator/resolver internals to integrate. Also clarifies that apps not overriding the hook are unaffected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Introduces a per-instance auto-sizing path for
num.stream.threads. When an app setsnum.stream.threads = "DYNAMIC", the framework derives the right thread count from the topology's partition layout and the deployment's replica count — keeping every stream task active without idle threads or noisy under-allocation.The new
threadingpackage contains:DynamicStreamThreadsCountCalculator— sums the maximum partition count across each sub-topology's source topics (one task per partition per sub-topology) and divides by the replica count to get per-instance threads.StreamThreadsCountResolver— bridges topology + AdminClient to the calculator. Falls back to a safe default of8threads on any failure (broker outage, missing replica count, AdminClient errors) so the app can still start.KafkaStreamsAppexposes a new overrideable hookgetStreamThreadsCountResolver()returningOptional.empty()by default. Apps that want auto-sizing override it; apps that don't are completely unaffected.Why
Currently every Kafka Streams app hardcodes
num.stream.threadsper-cluster in its Helm values. As topics grow partitions or as replica counts change, the hardcoded value drifts from the optimal — leading to idle threads or under-provisioned consumption. This change lets ops setDYNAMIConce and have the right number computed at startup based on actual broker state.Safety
This is a library change consumed by every Hypertrace Kafka Streams app, so the new flow is engineered to be invisible to apps that don't opt in:
StreamThreadsCountResolver.isDynamic(...)short-circuits on the very first check unless the configured value is literally the string"DYNAMIC". Numeric configs (the existing default) take the same code path as before.doInit()is wrapped intry { ... } catch (Throwable t) { log; }. Any unexpected failure — resolver bug, AdminClient timeout, classloader issue, NPE — is swallowed and the streams config is left untouched, soKafkaStreamsstarts with whatever value was originally configured.RuntimeExceptionfrom the calculator and falls back toFALLBACK_NUM_STREAM_THREADS = 8.Other changes
import Optional;(missingjava.util.) inKafkaStreamsApp.java.mockito-junit-jupiter:5.2.0as a test dep so@ExtendWith(MockitoExtension.class)resolves; matches the existingmockito-core:5.2.0version.Test plan
isDynamictrue on sentinel, false on numeric/absent