Skip to content

Scan only primary nodes in scanIteration on cluster - #4798

Merged
ggivo merged 4 commits into
redis:masterfrom
RaphaelFakhri:fix-scan-iteration-primaries
Oct 20, 2026
Merged

ggivo merged 4 commits into
redis:masterfrom
RaphaelFakhri:fix-scan-iteration-primaries

Conversation

@RaphaelFakhri

@RaphaelFakhri RaphaelFakhri commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On a cluster with replicas, scanIteration returns every key once from its primary and again from each replica. JedisCommandIterationBase now iterates getPrimaryNodesConnectionMap() instead of getConnectionMap(), so SCAN runs once per shard. This matches how KEYS and other all-shards commands are broadcast. Fixes #4797.

Key decisions and assumptions

  • The change is in the shared iteration base, so ftSearchIteration and ftAggregateIteration also stop querying replicas.
  • MultiDbConnectionProvider overrides only getConnectionMap(). Without an override, getPrimaryNodesConnectionMap() falls back to the interface default, which borrows a connection that is never returned to the pool. The provider now returns the active database pool from both methods.
  • Standalone, pooled and Sentinel providers already return the same map from both methods, so their behavior does not change.

Behavioral changes

  • On cluster, scanIteration returns each key once. With 3 primaries and 1 replica each, 100 keys previously came back as 200.
  • Release notes entry added under "Behavior Changes" in docs/release-notes/8.1.0.md.

Testing

testScanIterationSkipsReplicas mocks a provider whose all-nodes map has a primary and a replica and whose primary map has only the primary, and asserts that each key is returned once. The test fails without the fix (key1, key2, key1, key2) and passes with it. The two existing scanIteration mocked tests now stub the primary-nodes map. Checked by hand against a Redis 8.2 cluster with 3 primaries and 3 replicas: 200 keys before the fix, 100 after.


Note

Medium Risk
Changes cluster iteration behavior for scan and related iterators; fixes incorrect duplicates but is a user-visible semantic change for replica-heavy setups.

Overview
Fixes duplicate keys from scanIteration on Redis Cluster when replicas are included in the provider’s node map. JedisCommandIterationBase now builds its per-node round-robin from getPrimaryNodesConnectionMap() instead of getConnectionMap(), so SCAN runs once per shard (same idea as other all-shard commands). That also applies to iteration helpers built on the same base (e.g. FT search/aggregate iterations).

On ConnectionProvider, the default getPrimaryNodesConnectionMap() delegates to getConnectionMap() (with docs) so standalone/pooled providers keep the same behavior; cluster providers that override the primary map exclude replicas.

Tests stub the primary map for existing scanIteration mocks, add testScanIterationSkipsReplicas, and assert the interface default matches getConnectionMap().

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

On a cluster with replicas, the command iteration base ran SCAN on every
known node, so each key was returned by its primary and again by every
replica. Iterate the primary nodes map instead, matching how KEYS and
other all-shards commands are broadcast. MultiDbConnectionProvider now
overrides getPrimaryNodesConnectionMap so it keeps using the active
database pool instead of the interface default.
@RaphaelFakhri
RaphaelFakhri force-pushed the fix-scan-iteration-primaries branch from 7fe093e to df932b9 Compare October 19, 2026 09:10
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 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-10-19T14:39:25.755620Z 25ca6df New commits
ℹ️ 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: 7fe093ef70

ℹ️ 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 docs/release-notes/8.1.0.md Resolved
@ggivo ggivo added the waiting-for-triage Still needs to be triaged label Oct 19, 2026
ggivo added a commit that referenced this pull request Oct 19, 2026
* Return pooled connections from iterations on MultiDbClient

MultiDbConnectionProvider overrode getConnectionMap() but not
getPrimaryNodesConnectionMap(), so the interface default borrowed a
pooled connection that AggregateIterator treated as caller-owned and
never returned; each ftAggregateIterator call leaked one connection
until the pool was exhausted. Return the active database's pool from
both methods.

The ITs also cover scanIteration and ftAggregateIteration, which switch
to the primaries map in #4798. Fixes #4799.

Co-authored-by: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com>

* Share the MultiDbClient active-pool test helper

Five tests copied the same lookup of the active database's connection pool. MultiDbConnectionProviderHelper.activeConnectionPool replaces them; test code only.

---------

Co-authored-by: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com>
ggivo added 2 commits October 19, 2026 16:16
# Conflicts:
#	docs/release-notes/8.1.0.md
A bug fix with no action required from users.
@ggivo

ggivo commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator

Pushed 27ab24e and f416959 on top of your branch: merged master, which now carries the MultiDbConnectionProvider override and the MultiDb iteration ITs via #4800 (extracted from this PR, with you as co-author), and dropped the release-notes entry since this is a bug fix with no action required. The remaining diff is the JedisCommandIterationBase switch and its unit test.

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

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

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ggivo ggivo added bug and removed waiting-for-triage Still needs to be triaged labels Oct 19, 2026
@ggivo ggivo added this to the 8.1.0 milestone Oct 19, 2026
The default returned a single getConnection() connection and ignored any
getConnectionMap() override; with a pooled provider that connection is
borrowed and never returned (redis#4323, redis#4799). Now that iterations read the
primaries map, a custom provider overriding only getConnectionMap()
would iterate one node through such a connection. Built-in providers
override both methods or neither, so they behave as before.

@ggivo ggivo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. scanIteration, ftSearchIteration and ftAggregateIteration now iterate only the primaries, the same nodes all-shards commands are broadcast to, so on a cluster with replicas they no longer return duplicate keys (#4797).

I pushed two commits on top:

  • f416959 drops the release-notes entry.
  • 25ca6df changes the getPrimaryNodesConnectionMap() default to return getConnectionMap(). A custom provider that overrides only getConnectionMap() keeps its full map and no longer leaks a pooled connection.

I'll merge once CI is green

@ggivo
ggivo merged commit 7106a66 into redis:master Oct 20, 2026
20 of 22 checks passed
@ggivo

ggivo commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

@RaphaelFakhri
Thanks for the contribution! Merged now.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scanIteration on a cluster with replicas returns every key more than once

2 participants