An MGET over three keys that worked against a single Redis instance now fails with "CROSSSLOT Keys in request don't hash to the same slot" after a move to Redis Cluster. Explain the rule being enforced and the realistic ways to make that read work.
answer
- one command, one slot - node executes locally
- CROSSSLOT is permanent, not a redirect
- slot equality, not node equality, is checked
- hash tag / split-and-pipeline / one hash + HMGET
- client-side split loses atomicity
basics
~20 sIn cluster mode every key of a single command must live in one hash slot, because one node executes it locally with no cross-node coordination. Fix it by hash-tagging the keys into one slot, by issuing separate GETs pipelined per node, or by restructuring the data into one key.
solid answer
~60 sRedis Cluster executes each command entirely on the node that owns the slot; there is no distributed execution or two-phase coordination. So a multi-key command is only legal when **all** its keys hash to the same slot, and `MGET a b c` with keys in different slots is rejected up front with `CROSSSLOT`. Three honest options: 1. **Co-locate with hash tags** - rename to `{ctx}:a`, `{ctx}:b`, `{ctx}:c` so all three hash the same tag. Correct when the keys genuinely belong to one entity; dangerous if the tag is broad, because a slot cannot be split. 2. **Split client-side.** Send individual `GET`s, grouped per node and pipelined, so you still get roughly one round trip per node. Many clients (for example Lettuce) do this automatically for `MGET`/`MSET`/`DEL` - note it loses the single-command atomicity. 3. **Restructure.** If the fields always travel together, store them as one hash and use `HMGET`, which is a single-key command and always legal. The same rule governs `MSET`, `SINTERSTORE`, `RENAME`, `SMOVE`, `PFMERGE`, `BITOP`, `ZRANGESTORE` and friends.
code
text · 12 lines> MGET user:1:name user:1:email user:1:plan
(error) CROSSSLOT Keys in request don't hash to the same slot
# fix A: co-locate with a hash tag (narrow identity)
> MGET "user:{1}:name" "user:{1}:email" "user:{1}:plan"
1) "ada"
2) "[email protected]"
3) "pro"
# fix B: model it as one object - single-key command, legal anywhere
> HSET user:1 name ada email [email protected] plan pro
> HMGET user:1 name email plango deeper
State the rule - all keys of one command must be in one slot - and name the hash-tag fix.
List the affected command families, explain the slot-not-node subtlety, and know that client-side splitting trades atomicity for reach.
Drive the decision from requirements: atomic or not, co-location group bounded or not, and prefer restructuring over broad tags.
Treat it as a data-model boundary rather than an error to suppress: define which access paths need co-location, encode that as a documented key-naming contract, and watch for split-write partial-failure semantics.
## The rule Redis Cluster shards the keyspace into 16384 hash slots, each owned by one master. A command is dispatched to a single node and executed there against local memory only. There is no cross-node query engine, no distributed transaction, no scatter-gather in the server. Therefore any command that names more than one key is accepted **only if every one of those keys hashes to the same slot**. Otherwise the node answers: ``` (error) CROSSSLOT Keys in request don't hash to the same slot ``` Note what this is not. It is not a redirect and not a temporary condition - it will never succeed by retrying, because the keys are permanently in different slots. And the check is on the **slot**, not the node: two keys that happen to sit on the same master today but in different slots are still rejected, precisely so that a later slot migration cannot silently break the command. ## Which commands are affected Everything that takes multiple key arguments, including the ones people forget: - Batching: `MGET`, `MSET`, `MSETNX`, `DEL`/`UNLINK`/`EXISTS`/`TOUCH` with several keys. - Set and sorted-set algebra with a destination: `SINTERSTORE`, `SUNIONSTORE`, `SDIFFSTORE`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZDIFFSTORE`, `ZRANGESTORE`; also the non-storing `SINTER`, `SUNION`, `SDIFF`, `ZINTERCARD` over several keys. - Moves and copies: `RENAME`, `RENAMENX`, `COPY`, `SMOVE`, `LMOVE`, `BLMOVE`, `LMPOP`, `ZMPOP`, `BLMPOP`. - Merges and bit ops: `PFMERGE`, `PFCOUNT` over several keys, `BITOP`. - Reading several streams in one call: `XREAD`/`XREADGROUP` with multiple stream names. - `SORT` with `BY`/`GET` patterns that reference other keys, and `GEORADIUS ... STORE`. - `EVAL`/`FCALL` with keys in `KEYS` spanning slots, and multi-key `MULTI` blocks. ## Option 1: hash tags Rename the keys so they share a hash tag: `{order:77}:items`, `{order:77}:total`. Redis then hashes only `order:77`, so all of them land in one slot and the multi-key command becomes legal. This is the right answer when the keys are facets of one entity whose cardinality is high - millions of orders spread evenly over slots. It is the wrong answer when the tag is broad (`{tenant}`, `{prod}`), because a slot is indivisible: everything under one tag is pinned to one node forever, and resharding cannot relieve the resulting memory and CPU hotspot. Tag by the narrowest identity the operation actually spans. ## Option 2: split client-side For a *read* like `MGET`, atomicity is usually not required - you are fetching three independent values, and Redis gives you no snapshot isolation across commands anyway. So issue three `GET`s. The efficient shape is to group them by owning node using the client's cached slot map and pipeline each group: with a three-node cluster you pay three parallel round trips instead of one, not three sequential ones. Several mature clients do this transparently. Lettuce splits cross-slot `MGET`, `MSET` and `DEL` into per-node batches and reassembles the result in the caller's key order; redis-py's cluster client similarly fans out. Know whether *your* client does this, because the behavioural difference is important: the fan-out version is **not atomic**. Concurrent writers can change one key between the sub-requests, so you can observe a mix of old and new values. For a cache read that is almost always fine; for a read that must be self-consistent it is not, and you need option 1 or 3. Writes are more delicate. A split `MSET` can partially fail: some nodes accept, one is down, and you are left with half the batch applied and no rollback. Either make the write idempotent and retryable, or co-locate. ## Option 3: restructure the data Often the multi-key command is an artifact of a standalone-era schema. Three keys that are always read together are usually one object: store them as a hash and read with `HMGET`, which names one key and therefore works anywhere in the cluster. A set intersection over per-user sets can sometimes be replaced by a precomputed set maintained at write time. Restructuring removes the constraint instead of working around it, and it does not create slot skew. ## Choosing Ask two questions. *Does this operation need to be atomic or self-consistent?* If yes, co-location or a single-key structure is mandatory; splitting is not an option. *Is the co-location group bounded and numerous?* If yes, a hash tag is safe; if the group is unbounded (a tenant, an environment), prefer restructuring, because you would be trading a correctness error today for an unfixable hotspot later.
- Your client library transparently splits a cross-slot MGET into per-node requests. What have you given up?Atomicity and point-in-time consistency. The sub-requests execute independently, so a concurrent writer can modify one key after another sub-request has already read its old value, and you can observe a mix of versions. Partial failure is also possible for split writes: one node's batch can fail while others succeed, with no rollback.
- Two keys currently live on the same master node but in different slots. Will a multi-key command over them succeed?No. The check is on slot equality, not node co-residence, and it fails with CROSSSLOT. This is intentional: if the command were allowed, a later slot migration would move one key away and silently break an operation that had been working.
saying these in an interview costs you the question
- Treating CROSSSLOT as transient and adding retries
- Assuming keys on the same node can be used together even in different slots
- Slapping a broad hash tag like {app} or {tenant} on everything to make the errors disappear
- Believing a client-side split preserves the atomicity of MGET or MSET
- Thinking Redis Cluster will internally scatter-gather the request across nodes