You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jira@kafka.apache.org by "Shekhar Prasad Rajak (Jira)" <ji...@apache.org> on 2026/07/14 18:57:00 UTC
[jira] [Updated] (KAFKA-20803) Share-group persister: O(N^2) response demultiplexing in PersisterStateManager batched RPCs
[ https://issues.apache.org/jira/browse/KAFKA-20803?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Shekhar Prasad Rajak updated KAFKA-20803:
-----------------------------------------
Labels: queue queues-for-kafka share-group (was: )
> Share-group persister: O(N^2) response demultiplexing in PersisterStateManager batched RPCs
> -------------------------------------------------------------------------------------------
>
> Key: KAFKA-20803
> URL: https://issues.apache.org/jira/browse/KAFKA-20803
> Project: Kafka
> Issue Type: Improvement
> Affects Versions: 4.3.1
> Reporter: Shekhar Prasad Rajak
> Assignee: Shekhar Prasad Rajak
> Priority: Major
> Labels: queue, queues-for-kafka, share-group
> Fix For: 4.3.2
>
>
> When the share-group persister coalesces many partitions into a single WRITE/READ/READ_SUMMARY/INITIALIZE/DELETE_SHARE_GROUP_STATE RPC, the response is demultiplexed in O(N ^ 2) time, where N is the number of
> partitions batched into that RPC. This runs on the network send thread, so it also stalls unrelated in-flight RPCs.
>
> *Current behavior*
> Request coalescing is fine — RequestCoalescerHelper.coalesceWrites (PersisterStateManager.java) folds all N per-partition handlers into one request in a single O(N) pass.
> The response side is the problem. The completion callback hands the entire combined response to every handler:
> // PersisterStateManager.generateRequests()
> handlersPerGroup.forEach(handler1 -> handler1.onComplete(response));
> Each handler then linearly scans the full response to locate its own single PartitionResult:
> // WriteStateHandler.handleRequestResponse(),
> for (WriteStateResult r : combinedResponse.data().results()) // walk all topics
> if (r.topicId().equals(partitionKey().topicId()))
> r.partitions().stream()
> .filter(p -> p.partition() == partitionKey().partition()) // walk all partitions
> .findFirst();
> N handlers × O(N) scan each = O(N ^ 2). The five handler types (WriteStateHandler, ReadStateHandler, ReadStateSummaryHandler, InitializeStateHandler, DeleteStateHandler) all share this identical pattern.
>
>
> *Why it matters*
>
> - Share groups typically subscribe to a topic with many partitions, and many partitions hash to the same share-coordinator node, so N is large in practice (hundreds).
> - The demux executes inside the completion callback on the NetworkClient/InterBrokerSendThread, so the O(N^2) work blocks the send loop and delays every other queued RPC (reads, writes to other coordinators,
> find-coordinator). wakeup() only fires after the scan completes.
>
> *Proposed fix*
>
> Demultiplex the combined response once into a Map<Uuid topicId, Map<Integer partition, PartitionResult>> (O(N)), then hand each handler only its own slice for an O(1) lookup — replacing the per-handler linear
> scan.
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)