pub enum Route {
Show 40 variants
Local,
Single(usize),
DelKeys,
ExistsKeys,
Dbsize,
Flush,
Save,
BgSave,
RewriteAof,
MSet,
Gather(MultiOp),
ZAlgebraStore(ZCombine),
BitOpStore,
Copy,
GeoStore {
src: Vec<u8>,
dst: Vec<u8>,
},
FeedRead,
FeedTail,
FeedShards,
PrefixStats,
ClientList,
ClientKill,
Extension,
ReplWait {
numreplicas: u32,
timeout_ms: u64,
},
ReplToken,
ReplBarrier {
offsets: Vec<u64>,
timeout_ms: u64,
miss: Vec<u8>,
},
Keys(Option<Vec<u8>>),
Scan(Result<ScanArgs, &'static str>),
RandomKey,
Subscribe,
Unsubscribe,
Psubscribe,
Punsubscribe,
Publish,
Watch,
Unwatch,
Hello,
Rename {
nx: bool,
},
ListMove {
from_left: bool,
to_left: bool,
},
Slowlog(SlowlogSub),
XReadGather {
streams: Vec<(Vec<u8>, Vec<u8>)>,
count: Option<usize>,
group: Option<XGroupCtx>,
},
}Expand description
How a command maps onto shards.
Variants§
Local
Keyless; execute on the connection’s own shard (e.g. PING).
Single(usize)
Single-key; route by args[idx].
DelKeys
args[1..] are keys; delete each on its shard, sum the counts.
ExistsKeys
args[1..] are keys; count existing across shards.
Dbsize
Sum every shard’s key count.
Flush
Flush every shard.
Save
Snapshot every shard’s store to disk, synchronously (SAVE —
blocks until durable, the Redis contract for the explicit form).
BgSave
BGSAVE — collect a COW view per shard and persist in the
background; the command returns once the views are frozen.
RewriteAof
BGREWRITEAOF — rebuild every shard’s AOF from in-memory state.
Each shard freezes a COW view and hands the dump to its persist
worker, so the reply returns before the rewrite is durable.
MSet
MSET — args[1..] are key/value pairs, routed per key’s shard.
Gather(MultiOp)
Cross-shard multi-key gather (MGET / SINTER / SUNION /
SDIFF / ZINTERCARD): each key’s payload is fetched on its
owning shard and the origin reduces them per crate::MultiOp.
ZAlgebraStore(ZCombine)
zset/set algebra *STORE family: gather sources, combine
per crate::message::ZCombine, materialize at args[1].
BitOpStore
BITOP op dst src [src …] — N sources gathered, combined, and
stored at a destination that sits at args[2], not args[1].
ZAlgebraStore is the same shape with a different payload: it
combines set and zset members, not raw bytes.
Carries nothing. An earlier draft carried the operator so the
router could pick it, which meant parsing the operator twice and
needing a fallback route for the argv the router could not parse
— and that fallback led to a dispatch table with no BITOP arm,
so a malformed BITOP would have been answered “unknown command”.
The route says only that this is a BITOP; every refusal is
worded once, in exec_bitop.
Why it cannot ride Single(1), in one assertion:
use kevy_rt::{Route, shard_of_key};
// `Single(1)` hashes args[1]. For BITOP that is the OPERATOR.
let operator = b"AND".as_slice();
let destination = b"dst".as_slice();
assert_ne!(shard_of_key(operator, 8, false), shard_of_key(destination, 8, false));
assert!(matches!(Route::BitOpStore, Route::BitOpStore));Copy
COPY src dst [REPLACE] — two keys, so the same hazard the
rename and list-move routes exist for: left to the catch-all
Single(1) the copy lands in the SOURCE’s shard, where no later
read of the destination will ever look. Same-shard pairs take
one atomic op; cross-shard pairs run Read → Put, and need no
rollback because the read does not remove anything.
Why it cannot ride Single(1), in one assertion:
use kevy_rt::{Route, shard_of_key};
// A pair of ordinary key names on an eight-shard server.
let (src, dst) = (b"ca".as_slice(), b"cb".as_slice());
assert_ne!(shard_of_key(src, 8, false), shard_of_key(dst, 8, false));
// `Single(1)` hashes args[1] — the SOURCE — and runs the whole
// command there, so the copy would land in a shard no later read
// of `dst` ever looks at, while the reply said it worked.
assert!(matches!(Route::Copy, Route::Copy));GeoStore
Geo *STORE family — GEOSEARCHSTORE dst src … and
GEORADIUS[BYMEMBER] src … STORE|STOREDIST dst.
These MUST be routed, not left to the catch-all Route::Single(1):
GEOSEARCHSTORE puts the DESTINATION at argv[1] (so the search then
read the source off the wrong shard — :0, or “could not decode
requested zset member” for FROMMEMBER) while GEORADIUS puts the
SOURCE there (so the destination was written into the source’s
shard, invisible to every later read of it). Both keys are carried
here because neither sits at a fixed argv index — the legacy forms
hide dst behind an option-soup scan.
The search runs on src’s shard (crate::Commands::geo_search),
the write lands on dst’s (Op::ZStoreResult) — see
[crate::exec_geostore].
Fields
FeedRead
FEED.READ <shard> <gen> <offset> … — shard-index routed.
FeedTail
FEED.TAIL <shard>.
FeedShards
FEED.SHARDS — answered locally.
PrefixStats
PREFIX.STATS <prefix> — all-shard fanout, summed.
ClientList
CLIENT LIST — all-shard fanout; each shard renders its conn
table rows, the origin concatenates into one bulk reply.
ClientKill
CLIENT KILL … — all-shard fanout; each shard closes its
matching conns, the origin sums (or maps the legacy positional
form to +OK / -ERR).
Extension
Extension fan-out (IDX.* reads): every shard runs
Commands::extension_op, the origin reduces.
ReplWait
WAIT numreplicas timeout — all-shard barrier: each
shard answers (possibly deferred until its replicas ACK or the
deadline) with how many of its replicas acked its
master_repl_offset at arm time; the origin replies the MIN.
timeout_ms == 0 = the Redis “wait forever” form (the runtime
hard-caps it — see exec_replwait::WAIT_HARD_CAP_MS).
Fields
ReplToken
REPL.TOKEN on a primary — gather every shard’s
(feed generation, next_offset) pair into one flat array.
ReplBarrier
REPL.WAIT on a replica — all-shard applied barrier:
shard i answers once its replication-apply position reaches
offsets[i] (or the deadline passes). All met → +OK; any
timeout → the pre-built miss reply (kevy sends
-MISDIRECTED writer is <primary>). The command layer builds
miss because the upstream address is its knowledge, not the
runtime’s.
Fields
Keys(Option<Vec<u8>>)
KEYS pattern — every shard returns its matching keys.
Scan(Result<ScanArgs, &'static str>)
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] — a real
cursor iterator: each call visits ~COUNT buckets of ONE shard
(chaining into the next shard only while the work budget lasts)
and replies [next-cursor, keys]. Err carries the pre-parsed
error message the command layer wants on the wire (invalid
cursor / syntax error) — the runtime replies it verbatim.
RandomKey
RANDOMKEY — one arbitrary key across all shards.
Subscribe
SUBSCRIBE / UNSUBSCRIBE — connection-level (modifies this conn).
Unsubscribe
The other half of the pair above: drops this conn’s channel subscriptions, all of them when no channel is named.
Psubscribe
PSUBSCRIBE pattern [pattern ...] / PUNSUBSCRIBE [pattern ...] —
like Subscribe/Unsubscribe but the conn registers Redis-glob
patterns; PUBLISH to a matching channel delivers a pmessage
frame. Connection-level (modifies this conn + shared pattern
registry).
Punsubscribe
The other half of the pattern pair: drops this conn’s pattern subscriptions, all of them when no pattern is named, and removes them from the shared registry.
Publish
PUBLISH channel message — delivered to subscribers on every core.
Watch
WATCH key [key ...] — fan-out to record per-shard versions, then
stash the (key, version) pairs in the conn’s watched set so the
next EXEC can validate them. Connection-level.
Unwatch
UNWATCH — clear the conn’s watched set. Connection-level, local.
Hello
HELLO [protover [AUTH user pass] [SETNAME name]] — server
handshake; on HELLO 3 flips the conn into RESP3 mode (per-conn
proto field). Reply shape itself is proto-aware (V2: array of
pairs; V3: Map). Connection-level, dispatch via the
crate::Commands::hello_reply hook so embedders set their own server
metadata.
Rename
RENAME source destination / RENAMENX source destination. The
runtime handles the two-shard decision: same-shard renames go
through one atomic crate::Store::rename on the owning shard; cross-
shard renames use the Take→Put orchestrator (lands in v2-3b;
v2-3a emits -CROSSSHARD ... for that case).
ListMove
RPOPLPUSH src dst / LMOVE src dst LEFT|RIGHT LEFT|RIGHT /
BRPOPLPUSH src dst timeout, once the blocking form has an element
to serve.
These MUST be routed, not left to Route::Single(1). The source and
the destination are different keys and can live on different shards;
the catch-all route hashes args[1] (the source), so the destination
push executed on the SOURCE’s shard and the element was written into
a keyspace nobody would ever read it from. It returned the moved
value, so the caller believed it had worked. Measured on an 8-shard
server: 11 of 12 moves silently lost the element.
Same-shard pairs are one atomic Op on the owning shard. Cross-shard
pairs run the Take→Push orchestrator (mirroring Self::Rename),
which is NOT atomic — see exec_listmove.
Fields
Slowlog(SlowlogSub)
SLOWLOG GET / LEN / RESET / HELP. The sub-command + parsed
args are pre-decoded at routing time so the runtime knows
whether to short-circuit (HELP / error) or fan out across
shards (GET / LEN / RESET). See crate::parse_slowlog_sub.
XReadGather
Non-blocking XREAD / XREADGROUP over multiple streams — fan
each stream out to its owning shard and merge the per-stream replies
in request order (single-stream forms still route via
Self::Single). Each element is (stream key, last-seen id);
count is the optional COUNT cap applied per stream; group
Some makes each per-shard sub-query an XREADGROUP (a write —
PEL / last-delivered updates happen on each stream’s owning shard
and are AOF-logged there as the rewritten single-stream command).
The command set builds this only for the non-blocking, ≥2-stream
forms; blocking reads park on the origin shard instead (see the
cross-shard BLOCK arbiter).