Skip to content

fix(cluster): retry sharded pub/sub resubscribe after stale rediscover - #3589

Open
waw666waw666 wants to merge 1 commit into
redis:masterfrom
waw666waw666:fix-sharded-pubsub-moved-retry
Open

waw666waw666 wants to merge 1 commit into
redis:masterfrom
waw666waw666:fix-sharded-pubsub-moved-retry

Conversation

@waw666waw666

@waw666waw666 waw666waw666 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3585

When part of a master's slots move while a RedisCluster holds sharded
subscriptions, a few channels can be dropped for good. The
sharded-channel-moved handler rediscovers and resubscribes, but
rediscover() reuses an in-flight discovery that may have started before
this slot moved. The slot map still points at the old master, the
resubscribe gets MOVED, and the handler used to emit
sharded-channel-moved-error and give up.

Fix

  • If the slot still maps to the client that just lost the channel after
    rediscover, force a fresh discovery (wait for the in-flight one, then
    start a new one).
  • Retry rediscover + resubscribe on failure up to maxCommandRedirections
    (same budget as the SSUBSCRIBE MOVED loop), instead of dropping the
    subscription on the first error.

Tests

  • New cluster PubSub case: subscribe to many channels on one master, move
    most of their slots one-by-one, assert every channel receives again and
    no sharded-channel-moved-error is emitted.
  • Existing #3311 / sharded-channel-moved cases still pass.

Note

Medium Risk
Touches cluster topology refresh and sharded Pub/Sub reattachment with a new retry loop; behavior is bounded by maxCommandRedirections but could affect live migration edge cases.

Overview
Sharded Pub/Sub resubscribe after slot moves no longer gives up when coalesced rediscover() returns a stale topology: the handler now loops with the same max redirect budget as SSUBSCRIBE, forcing a fresh discovery (wait for any in‑flight rediscover, then run again) when routing still lands on the node that lost the channel.

On repeated failures (stale maps, MOVED races), it retries instead of emitting sharded-channel-moved-error immediately; errors are only surfaced after exhausting the budget.

Adds a cluster integration test that migrates many sharded channels concurrently and asserts all subscriptions keep receiving and no moved errors fire.

Reviewed by Cursor Bugbot for commit bf7e8ff. Bugbot is set up for automated code reviews on this repo. Configure here.

During a live reshard, rediscover() may reuse an in-flight discovery that
started before a channel's slot moved. Resubscribing then hits MOVED (or
races an uncovered slot) and used to emit sharded-channel-moved-error and
drop the subscription permanently (redis#3585).

Force a fresh rediscover when the slot still maps to the node that lost
the channel, and retry the resubscribe up to maxCommandRedirections.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T15:05:21.789203Z bf7e8ff PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bf7e8ff445

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
}

await redirectTo.extendPubSubChannelListeners(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Clear the failed target before retrying resubscription

When the first extendPubSubChannelListeners reaches a target but its SSUBSCRIBE is rejected (for example, the target replies MOVED while its topology view is stale, or returns NOPERM), PubSub.extendChannelListeners in client/pub-sub.ts has already stored this channel and its reject handler does not remove it. If the forced discovery selects that same target on the next iteration, this call becomes a no-op and resolves, so the handler returns without a wire subscription and without sharded-channel-moved-error; roll back the failed target's listener entry or otherwise ensure the retry actually reissues SSUBSCRIBE.

Useful? React with 👍 / 👎.

Comment on lines +1491 to +1492
if (redirectTo === client) {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resubscribe when a fresh map returns to the source

If a slot is reassigned back to its original master before this async move handler finishes, such as during a reshard rollback, the forced discovery is fresh but legitimately returns client. The server's earlier SUNSUBSCRIBE already removed the wire subscription and local listener entry, yet this continue refuses to send SSUBSCRIBE; after exhausting the budget it emits an error and permanently drops the channel. After the forced refresh, allow reattachment to the original owner rather than treating object identity as proof of staleness.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit bf7e8ff. Configure here.

return;
}
await this.#forceRediscover(client).catch(() => {});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Retry skips SSUBSCRIBE after failed resubscribe

High Severity

A failed extendPubSubChannelListeners still registers the channel locally, so a later retry on the same client treats it as already subscribed and skips SSUBSCRIBE. The handler then returns without emitting sharded-channel-moved-error, and the sharded subscription is dropped silently.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bf7e8ff. Configure here.

This branch has not been deployed

No deployments
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.

RedisCluster: a few sharded subscriptions are dropped permanently when some of a master's slots are resharded (MOVED on resubscribe is not retried)

1 participant