Skip to content

Executing READONLY keyless commands on non-readonly replicas #2991

Description

@yanamolo

As of today, when establishing a connection to a replica without readonly mode, it is redirecting all commands to the primary node by returning MOVED error.
But keyless commands (which marked as READONLY) do get executed on the replica, even if those type of command extract key information, and can return stale data.
This creates inconsistent behavior: some operations fail over to the primary, while others proceed on the replica with potentially outdated information.
Example output:

127.0.0.1:6379> readwrite
OK
127.0.0.1:6379> get a
(error) MOVED 15495 172.19.117.208:6380
127.0.0.1:6379> hgetall p3
(error) MOVED 3689 172.19.117.208:6380
127.0.0.1:6379> FT.SEARCH idx "*=>[KNN 1 @description $query_vector]" PARAMS 2 query_vector "\xcd\xccL?\x00\x00\x00\x00\x00\x00\x00\x00" DIALECT 2
 1) (integer) 1
 2) "p3"
 3) 1) "__description_score"
    2) "0.0399999953806"
    3) "description"
    4) "\x00\x00\x80?\x00\x00\x00\x00\x00\x00\x00\x00"
127.0.0.1:6379> scan 0
1) "0"
2) 1) "p3"
   2) "p2"
   3) "p1"
   4) "p5"
   5) "a"
   6) "p4"

This example shows that running hgetall on a key returns MOVED error, but running ft.search (which is a valkey-search command) returns that same value.
Also SCAN command worked and returned information about the keys.

I believe those type of commands needs to be treated the same as other readonly commands that include keys.


Proposed Solution

The proposed solution is a new opt-in client capability: CLIENT CAPA redirect-keyless.
When a client declares this capability, keyless read commands (those marked CMD_READONLY with no key specs and not CMD_MOVABLE_KEYS) on a replica will return a -MOVED redirect to the primary, consistent with how keyed reads behave. If the replica is READONLY, keyless reads continue to execute locally on the replica, since the client has explicitly opted into replica reads.
This capability is independent of the existing CLIENT CAPA redirect and requires no changes to COMMAND INFO metadata or client-side routing tables — the server handles the redirect decision entirely. Non-read keyless commands (e.g., PING, INFO) are unaffected.


Affected client libraries

Here is a summary of how clients currently handle -REDIRECT error both in CMD and CME:

  ┌──────────────────┬────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────┬─────────────────────────────────────────────────────┐              
  │ Client           │ Sends `CLIENT CAPA redirect` in CMD?   │ Handles `-REDIRECT` in CMD?                                                        │ Sends `CLIENT CAPA redirect` in CME? │ Handles `-REDIRECT` in CME?                         │              
  ├──────────────────┼────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────┼─────────────────────────────────────────────────────┤              
  │ **valkey-java**  │ Yes (by default)                       │ Only with explicit `RedirectCommandExecutor` setup; default `JedisPooled` does not │ Yes (by default)                     │ Yes — retries on target node, no slot cache refresh │              
  │ **valkey-go**    │ Yes (when `EnableRedirect=true`)       │ Yes — swaps primary connection                                                     │ No (never sent in CME)               │ No — not checked in cluster path                    │              
  │ **valkey-glide** │ No                                     │ No                                                                                 │ No                                   │ No                                                  │              
  │ **valkey-py**    │ Yes (when `client_capa_redirect=True`) │ No — parsed as `RedirectError` but never caught                                    │ No (filtered out in cluster mode)    │ No — uncaught exception                             │              
  │ **iovalkey**     │ No                                     │ No                                                                                 │ No                                   │ No                                                  │              
  │ **libvalkey**    │ No                                     │ No                                                                                 │ No                                   │ No                                                  │              
  └──────────────────┴────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────┴─────────────────────────────────────────────────────┘             

Draft solution: #3505

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Needs Review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions