Skip to content

Stop consuming once the shutdown has started - #2834

Open
delthas wants to merge 1 commit into
improvement/BB-833/leave-group-on-shutdownfrom
improvement/BB-833/stop-consuming-on-shutdown
Open

Stop consuming once the shutdown has started#2834
delthas wants to merge 1 commit into
improvement/BB-833/leave-group-on-shutdownfrom
improvement/BB-833/stop-consuming-on-shutdown

Conversation

@delthas

@delthas delthas commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

close() drains the in-flight work before releasing the partitions, but nothing stopped the fetch loop while it waited. Every completed task re-armed _tryConsume(), so the pipeline refilled as fast as it emptied and the departure waited on work that arrived after the shutdown began.

flowchart TD
    C["close() begins draining"] --> W["wait for the queue to idle<br/>and the ledger to empty"]
    W --> T["a task completes"]
    T --> G{"shutting down?"}
    G -->|"before: not checked"| P["_tryConsume re-arms<br/>and fetches more"]
    P --> Q["queue refills as fast as it empties"]
    Q --> W
    G -->|"after: guard returns early"| S["fetch loop ends,<br/>queue drains to empty"]
    S --> L["departure proceeds"]
Loading

Changes

_tryConsume() returns early once the shutdown has started. The work already in flight is still drained and its offsets still committed — only the refill stops.

Measured against a 3000 message backlog:

concurrency task close() tasks started after close
10 200 ms 6210 ms → 175 ms 301 → 0
10 50 ms 9482 ms → 31 ms 1864 → 0

The same guard ends the self-rescheduling consume loop, which otherwise kept polling a closed client for the lifetime of the process.

Verification

Two unit tests: the loop still fetches while the consumer is running, and stops once the shutdown has started. Both were checked against the previous implementation to confirm the second fails there.

End-to-end measurement of the whole stack is in #2819.

Issue: BB-833

@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.84%. Comparing base (22f3a61) to head (653f932).

Additional details and impacted files

Impacted file tree graph

Files with missing lines Coverage Δ
lib/BackbeatConsumer.js 95.49% <100.00%> (+0.88%) ⬆️

... and 2 files with indirect coverage changes

Components Coverage Δ
Bucket Notification 80.27% <ø> (ø)
Core Library 82.07% <100.00%> (+0.65%) ⬆️
Ingestion 70.09% <ø> (ø)
Lifecycle 80.46% <ø> (ø)
Oplog Populator 85.83% <ø> (ø)
Replication 62.01% <ø> (ø)
Bucket Scanner 85.76% <ø> (ø)
@@                              Coverage Diff                               @@
##           improvement/BB-833/leave-group-on-shutdown    #2834      +/-   ##
==============================================================================
+ Coverage                                       75.57%   75.84%   +0.26%     
==============================================================================
  Files                                             200      200              
  Lines                                           13974    13976       +2     
==============================================================================
+ Hits                                            10561    10600      +39     
+ Misses                                           3403     3366      -37     
  Partials                                           10       10              
Flag Coverage Δ
api:retry 9.07% <0.00%> (-0.01%) ⬇️
api:routes 8.83% <0.00%> (-0.01%) ⬇️
bucket-scanner 85.76% <ø> (ø)
ft_test:queuepopulator 11.03% <50.00%> (+1.85%) ⬆️
ingestion 12.22% <0.00%> (-0.01%) ⬇️
lib 9.14% <100.00%> (+0.41%) ⬆️
lifecycle 19.43% <100.00%> (+0.01%) ⬆️
notification 1.01% <0.00%> (-0.01%) ⬇️
oplogPopulator 0.13% <0.00%> (-0.01%) ⬇️
replication 18.97% <100.00%> (+0.01%) ⬆️
unit 55.50% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

close() drains the in-flight work before releasing the partitions, but
nothing stopped the fetch loop while it waited: every completed task
re-armed _tryConsume(), so the pipeline refilled as fast as it drained
and the departure was delayed by work that arrived after the shutdown
had begun. Measured against a 3000 message backlog, close() took 6.2s
and started 301 further tasks at concurrency 10, and 9.5s and 1864
further tasks with shorter ones; with the guard both are 0 further
tasks, in 175ms and 31ms.

The same guard ends the self-rescheduling consume loop, which otherwise
kept polling a closed client for the lifetime of the process.

Issue: BB-833
@delthas
delthas force-pushed the improvement/BB-833/stop-consuming-on-shutdown branch from a100b8f to 653f932 Compare September 1, 2026 09:15
@delthas
delthas marked this pull request as ready for review September 1, 2026 09:35
@delthas
delthas requested review from a team, DarkIsDude and maeldonn September 1, 2026 09:35
Comment thread lib/BackbeatConsumer.js
// only delays the departure and strands the extra work. This also
// ends the self-rescheduling loop, which would otherwise keep
// consuming against a closed client for the life of the process.
if (this._shuttingDown) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the assumption is that consume() should have no effect during drain (and shutdown, by extension), as we are already unsubscribed.

So this should not be needed, as far as we know.

  • c.f. previous PR: if we need to do it for shutdown, probably also needed during "regular" rebalance.
  • Should not be needed I think, but maybe there could be race conditions (if our timeout hits after kafka rebalance timeout), so it may be a good defensive measure to avoid any race condition
  • On the other hand, this call is only way to poll kafka, i.e. let it know the consumer is alive (even if not consuming). Not sure if this has any effect on rebalance, but we should make sure that skipping these calls does not actually degrade the situation by getting kafka to kick the consumer out of the group for aggressively...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants