Skip to main content

Route

Enum Route 

Source
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

MSETargs[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

§src: Vec<u8>

Key the search reads — its shard runs the query.

§dst: Vec<u8>

Key the result is written to — its shard takes the write, which is why both keys have to be extracted before routing.

§

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

§numreplicas: u32

How many replicas the caller wants acked. Reported per shard; the origin answers the minimum across them.

§timeout_ms: u64

Deadline in milliseconds. 0 is Redis’s wait-forever form and is hard-capped by the runtime rather than honoured literally.

§

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

§offsets: Vec<u64>

One target apply-position per shard, indexed by shard number.

§timeout_ms: u64

Deadline in milliseconds for every shard to reach its target.

§miss: Vec<u8>

The reply to send if any shard misses its deadline, pre-built by the command layer because it names the upstream primary — the runtime does not know that address.

§

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).

Fields

§nx: bool

true for RENAMENX (no overwrite — reply :0 if dst exists).

§

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

§from_left: bool

Pop from the head of the source (LMOVE ... LEFT ...) rather than the tail (RPOPLPUSH).

§to_left: bool

Push onto the head of the destination (RPOPLPUSH, LMOVE ... LEFT) rather than the tail.

§

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).

Fields

§streams: Vec<(Vec<u8>, Vec<u8>)>

(stream key, start id) per stream, already paired — the wire form lists all keys and then all ids, which is not routable.

§count: Option<usize>

COUNT, applied per stream rather than across the gather.

§group: Option<XGroupCtx>

Some turns each per-shard sub-query into an XREADGROUP, which makes it a write: the PEL update happens on the stream’s own shard and is logged there.

Trait Implementations§

Source§

impl Debug for Route

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Route

Source§

fn eq(&self, other: &Route) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Route

Auto Trait Implementations§

§

impl Freeze for Route

§

impl RefUnwindSafe for Route

§

impl Send for Route

§

impl Sync for Route

§

impl Unpin for Route

§

impl UnsafeUnpin for Route

§

impl UnwindSafe for Route

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.