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 as CrossSlotTransactionError in TransactionStrategy.execute_command (redis/cluster.py:4684) during WATCH or immediate execution when the new command's slot differs from the slot(s) already pinned in _pipeline_slots. Redis Cluster transactions must operate on a single hash slot, so mixing slots is rejected.

Solutions

  1. Design keys to share a hash tag so they map to one slot: 'user:{tx1}', 'order:{tx1}'.
  2. Split the transaction into per-slot transactions if keys cannot share a tag.
  3. Use the non-transactional pipeline if cross-key atomicity is not actually required.

Example fix

// before
with rc.pipeline(transaction=True) as pipe:
    pipe.watch('user:1')
    pipe.watch('order:2')
// after
with rc.pipeline(transaction=True) as pipe:
    pipe.watch('user:{tx1}')
    pipe.watch('order:{tx1}')
Defensive patterns

Strategy: validation

Validate before calling

from redis.cluster import key_slot
def same_slot(keys):
    slots = {key_slot(k.encode()) for k in keys}
    return len(slots) == 1
keys = ['user:{tx1}', 'order:{tx1}']
assert same_slot(keys)

Type guard

from redis.cluster import key_slot
def keys_share_slot(keys) -> bool:
    return len({key_slot(k.encode() if isinstance(k,str) else k) for k in keys}) == 1

Try / catch

from redis.exceptions import RedisClusterException
try:
    pipe.execute()
except RedisClusterException as e:
    if 'different slots' in str(e):
        # redesign keys with a shared hash tag and retry

Prevention

When it happens

Trigger: In a transactional pipeline, watching or executing commands on keys that hash to different slots, e.g. pipe.watch('user:1') then pipe.watch('order:2') where the keys lack a shared hash tag.

Common situations: Optimistic-locking patterns ported from standalone Redis that watch unrelated keys; multi-key transactions without hash-tag design.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/84275e5ee37583e7. Report an issue: GitHub.

Appendix: source

Thrown at redis/cluster.py:4684

        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._resolve_transaction_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 6a6b581b48)