redis/redis-py · error · CrossSlotTransactionError

All keys involved in a cluster transaction must map to the…

Error message

All keys involved in a cluster transaction must map to the same slot

What it means

CrossSlotTransactionError raised at the top of TransactionStrategy._execute_transaction (redis/asyncio/cluster.py:3377) when the accumulated pipeline slots contain more than one distinct slot. Redis Cluster guarantees atomicity only within a single slot, so a MULTI/EXEC block spanning multiple slots is impossible. This fires at execute() time after all commands were queued.

Solutions

  1. Group keys by hash slot using hash tags, e.g. 'k{group1}', so all keys share a slot
  2. Split into several single-slot transactions
  3. If atomicity is not required, use the non-atomic cluster pipeline strategy

Example fix

// before
pipe.multi()
pipe.set('user:1', 'a')
pipe.set('account:2', 'b')
await pipe.execute()
// after - same slot via shared hash tag
pipe.multi()
pipe.set('user:{group1}', 'a')
pipe.set('account:{group1}', 'b')
await pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

from redis.cluster import key_slot
slots = {key_slot(k.encode()) for k in keys}
if len(slots) > 1:
    raise ValueError('transaction keys span multiple slots; use hash tags or split')

Try / catch

try:
    await pipe.execute()
except CrossSlotTransactionError:
    # rebucket with hash tags or split into per-slot transactions

Prevention

When it happens

Trigger: Queueing `pipe.set('k1', '1'); pipe.set('k2', '2'); await pipe.execute()` inside a transaction where k1 and k2 map to different slots.

Common situations: Porting a standalone-Redis transactional pipeline to cluster without hash tags; aggregating unrelated keys in one transaction.

Related errors


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

Appendix: source

Thrown at redis/asyncio/cluster.py:3378

        return await self._execute_transaction_with_retries(stack, raise_on_error)

    async def _execute_transaction_with_retries(
        self, stack: List["PipelineCommand"], raise_on_error: bool
    ):
        return await self._retry.call_with_retry(
            lambda: self._execute_transaction(stack, raise_on_error),
            lambda error, failure_count: self._reinitialize_on_error(
                error, failure_count
            ),
            with_failure_count=True,
        )

    async def _execute_transaction(
        self, stack: List["PipelineCommand"], raise_on_error: bool
    ):
        if len(self._pipeline_slots) > 1:
            raise CrossSlotTransactionError(
                "All keys involved in a cluster transaction must map to the same slot"
            )

        self._executing = True

        redis_node, connection = self._get_client_and_connection_for_transaction()
        # Only disconnect if not watching - disconnecting would lose WATCH state
        if not self._watching:
            await redis_node.disconnect_if_needed(connection)

        # Ensure fieldsets referenced by buffered HIMPORT SETs are prepared on this
        # node's connection before the MULTI/EXEC block (session state, not
        # transactional). All keys share one slot here, so it is a single node.
        await redis_node._himport_prepare_pipeline(connection, stack)

        stack = chain(
            [PipelineCommand(0, "MULTI")],
            stack,

View on GitHub (pinned to 6a6b581b48)