redis/redis-py · error · RedisClusterException
{command} - all keys must map to the same key slot
Error message
{command} - all keys must map to the same key slot What it means
Raised by _determine_slot() when a multi-key command's keys hash to more than one slot. Redis Cluster only permits multi-key operations (MSET, SUNIONSTORE, pipeline of writes, etc.) when every key lives in the same slot. The library computes keyslot() for each key and rejects the command client-side if the set of slots has length > 1, because the server would reject it with a CROSSSLOT error anyway.
Source
Thrown at redis/asyncio/cluster.py:1027
# FCALL can call a function with 0 keys, that means the function
# can be run on any node so we can just return a random slot
if command.upper() in ("FCALL", "FCALL_RO"):
return random.randrange(0, REDIS_CLUSTER_HASH_SLOTS)
raise RedisClusterException(
"No way to dispatch this command to Redis Cluster. "
"Missing key.\nYou can execute the command by specifying "
f"target nodes.\nCommand: {args}"
)
# single key command
if len(keys) == 1:
return self.keyslot(keys[0])
# multi-key command; we need to make sure all keys are mapped to
# the same slot
slots = {self.keyslot(key) for key in keys}
if len(slots) != 1:
raise RedisClusterException(
f"{command} - all keys must map to the same key slot"
)
return slots.pop()
def _is_node_flag(self, target_nodes: Any) -> bool:
return isinstance(target_nodes, str) and target_nodes in self.node_flags
def _parse_target_nodes(self, target_nodes: Any) -> List["ClusterNode"]:
if isinstance(target_nodes, list):
nodes = target_nodes
elif isinstance(target_nodes, ClusterNode):
# Supports passing a single ClusterNode as a variable
nodes = [target_nodes]
elif isinstance(target_nodes, dict):
# Supports dictionaries of the format {node_name: node}.
# It enables to execute commands with multi nodes as follows:
# rc.cluster_save_config(rc.get_primaries())View on GitHub (pinned to da03cdc7e8)
Solutions
- Use Redis hash tags so all keys share a slot: name keys as '{user:1000}:name', '{user:1000}:email' — the substring inside {} is what gets hashed.
- Fall back to per-key single-key operations (a loop of SET/GET) if the keys genuinely belong to different shards.
- For LOAD or pipeline fan-out, ensure each grouped command's keys share a slot or split the group by slot before dispatch.
Example fix
// before
await rc.mset({'k1': 1, 'k2': 2})
// after
await rc.mset({'{tag}:k1': 1, '{tag}:k2': 2}) Defensive patterns
Strategy: validation
Validate before calling
from redis.asyncio.cluster import RedisCluster
def assert_same_slot(rc: RedisCluster, *keys: str) -> None:
slots = {rc.keyslot(k) for k in keys}
if len(slots) != 1:
raise ValueError(
f'Keys {keys} map to multiple slots {slots}. Use a shared hash tag '
f'e.g. "{{tag}}:k1".'
)
# usage before a multi-key op
assert_same_slot(rc, 'k1', 'k2')
await rc.mset({'k1': 1, 'k2': 2}) Type guard
from redis.asyncio.cluster import RedisCluster
def keys_share_slot(rc: RedisCluster, keys: list[str]) -> bool:
if not keys:
return True
first = rc.keyslot(keys[0])
return all(rc.keyslot(k) == first for k in keys[1:]) Try / catch
from redis.cluster import RedisClusterException
try:
await rc.mset(mapping)
except RedisClusterException as e:
if 'same key slot' in str(e):
# fall back to per-key writes across slots
async with rc.pipeline() as p:
for k, v in mapping.items():
p.set(k, v)
await p.execute()
else:
raise Prevention
- Design key names with a shared hash tag ({entity_id}:field) when they must be operated on together.
- Validate multi-key command inputs with keyslot() before dispatch in test harnesses.
- For bulk loads, partition keys by slot and group writes accordingly.
When it happens
Trigger: rc.mset({'k1':1,'k2':2}) where k1 and k2 hash to different slots; rc.smove('src','dst',v) with untagged keys; EVAL scripts that touch keys in different slots; any command touching >1 key without shared hash tags.
Common situations: Treating a cluster like standalone Redis and using cross-slot multi-key commands; migrating data with bulk MSET/DELETE; keys naturally named without a common {tag} prefix.
Related errors
- unlinking multiple keys is not implemented in pipeline comma
- Cannot execute FT.CURSOR commands without FT.AGGREGATE
- Invalid args in command: {command, *args}
- No way to dispatch this command to Redis Cluster. Missing ke
- target_nodes type can be one of the following: node_flag (PR
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/96c3282b1d7c462d.json.
Report an issue: GitHub.