Ignore a deferred un-assign superseded by a later rebalance - #2818
Ignore a deferred un-assign superseded by a later rebalance#2818delthas wants to merge 1 commit into
Conversation
Hello delthas,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Codecov Report❌ Patch coverage is
Additional details and impacted files
... and 2 files with indirect coverage changes
@@ Coverage Diff @@
## development/9.5 #2818 +/- ##
===================================================
- Coverage 75.72% 75.51% -0.21%
===================================================
Files 200 200
Lines 13926 13946 +20
===================================================
- Hits 10545 10531 -14
- Misses 3371 3405 +34
Partials 10 10
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Waiting for approvalThe following approvals are needed before I can proceed with the merge:
|
de184b1 to
fa04a32
Compare
Request integration branchesWaiting for integration branch creation to be requested by the user. To request integration branches, please comment on this pull request with the following command: Alternatively, the |
fa04a32 to
750c68e
Compare
9a56681 to
27de845
Compare
On ERR__REVOKE_PARTITIONS the un-assign is deferred until the processing queue and the offset ledger have drained. If the next rebalance granted the partitions back before that happened, the deferred callback still ran and un-assigned them: the consumer then owned partitions at the broker with no local assignment, and since group membership had not changed, nothing triggered another rebalance to rescue it. Track a rebalance id, bumped on every rebalance event, and give up on a deferred un-assign whose id no longer matches. The check runs again before un-assigning, as publishing offsets to zookeeper in between is asynchronous and leaves a second window for the partitions to come back. The drain watchdog is now cleared on every rebalance rather than only on assignment, so a timer armed by a superseded revoke can no longer disconnect a consumer that is not stuck. Issue: BB-835
27de845 to
6c6acda
Compare
francoisferrand
left a comment
There was a problem hiding this comment.
AFAIK this cannot happen:
- while draining, we did not "reply" to kafka yet
- if we don't reply within the expected period (5 min I think), this consumer is kicked out of the group, and will not take part in the next rebalance: i.e. it will not be assigned any partition
- the consumer may rejoin the group later, when it calls
consume(); but this cannot happen while draining.
In addition, I think we have an extra safety: if we fail to drain quickly enough, the process will sabotage itself to fail health check, so it is restarted.
|
Hm, there is really a run where we found exactly that apparently: But I will try to do a large census again to see if I can reasonably reproduce it. |
|
Running a census to see if I can reproduce this |
|
Replaced by #2837 |
On
ERR__REVOKE_PARTITIONSthe un-assign is deferred until in-flight work has drained, so offsets can be committed before the partitions are released. If the next rebalance grants those partitions back before the drain finishes, the deferred callback still fires and un-assigns them.sequenceDiagram participant K as Kafka participant C as BackbeatConsumer K->>C: revoke [0-4] Note right of C: work in flight,<br/>un-assign deferred K->>C: assign [0-4] C->>C: assign() applied Note right of C: drain completes C->>K: unassign() — discards the new assignment Note right of C: owns partitions at the broker,<br/>no local assignment, no rebalance to recoverMembership has not changed, so nothing triggers another rebalance and the consumer is stuck until restarted, while looking healthy on every liveness signal. Observed in a CTST census run: idle for 2 h 35 min with 188 of 199 messages stranded.
Changes
Every rebalance takes the next
_rebalanceId. A deferred un-assign carries the id it was created with and gives up as soon as that no longer matches — the partitions belong to a later rebalance by then, so there is nothing left for it to do.sequenceDiagram participant K as Kafka participant C as BackbeatConsumer participant Z as ZooKeeper K->>C: revoke [0-4] Note right of C: id 7 — work in flight,<br/>un-assign deferred Note over C: drain completes,<br/>deferred un-assign wakes C->>C: guard 1 — still id 7, proceed C->>Z: publish offsets (asynchronous) K->>C: assign [0-4] Note right of C: id 8 supersedes 7 Z-->>C: published C->>C: guard 2 — 7 is not 8, give up Note right of C: assignment from id 8 kept,<br/>counter records SUPERSEDEDTwo guards rather than one because the offset publish between them is asynchronous: checking only on wake-up leaves exactly the window above. The first guard also protects the drain signals and the watchdog, which by then belong to the later rebalance.
SUPERSEDEDvalue on the existing rebalance counter, so the race is visible in metrics. Nothing downstream enumerates these values.Tests
Eight unit tests in
tests/unit/backbeatConsumer.js, no broker needed (existingBackbeatConsumerMock). Each guard was verified by reverting it and confirming exactly one test fails.Note
A superseded cycle does not emit
'unassign', so a shutdown racing an assignment still leavesclose()waiting. That is pre-existing and owned by BB-833: emitting the event without having un-assigned would pushclose()intodisconnect()while still holding partitions, which hangs.Issue: BB-835