{"id":"9cefd59b7f148014","repo":"redis/redis-py","slug":"all-keys-involved-in-a-cluster-transaction-must-ma","errorCode":null,"errorMessage":"All keys involved in a cluster transaction must map to the same slot","messagePattern":"All keys involved in a cluster transaction must map to the same slot","errorType":"exception","errorClass":"CrossSlotTransactionError","httpStatus":null,"severity":"error","filePath":"redis/asyncio/cluster.py","lineNumber":3346,"sourceCode":"\n        return await self._execute_transaction_with_retries(stack, raise_on_error)\n\n    async def _execute_transaction_with_retries(\n        self, stack: List[\"PipelineCommand\"], raise_on_error: bool\n    ):\n        return await self._retry.call_with_retry(\n            lambda: self._execute_transaction(stack, raise_on_error),\n            lambda error, failure_count: self._reinitialize_on_error(\n                error, failure_count\n            ),\n            with_failure_count=True,\n        )\n\n    async def _execute_transaction(\n        self, stack: List[\"PipelineCommand\"], raise_on_error: bool\n    ):\n        if len(self._pipeline_slots) > 1:\n            raise CrossSlotTransactionError(\n                \"All keys involved in a cluster transaction must map to the same slot\"\n            )\n\n        self._executing = True\n\n        redis_node, connection = self._get_client_and_connection_for_transaction()\n        # Only disconnect if not watching - disconnecting would lose WATCH state\n        if not self._watching:\n            await redis_node.disconnect_if_needed(connection)\n\n        # Ensure fieldsets referenced by buffered HIMPORT SETs are prepared on this\n        # node's connection before the MULTI/EXEC block (session state, not\n        # transactional). All keys share one slot here, so it is a single node.\n        await redis_node._himport_prepare_pipeline(connection, stack)\n\n        stack = chain(\n            [PipelineCommand(0, \"MULTI\")],\n            stack,","sourceCodeStart":3328,"sourceCodeEnd":3364,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L3328-L3364","documentation":"Raised as CrossSlotTransactionError at the top of _execute_transaction when len(self._pipeline_slots) > 1. The buffered (deferred) branch of _execute_command collects every queued command's slot; at execute time, if more than one distinct slot is present, the library cannot wrap them in a single MULTI/EXEC (which is single-node in Redis Cluster).","triggerScenarios":"Queueing several deferred commands (the default pipeline path before execute()) whose keys hash to different slots, then calling execute() with transaction=True on a cluster pipeline. Example: pipe.set('a',1); pipe.set('b',2); await pipe.execute() with 'a' and 'b' in different slots.","commonSituations":"Treating a cluster pipeline like a standalone pipeline and batching unrelated keys; forgetting hash tags for keys that must be transacted together.","solutions":["Add a shared hash tag to all keys in the transaction so they map to one slot (e.g. 'user:{42}', 'acct:{42}').","Split the work into multiple single-slot transactions, one per slot.","If atomicity is not required, use the non-transactional cluster pipeline (client.pipeline(transaction=False)) which fans out across slots."],"exampleFix":"// before\npipe = client.pipeline(transaction=True)\nawait pipe.set('user:1', 'a')\nawait pipe.set('user:2', 'b')  # different slot\nawait pipe.execute()  # raises [86]\n// after\npipe = client.pipeline(transaction=True)\nawait pipe.set('user:{1}', 'a')\nawait pipe.set('acct:{1}', 'b')   # same slot via tag\nawait pipe.execute()","handlingStrategy":"validation","validationCode":"from redis.cluster import key_slot\nfrom collections import defaultdict\nbuckets = defaultdict(list)\nfor k, v in mapping.items():\n    buckets[key_slot(k.encode())].append((k, v))\n# then one transaction per slot, or use hash tags to collapse to one","typeGuard":null,"tryCatchPattern":"from redis.exceptions import CrossSlotTransactionError\ntry:\n    await pipe.execute()\nexcept CrossSlotTransactionError:\n    # split commands by slot and execute one transaction per slot","preventionTips":["Add a shared hash tag to keys that must be transacted together.","Validate single-slot before calling execute() on a transactional pipeline."],"tags":["cluster","pipeline","transaction","cross-slot","hash-tag"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}