Skip to content

Skip COMMAND GETKEYS for movablekeys commands with complete key specs - #4605

Merged
petyaslavova merged 13 commits into
redis:masterfrom
patrick-immometrica:perf-cluster-movablekeys-specs
Nov 10, 2026
Merged

petyaslavova merged 13 commits into
redis:masterfrom
patrick-immometrica:perf-cluster-movablekeys-specs

Conversation

@patrick-immometrica

@patrick-immometrica patrick-immometrica commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Description of change

For a movablekeys command such as ZUNIONSTORE, ZINTER, LMPOP, BZMPOP or SINTERCARD, CommandsParser.get_keys() sends COMMAND GETKEYS to the default node. The cluster client only needs the keys to pick a slot, so every one of these commands costs an extra round trip first. A ClusterPipeline pays that round trip once per queued command.

The key specs we already cache from COMMAND say where the keys are. get_keys() now reads them the same way the server does, handling index begin_search with range or keynum find_keys. It still asks the server when a spec is incomplete or uses keyword/unknown, which covers XREAD, SORT, MIGRATE and GEORADIUS. The helper sits in the shared base class, so the async parser uses it too.

get_keys() per call against a local server, so this is just the loopback round trip. Over a real network the gap is larger:

command before after
ZUNIONSTORE 267 µs 5.6 µs
LMPOP 154 µs 2.2 µs
SINTERCARD 134 µs 2.2 µs

I checked the result against COMMAND GETKEYS for about 25 commands on RESP2 and RESP3, with and without decode_responses, and got no mismatches. One behaviour change: keys found this way are returned as the caller passed them, the same as the fixed-position path already does, rather than in the server's encoding. I adjusted two assertions in test_get_moveable_keys for that.

Pull Request check-list

  • 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

Medium Risk
Changes cluster slot routing for movablekeys commands; incorrect spec compilation could mis-route before MOVED redirects, though tests and GETKEYS fallback mitigate that.

Overview
Cluster routing for movablekeys commands (ZUNIONSTORE, LMPOP, SINTERCARD, etc.) no longer always pays a COMMAND GETKEYS round trip: CommandsParser / AsyncCommandsParser compile key specs from COMMAND on initialize(), and get_keys() resolves keys locally via the new _get_keys_from_specs path (range/keynum, server-style arity and numkeys validation). COMMAND GETKEYS remains the fallback for incomplete specs, unsupported searches, or malformed argument counts.

Keys returned from the spec path keep the caller’s argument types (strings/bytes), matching fixed-position extraction rather than the server’s encoding. key_slot() now accepts memoryview keys. Docs and tests were extended for sync/async parsers and cluster commentary.

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

The cluster client sent COMMAND GETKEYS before every movablekeys command
(ZUNIONSTORE, LMPOP, SINTERCARD, ...) just to find the slot. The key
specs from COMMAND already describe where the keys are; read them
locally and only ask the server when a spec is incomplete or uses a
keyword search (XREAD, SORT, MIGRATE, GEORADIUS).
@patrick-immometrica
patrick-immometrica marked this pull request as ready for review November 9, 2026 13:01
@chatgpt-codex-connector

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

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T16:02:12.147196Z 3ce6eb6 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: 4813005920

ℹ️ 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/_parsers/commands.py Resolved

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

Comment thread redis/_parsers/commands.py Resolved

@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: 262c4576fb

ℹ️ 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/_parsers/commands.py Resolved

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

Hey @patrick-immometrica, thank you for your contribution!

Skipping the extra round trip for movablekeys commands is a good improvement, and I would like to merge it. A few changes are needed first:

  1. Match the server for the two spec flags it treats specially. COMMAND GETKEYS skips specs flagged not_key, and it does not use the specs when one is flagged variable_flags. Please skip not_key specs and return None (ask the server) on variable_flags. The existing _key_spec_flags helper in the same file already reads the flags in both RESP2 and RESP3 shapes, so please reuse it.
  2. This runs for every cluster command with movable keys, so please parse the specs once in initialize() (both parsers) instead of on every call. A per-command tuple, or a marker meaning "ask the server", is enough. The args[0].split() copy can go too, because get_keys already splits multi-word command names.
  3. Please add the async counterparts of the two new tests in tests/test_asyncio/test_command_parser.py.
  4. Please update the text that still says these keys come from COMMAND GETKEYS: the CommandsParser and AsyncCommandsParser docstrings, docs/resp3_features.rst, and AGENTS.md.

@petyaslavova petyaslavova added maintenance Maintenance (CI, Releases, etc) waiting-for-response labels Nov 9, 2026
Parse the key specs of every movablekeys command when the parser
initializes, for both the sync and the async parser, instead of on every
get_keys() call. Match COMMAND GETKEYS for the two flags it treats
specially: specs flagged not_key are skipped, and a command with a spec
flagged variable_flags is left to the server. The args[0].split() copy is
gone, since get_keys() already splits multi-word command names.

Add the async counterparts of the key-spec tests, and update the parser
docstrings, docs/resp3_features.rst and AGENTS.md.

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

Thanks for the quick update. All four points are addressed, and the compiled specs match the server's key-spec handling.

One small thing remains: a few comments still say the cluster client finds movablekeys keys through COMMAND GETKEYS. Please update the comment above _CACHEABLE_KEYED in redis/commands/metadata.py, the slot_routed_args and TestWithheldRoutingPolicies docstrings in tests/test_command_metadata.py, and the TestStaticMetadataRouting docstrings in tests/test_cluster.py and tests/test_asyncio/test_cluster.py. After that, this is good to go once CI passes.

@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: 76666b6e0b

ℹ️ 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/_parsers/commands.py Resolved
int() accepts "+2", " 2", "02", "2_0" and the float 2.5, which the
server rejects, so the spec path routed commands the server would refuse
as malformed. Only a count in its canonical form is read locally now.

@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: 5bdcf53569

ℹ️ 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/_parsers/commands.py Resolved
Comment thread redis/_parsers/commands.py Resolved
A str key count is checked after encoding, since a non-ASCII client
encoding changes the bytes the server parses. A keynum or range spec
whose last key falls before the first now yields no keys, as on the
server, instead of a slice that wraps around.

@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: 95bb27a2e1

ℹ️ 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/_parsers/commands.py Resolved
Comment thread redis/_parsers/commands.py Resolved
The server checks the argument count before it reads key specs, so a
call below or off the command's arity no longer gets keys from the
specs. A key count with more than 19 digits is out of range for the
server and made int() raise past 4300 digits; it now falls back too.

@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: 57350b6857

ℹ️ 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/_parsers/commands.py Resolved

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

Thanks for the follow-up commits. The comment updates are in, and the new count, arity and range checks match how the server reads key specs.

One small change before I merge: _get_keys_from_specs reads and casts self.commands[cmd_name]["arity"] on every call, but the arity is already an int and never changes per command. Please store it with the compiled specs in _compile_movablekeys_specs (for example as (arity, specs)), so the hot path skips the extra lookup.

@patrick-immometrica

Copy link
Copy Markdown
Contributor Author

Done, _compile_movablekeys_specs now stores (arity, specs) and the hot path just unpacks it.

@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: 1971410eaa

ℹ️ 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/_parsers/commands.py Resolved

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

Thanks, the arity is now compiled with the specs and the hot path only unpacks it. CI is green. Approving.

@petyaslavova
petyaslavova merged commit a5916d7 into redis:master Nov 10, 2026
132 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenance Maintenance (CI, Releases, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants