fix(kafka source): wait for aborted partition consumers before continuing a rebalance - #26438
Open
sandervandegeijn wants to merge 2 commits into
Open
sandervandegeijn wants to merge 2 commits into
sandervandegeijn wants to merge 2 commits into
Conversation
Contributor
|
All contributors have signed the CLA ✍️ ✅ |
…uing a rebalance After a consumer group rebalance the kafka source could silently stop consuming some of its partitions: lag grew, memory grew, nothing was logged and only a restart helped (vectordotdev#22006). Every partition is consumed through its own rdkafka `StreamPartitionQueue`. All queues for one partition wrap the same librdkafka fetch queue, whose wake-up callback is last-writer-wins, and dropping a `StreamPartitionQueue` unconditionally disables that callback. When the acknowledgement drain deadline fired during a revoke, the coordinator aborted the partition tasks and immediately released the rebalance callback. Tokio drops an aborted task asynchronously, so if the partition was re-assigned to this consumer before the old task was dropped, the new queue was split first and the old drop then removed the new queue's wake-up: the new task was never woken again and fetched messages piled up unread. The coordinator now stays in the draining state after aborting until `JoinSet::join_next_with_id` has reaped every aborted task; tokio drops the task's future before yielding its result, so the old queue is gone before the rebalance callback is released and the partition can be split again. Task ids guard the abort handle and end signal bookkeeping so a finished task cannot remove the entries of a newer task for the same partition. The wait is not bounded, because giving up would re-introduce the stall; it is logged at warn level every `drain_timeout_ms` while it lasts. Aborted tasks no longer trigger a synchronous commit, the client commits once more after the drain anyway. Reproduced and verified against a live broker: on the unmodified 0.58.0 release binary, a partition task that is inside a long synchronous decode when the deadline fires reliably triggers the stall, and a probe on librdkafka's queue callback shows the second queue being enabled and then disabled by the old drop. With this change the probe never sees two live queues and every partition keeps consuming. New integration tests exercise the aborted-drain re-assignment path under backpressure for both rebalance protocols. Closes vectordotdev#22006
The kafka source tests never initialised tracing, so the drain deadline and abort log lines added for vectordotdev#22006 were invisible when debugging a failing run. The aborted-drain tests now call `trace_init()` so `VECTOR_LOG=debug` shows them, and the settle sleep before the follow-up messages explains why it is needed. Scope note for vectordotdev#22006: the fix in the previous commit covers the path where pending acknowledgements are not drained within `drain_timeout_ms` during a revoke and the partitions are then re-assigned to the same consumer. Other reports in that issue thread (partition count changes, very large consumer groups) are not covered.
sandervandegeijn
force-pushed
the
fix/kafka-source-rebalance-stall
branch
from
September 20, 2026 07:57
36b6b22 to
495947e
Compare
Author
|
I have read the CLA Document and I hereby sign the CLA |
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
After a consumer group rebalance the
kafkasource could silently stop consuming some of its partitions. Lag and memory grew, nothing was logged, and only a restart helped. This is the behaviour reported in #22006.Root cause
Every partition is consumed through its own rdkafka
StreamPartitionQueue. All queues split for the same partition wrap one librdkafka fetch queue.split_partition_queueinstalls a wake-up callback on that queue andDrop for StreamPartitionQueueremoves it unconditionally, so the last writer wins.When pending acknowledgements could not be drained within
drain_timeout_msduring a revoke (for example under sink backpressure), the coordinator aborted the partition tasks and immediately released the rebalance callback. Tokio drops an aborted task asynchronously. If the partition was re-assigned to the same consumer before the old task was actually dropped, the new queue was split first and the old drop then removed the new queue's wake-up. The new task was never woken again and fetched messages piled up unread.Fix
JoinSet::join_next_with_idhas reaped every aborted task. Tokio drops the task's future before yielding its result, so the old queue is gone before the rebalance callback is released.select!pattern, so the drain can no longer hang on them.warneverydrain_timeout_mswhile it lasts. In practice it ends at the task's next.await.This covers the path where partitions revoked under backpressure come back to the same consumer. Other reports in the #22006 thread (partition count changes, very large consumer groups) are not addressed here.
I developed and verified this fix together with Claude Code. I reviewed every line of it and the reasoning above is my own understanding of the problem.
References
Closes #22006
Vector configuration
The integration tests use a source equivalent to this, with a deliberately short drain timeout so the deadline fires during the rebalance:
How did you test this PR?
Reproduction and proof. I confirmed the mechanism in the rdkafka 0.39.0 source and then reproduced the stall deterministically against a live broker. With a scratch-only patch that makes every message take a long synchronous decode, the partition tasks are mid-decode when the drain deadline fires and their abort lands late. A second blocked group member joins and leaves twice. I ran this scenario from the same branch with and without the new waiting logic:
range,roundrobincooperative-stickyCooperative-sticky does not stall in this scenario because revoked partitions move to the other member instead of returning to the same consumer immediately. The reproduction test itself is not part of this PR because it needs a test-only hook in production code.
Integration tests. Two new tests (
consumes_after_aborted_drain_*) exercise the aborted-drain re-assignment path under backpressure for both rebalance protocols. They passed in 9 out of 9 runs. With tracing enabled they show the drain deadline firing and the tasks being aborted, and never the "still waiting" warning. The fullkafka-integration-testssuite passes on this branch (the two sink SASL/TLS tests only fail locally because my scratch broker has no SASL or TLS listener).Production. I have been running this fix in production for a week. The stall has not recurred and I have seen no side effects.
Static checks.
make check-clippy(kafka features),make check-fmt,cargo vdev check changelog-fragments, andmarkdownlint-cli2on the changelog fragment are clean. The generated docs inwebsite/cueare updated to match the changeddrain_timeout_msdescription.Does this PR include user facing changes?
no-changeloglabel to this PR.The user-visible change is that a rebalance or shutdown can take slightly longer than
drain_timeout_mswhen partitions did not drain in time, and thedrain_timeout_msdocumentation now states that undrained partitions are stopped.Contributor Guidelines
@vectordotdev/vectorto reach out to us regarding this PR.git merge origin masterandgit push.