redis/redis-py · error · CrossSlotTransactionError

Cannot watch or send commands on different slots

Error message

Cannot watch or send commands on different slots

What it means

Raised in TransactionStrategy.execute_command (redis/cluster.py:4630) as CrossSlotTransactionError when a watched/immediate command in a transaction hashes to a slot different from those already in self._pipeline_slots. Redis Cluster transactions are atomic only within a single hash slot, so mixing slots during WATCH or immediate execution is rejected.

Source

Thrown at redis/cluster.py:4631

        if not self._transaction_connection:
            self._transaction_connection = get_connection(redis_node)

        return redis_node, self._transaction_connection

    def execute_command(self, *args, **kwargs):
        slot_number: Optional[int] = None
        if args[0] not in ClusterPipeline.NO_SLOTS_COMMANDS:
            slot_number = self._pipe.determine_slot(*args)

        if (
            self._watching or args[0] in self.IMMEDIATE_EXECUTE_COMMANDS
        ) and not self._explicit_transaction:
            if args[0] == "WATCH":
                self._validate_watch()

            if slot_number is not None:
                if self._pipeline_slots and slot_number not in self._pipeline_slots:
                    raise CrossSlotTransactionError(
                        "Cannot watch or send commands on different slots"
                    )

                self._pipeline_slots.add(slot_number)
            elif args[0] not in self.NO_SLOTS_COMMANDS:
                raise RedisClusterException(
                    f"Cannot identify slot number for command: {args[0]},"
                    "it cannot be triggered in a transaction"
                )

            return self._immediate_execute_command(*args, **kwargs)
        else:
            if slot_number is not None:
                self._pipeline_slots.add(slot_number)

            return self.pipeline_execute_command(*args, **kwargs)

    def _validate_watch(self):

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Put all keys of one transaction under a shared hash tag so they land in the same slot, e.g. 'user:{1}', 'order:{1}'.
  2. Split the operation into multiple single-slot transactions.
  3. Use a Lua script (via rc.eval) keyed on one slot to do multi-key logic atomically, again with a shared hash tag.
  4. Re-check your keys' slots with rc.cluster_keyslot(key) before issuing WATCH.

Example fix

# before
pipe = rc.pipeline(transaction=True)
pipe.watch('user:1')
pipe.multi()
pipe.set('order:1', 'x')   # different slot -> CrossSlotTransactionError
pipe.execute()

# after (shared hash tag forces one slot)
pipe = rc.pipeline(transaction=True)
pipe.watch('{acct}:user')
pipe.multi()
pipe.set('{acct}:order', 'x')
pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

from redis.cluster import RedisCluster

def assert_same_slot(client, keys):
    slots = {client.cluster_keyslot(k) for k in keys}
    if len(slots) != 1:
        raise ValueError(f'keys span multiple slots: {slots}; use a shared hash tag')

def watch_and_mutate(client, watch_key, ops):
    assert_same_slot(client, [watch_key] + [a[0] for a in ops])
    pipe = client.pipeline(transaction=True)
    pipe.watch(watch_key)
    pipe.multi()
    for args in ops:
        pipe.execute_command(*args)
    return pipe.execute()

Try / catch

from redis.exceptions import RedisClusterException

try:
    pipe.execute()
except RedisClusterException as e:
    if 'different slots' in str(e):
        # re-issue keys under a shared hash tag, e.g. '{tag}:k'
        ...
    else:
        raise

Prevention

When it happens

Trigger: pipe.watch('user:1'); pipe.get('order:9') where the two keys hash to different slots. Or any immediate-execute command (WATCH/UNWATCH or a command issued while _watching is True and no MULTI yet) whose slot is not the one already recorded.

Common situations: Keys naively named without a shared hash tag. Porting multi-key WATCH/transaction logic from standalone redis to cluster. Using natural keys that span logical entities (user vs order) in one transaction.

Related errors


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