redis/redis-py · error · RedisClusterException

unlinking multiple keys is not implemented in pipeline comma

Error message

unlinking multiple keys is not implemented in pipeline command

What it means

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.

Source

Thrown at redis/asyncio/cluster.py:3053

    async def watch(self, *names):
        raise RedisClusterException(
            "method watch() is not supported outside of transactional context"
        )

    async def unwatch(self):
        raise RedisClusterException(
            "method unwatch() is not supported outside of transactional context"
        )

    async def discard(self):
        raise RedisClusterException(
            "method discard() is not supported outside of transactional context"
        )

    async def unlink(self, *names):
        if len(names) != 1:
            raise RedisClusterException(
                "unlinking multiple keys is not implemented in pipeline command"
            )

        return self.execute_command("UNLINK", names[0])


class TransactionStrategy(AbstractStrategy):
    NO_SLOTS_COMMANDS = {"UNWATCH"}
    IMMEDIATE_EXECUTE_COMMANDS = {"WATCH", "UNWATCH"}
    UNWATCH_COMMANDS = {"DISCARD", "EXEC", "UNWATCH"}
    SLOT_REDIRECT_ERRORS = (AskError, MovedError)
    CONNECTION_ERRORS = (
        ConnectionError,
        OSError,
        ClusterDownError,
        SlotNotCoveredError,
    )

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Queue one unlink per key: for k in keys: pipe.unlink(k). The pipeline will batch them per shard automatically.
  2. 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.
  3. For bulk cross-cluster delete outside a pipeline, call rc.delete(*keys) which the client fans out per slot.

Example fix

// before
pipe = rc.pipeline()
pipe.unlink('k1', 'k2', 'k3')  # raises
await pipe.execute()

// after
pipe = rc.pipeline()
for k in ['k1', 'k2', 'k3']:
    pipe.unlink(k)
await pipe.execute()
Defensive patterns

Strategy: validation

Validate before calling

def unlink_many_pipeline(pipe, keys):
    for k in keys:
        pipe.unlink(k)  # one key per command; pipeline batches per shard
    return pipe

# usage
pipe = rc.pipeline()
unlink_many_pipeline(pipe, ['k1', 'k2', 'k3'])
await pipe.execute()

Type guard

from typing import Iterable

def is_single_key(names) -> bool:
    if isinstance(names, str):
        return True
    if isinstance(names, Iterable):
        return len(list(names)) == 1
    return False

Try / catch

from redis.cluster import RedisClusterException

try:
    pipe.unlink('k1', 'k2', 'k3')
    await pipe.execute()
except RedisClusterException as e:
    if 'unlinking multiple keys' in str(e):
        for k in ['k1', 'k2', 'k3']:
            pipe.unlink(k)
        await pipe.execute()
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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