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

  1. 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.
  2. Fall back to per-key single-key operations (a loop of SET/GET) if the keys genuinely belong to different shards.
  3. 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

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


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/96c3282b1d7c462d.json. Report an issue: GitHub.