{"id":"dc9131344e52002c","repo":"redis/redis-py","slug":"unlinking-multiple-keys-is-not-implemented-in-pipe","errorCode":null,"errorMessage":"unlinking multiple keys is not implemented in pipeline command","messagePattern":"unlinking multiple keys is not implemented in pipeline command","errorType":"exception","errorClass":"RedisClusterException","httpStatus":null,"severity":"error","filePath":"redis/asyncio/cluster.py","lineNumber":3053,"sourceCode":"\n    async def watch(self, *names):\n        raise RedisClusterException(\n            \"method watch() is not supported outside of transactional context\"\n        )\n\n    async def unwatch(self):\n        raise RedisClusterException(\n            \"method unwatch() is not supported outside of transactional context\"\n        )\n\n    async def discard(self):\n        raise RedisClusterException(\n            \"method discard() is not supported outside of transactional context\"\n        )\n\n    async def unlink(self, *names):\n        if len(names) != 1:\n            raise RedisClusterException(\n                \"unlinking multiple keys is not implemented in pipeline command\"\n            )\n\n        return self.execute_command(\"UNLINK\", names[0])\n\n\nclass TransactionStrategy(AbstractStrategy):\n    NO_SLOTS_COMMANDS = {\"UNWATCH\"}\n    IMMEDIATE_EXECUTE_COMMANDS = {\"WATCH\", \"UNWATCH\"}\n    UNWATCH_COMMANDS = {\"DISCARD\", \"EXEC\", \"UNWATCH\"}\n    SLOT_REDIRECT_ERRORS = (AskError, MovedError)\n    CONNECTION_ERRORS = (\n        ConnectionError,\n        OSError,\n        ClusterDownError,\n        SlotNotCoveredError,\n    )\n","sourceCodeStart":3035,"sourceCodeEnd":3071,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/cluster.py#L3035-L3071","documentation":"Raised by PipelineStrategy.unlink() when more than one key is passed. Cluster pipelines route each command to a single node; UNLINK with multiple cross-slot keys cannot be placed on one shard, and the cluster pipeline does not auto-split multi-key UNLINK the way standalone Redis does. To avoid a CROSSSLOT failure the library limits pipeline unlink to exactly one key.","triggerScenarios":"Calling pipe.unlink('k1', 'k2') or pipe.unlink(['k1', 'k2']) in a cluster pipeline where len(names) != 1. The check at cluster.py:3052 rejects any call with zero or more-than-one keys.","commonSituations":"Porting standalone bulk-delete code (pipe.unlink(*many_keys)) to a cluster pipeline; deleting a batch of keys that span slots; cleanup routines that unlink variable-length key lists.","solutions":["Queue one unlink per key: for k in keys: pipe.unlink(k). The pipeline will batch them per shard automatically.","Use a single-key UNLINK if all keys share a hash tag and you concat them — but the pipeline API still requires one key per call, so loop.","For bulk cross-cluster delete outside a pipeline, call rc.delete(*keys) which the client fans out per slot."],"exampleFix":"// before\npipe = rc.pipeline()\npipe.unlink('k1', 'k2', 'k3')  # raises\nawait pipe.execute()\n\n// after\npipe = rc.pipeline()\nfor k in ['k1', 'k2', 'k3']:\n    pipe.unlink(k)\nawait pipe.execute()","handlingStrategy":"validation","validationCode":"def unlink_many_pipeline(pipe, keys):\n    for k in keys:\n        pipe.unlink(k)  # one key per command; pipeline batches per shard\n    return pipe\n\n# usage\npipe = rc.pipeline()\nunlink_many_pipeline(pipe, ['k1', 'k2', 'k3'])\nawait pipe.execute()","typeGuard":"from typing import Iterable\n\ndef is_single_key(names) -> bool:\n    if isinstance(names, str):\n        return True\n    if isinstance(names, Iterable):\n        return len(list(names)) == 1\n    return False","tryCatchPattern":"from redis.cluster import RedisClusterException\n\ntry:\n    pipe.unlink('k1', 'k2', 'k3')\n    await pipe.execute()\nexcept RedisClusterException as e:\n    if 'unlinking multiple keys' in str(e):\n        for k in ['k1', 'k2', 'k3']:\n            pipe.unlink(k)\n        await pipe.execute()\n    else:\n        raise","preventionTips":["Queue one unlink per key in cluster pipelines rather than batching keys in one call.","For cross-cluster bulk delete outside a pipeline, use rc.delete(*keys) which fans out by slot.","Wrap pipeline unlink helpers to enforce the single-key invariant."],"tags":["redis-cluster","pipeline","unlink","multi-key","cross-slot"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}