Skip to content

feat: support cluster topology discovery via CLUSTER SHARDS command - #4266

Open
bchrobot wants to merge 3 commits into
redis:masterfrom
bchrobot:feat/support-discovery-via-cluster-shards-command
Open

feat: support cluster topology discovery via CLUSTER SHARDS command#4266
bchrobot wants to merge 3 commits into
redis:masterfrom
bchrobot:feat/support-discovery-via-cluster-shards-command

Conversation

@bchrobot

@bchrobot bchrobot commented Aug 14, 2026

Copy link
Copy Markdown

Description of change

Add support for cluster discovery via CLUSTER SHARDS rather than CLUSTER SLOTS, deprecated in Redis 7.0+.

This introduces ClusterTopologyProvider/AsyncClusterTopologyProvider, mirroring the existing PolicyResolver pattern: a provider names the topology command and parses its reply, while NodesManager keeps connection handling and cache construction. Adding a source is now a new subclass rather than a branch in initialize().

CLUSTER SHARDS requires Redis 7.0+ and returns one entry per shard rather than per slot range, so replies stay small on clusters with fragmented slot maps. It is strictly opt-in via the new topology_provider kwarg; the default remains CLUSTER SLOTS.

The shards parser is tolerant of every shape the reply arrives in, because the async stack installs a CLUSTER SHARDS response callback on its node connections while the sync stack receives the raw wire reply.

Replicas reported as failed or loading are excluded; an unhealthy primary is retained, since dropping it would leave its slots uncovered.

Pull Request check-list

Please make sure to review and check all of these items:

  • Do tests and lints pass with this change?
  • Do the CI tests pass with this change (enable it first in your forked repo and wait for the github action build to finish)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
  • Is there an example added to the examples folder (if applicable)?

NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open.


Note

Medium Risk
Touches cluster bootstrap and slot mapping for both sync and async clients. Default remains CLUSTER SLOTS, but a parser bug in the new path could mis-route commands on opt-in deployments.

Overview
Makes cluster slot discovery pluggable so clients can opt into CLUSTER SHARDS (Redis 7.0+) instead of the default CLUSTER SLOTS. Default behavior is unchanged.

A topology_provider on sync and async RedisCluster names the command and parses the reply; NodesManager still builds the node/slot caches. ClusterShardsTopologyProvider returns one entry per shard (better on fragmented maps), skips failed/loading replicas while keeping unhealthy primaries, and can prefer TLS ports. Empty/null endpoints stay empty so load-balanced nodes are reached at the queried host, not their internal IP.

Custom sources can subclass the provider. Docs and tests cover RESP2/RESP3 reply shapes and parity with CLUSTER SLOTS.

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

@bchrobot bchrobot changed the title Feat/support discovery via cluster shards command feat: support cluster topology discovery via CLUSTER SHARDS command Aug 14, 2026
@bchrobot
bchrobot marked this pull request as ready for review August 14, 2026 13:02
Comment thread redis/cluster_topology.py

@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: 822402dc6c

ℹ️ 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".

Comment thread redis/cluster_topology.py
Comment thread redis/cluster.py Outdated
Comment thread redis/cluster_topology.py
endpoint = _as_str(node.get("endpoint", ""))
host = endpoint or _as_str(node.get("ip", ""))

port_keys = ("tls-port", "port") if prefer_tls_port else ("port", "tls-port")

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 Prefer TLS ports automatically for SSL cluster clients

When a caller opts into ClusterShardsTopologyProvider() on a TLS cluster using ssl=True or rediss://, Redis can return both the non-TLS port and the tls-port in CLUSTER SHARDS; this default ordering selects the plain TCP port first, then NodesManager builds SSL connections to that non-TLS port and routed commands fail after discovery. The client already knows it is using SSL, so the shards topology path should select tls-port automatically in that configuration instead of requiring a second, easy-to-miss provider flag.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reasonable point, but deferring it rather than changing it here.

Two reasons. Auto-detecting from the client SSL config would mean the parser reads connection kwargs, which costs the property that makes it cheap to test — it is currently pure and covered directly across all four response shapes. And ssl=True does not strictly imply the TLS port is wanted; a node can advertise both, and the choice is a deployment detail.

The explicit prefer_tls_port flag is discoverable from the provider signature and documented in the Topology Discovery section. If real usage shows the default trips people up on TLS clusters, auto-selection with the flag as an override is a clean follow-up.

Leaving this thread open for a maintainer to weigh in.

@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 bbaf4b1. Configure here.

Comment thread redis/cluster.py
# If there's only one server in the cluster, its ``host`` is ''
# Fix it to the host in startup_nodes
if (
len(cluster_slots) == 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Misleading shards discovery error

Medium Severity

Any ResponseError from the topology command is rewritten as “Cluster mode is not enabled on this node”. With ClusterShardsTopologyProvider, Redis older than 7.0 returns an unknown-command error for CLUSTER SHARDS, so initialization fails with a false diagnosis instead of indicating an unsupported command.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bbaf4b1. Configure here.

@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: bbaf4b1cf3

ℹ️ 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".

Comment thread redis/cluster_topology.py
# it at the host the topology command was sent to, so leave the host empty
# rather than substituting ``ip``, which is the unreachable internal address.
endpoint = node.get("endpoint")
host = "" if endpoint is None else _as_str(endpoint)

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 Don’t cache '?' as a node hostname

When CLUSTER SHARDS reports endpoint as ? (Redis uses this for hostname-preferred nodes without an announced hostname), this line caches ? as the node host. NodesManager then builds connections to names like ?:7000, so routed commands fail even though the same node map still carries its ip; handle ? as an unknown endpoint, for example by falling back to ip when available or failing discovery clearly instead of treating it as connectable.

Useful? React with 👍 / 👎.

@petyaslavova

Copy link
Copy Markdown
Collaborator

Hey @bchrobot, thank you for your contribution! We will have a look at it soon!

@Mukller Mukller 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.

Verified locally against upstream master and this branch (Python 3.13; no live cluster in this environment — noted where it matters).

Test results:

  • pytest tests/test_cluster.py -k "shards or topology or slots"40 passed (8 setup errors = server-dependent fixtures without a local Redis)
  • Same filter, async suite — 24 passed, 4 failed; those exact 4 fail identically on upstream master (test_cluster_slots[pool], test_cluster_addslots[pool], etc. — ConnectionRefused class), so unrelated to this diff.

Design assessment — this is a clean abstraction:

  1. The new redis/cluster_topology.py isolates all reply-shape knowledge in one module: _as_mapping normalizes RESP3 maps vs RESP2 flat arrays, _slot_ranges handles both paired and flat slot lists, and each provider exposes .command + .parse() — so adding future topology commands is a subclass away.
  2. Default wiring keeps back-compat: RedisCluster(topology_provider=AsyncClusterSlotsTopologyProvider()) reproduces today's CLUSTER SLOTS behavior byte-for-byte path-wise.
  3. The LB-endpoint fix (commit title) has a proper rationale comment: a node that doesn't know its own address (behind a load balancer) now yields an empty host instead of the unreachable internal ip, letting the caller reuse the queried host. That's the right call — substituting ip there produces connections that can never succeed.

Docs: clustering.rst/connections.rst additions document the provider injection point accurately per the implementation.

Caveat stated plainly: I could not exercise live CLUSTER SHARDS round-trips here, so parser correctness against real 7.x/8.x SHARDS replies rests on the branch's own added tests (which are thorough — 444 new lines covering RESP2/RESP3 shapes, TLS preference, unknown endpoints).

Approving — solid groundwork for SHARDS support.

NodesManager hardcoded CLUSTER SLOTS and read the reply positionally inline,
leaving no seam for an alternative topology source.

Introduce ClusterTopologyProvider / AsyncClusterTopologyProvider, mirroring the
existing PolicyResolver pattern: a provider names the topology command and parses
its reply, while NodesManager keeps connection handling and cache construction.
Adding a source is now a new subclass rather than a branch in initialize().

CLUSTER SHARDS requires Redis 7.0+ and returns one entry per shard rather than
per slot range, so replies stay small on clusters with fragmented slot maps. It
is strictly opt-in via the new topology_provider kwarg; the default remains
CLUSTER SLOTS.

The shards parser is tolerant of every shape the reply arrives in, because the
async stack installs a CLUSTER SHARDS response callback on its node connections
while the sync stack receives the raw wire reply.

Replicas reported as failed or loading are excluded; an unhealthy primary is
retained, since dropping it would leave its slots uncovered.
@bchrobot
bchrobot force-pushed the feat/support-discovery-via-cluster-shards-command branch from bbaf4b1 to 1111849 Compare August 23, 2026 10:41

@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: 1111849b02

ℹ️ 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".

Comment thread redis/cluster_topology.py
topology = []
for slot in response:
start, end = int(slot[0]), int(slot[1])
primary = (_as_str(slot[2][0]), int(slot[2][1]))

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 Normalize null CLUSTER SLOTS endpoints

When an async single-node cluster returns the valid unknown-endpoint form [None, port, ...] from CLUSTER SLOTS, this parser preserves None, while the new NodesManager fallback only recognizes ""; initialization therefore caches a None:port node and later attempts connections with a null host. Before this change, async initialization used not cluster_slots[0][2][0], so this case correctly reused the startup host. This is fresh evidence distinct from the fixed CLUSTER SHARDS path: the current shared SLOTS parser still passes None through, so normalize it to an empty host and allow the queried-host fallback to run.

AGENTS.md reference: AGENTS.md:L159-L163

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants