Introduction
Working with Redis Cluster from application code requires understanding smart clients, hash tag strategies, and the limitations that sharding imposes on multi-key operations, pipelines, and Lua scripts.
Key Concepts
- Smart Client: A client that caches the slot-to-node mapping and routes commands directly to the correct node
- Hash Tag: The
{...}syntax that controls which part of the key is used for slot calculation - CROSSSLOT Error: Error returned when a multi-key command references keys in different slots
- Cluster Pipeline: A pipeline that groups commands by target node and sends them in parallel
Real World Context
Most Cluster problems in production stem from application-level issues: CROSSSLOT errors breaking transactions, pipelines silently degrading performance, or Lua scripts that worked on standalone Redis failing in Cluster. Understanding these constraints upfront prevents costly refactoring.
Deep Dive
Smart Client Architecture
A smart client maintains a local copy of the slot map:
- On startup, calls
CLUSTER SLOTSorCLUSTER SHARDSto build the map - For each command, computes
CRC16(key) % 16384to find the slot - Looks up the slot in the local map to find the target node
- Sends the command directly (no redirects needed)
- On MOVED error, refreshes the slot map
pythonfrom redis.cluster import RedisCluster # Smart client automatically handles slot routing rc = RedisCluster(host='127.0.0.1', port=7000) # These go to different nodes transparently rc.set('user:1', 'Alice') # Slot X -> Node A rc.set('user:2', 'Bob') # Slot Y -> Node B
Hash Tags In Depth
Hash tags let you control slot assignment:
redis# Without hash tags - different slots CLUSTER KEYSLOT user:1:profile # Slot 12539 CLUSTER KEYSLOT user:1:orders # Slot 9visually # With hash tags - same slot CLUSTER KEYSLOT {user:1}:profile # Slot 5649 CLUSTER KEYSLOT {user:1}:orders # Slot 5649
Rules:
- Only the first
{...}is used - Empty braces
{}means the whole key is hashed - Nested braces are not supported
Common patterns:
redis# Per-user data {user:123}:profile {user:123}:settings {user:123}:sessions # Per-tenant data {tenant:acme}:config {tenant:acme}:users
Pipeline Limitations
In standalone Redis, a pipeline sends all commands to one server. In Cluster, the client must split the pipeline by target node:
python# Cluster-aware pipeline (redis-py handles this automatically) pipe = rc.pipeline() pipe.set('{user:1}:name', 'Alice') pipe.set('{user:1}:age', '30') pipe.set('{user:2}:name', 'Bob') # Different node results = pipe.execute() # Internally: 2 commands to Node A, 1 command to Node B
Performance impact: Cross-node pipelines have higher latency than single-node pipelines because the client must wait for all nodes to respond.
Lua Scripting Constraints
Lua scripts in Cluster can only access keys in a single slot:
redis# Works: all keys share hash tag EVAL "return redis.call('GET', KEYS[1]) .. redis.call('GET', KEYS[2])" 2 {user:1}:name {user:1}:age # Fails: keys in different slots EVAL "return redis.call('GET', KEYS[1])" 1 user:1:name # If the script also accessed user:2:name -> CROSSSLOT error
Workaround: Pass all keys via hash-tagged KEYS parameters and design Lua scripts to operate on a single entity.
Transaction Constraints
MULTI/EXEC transactions only work when all watched/accessed keys are in the same slot:
redis# Works WATCH {user:1}:balance MULTI DECRBY {user:1}:balance 100 INCRBY {user:1}:spent 100 EXEC
Common Pitfalls
- Assuming pipelines are atomic in Cluster -- Unlike standalone Redis, cluster pipelines are split across nodes and can partially fail. Always check individual results.
- Hot slots from poor hash tag design -- If too many keys share the same hash tag (e.g.,
{global}:counter), one node handles disproportionate traffic. Distribute hash tags across multiple entities.
Best Practices
- Use entity-level hash tags -- Tag by user, tenant, or order ID rather than by category. This distributes load while keeping related data together.
- Test multi-key operations locally with cluster mode -- Run a local 6-node cluster during development to catch CROSSSLOT errors before production.
Summary
- Smart clients cache slot mappings and route commands directly to the correct node
- Hash tags control slot assignment; design them around entity boundaries
- Pipelines in cluster mode are split by node and are not atomic
- Lua scripts and transactions can only access keys within a single slot
- Test with cluster mode enabled during development to catch CROSSSLOT errors early
Code Examples
from redis.cluster import RedisCluster
rc = RedisCluster(host='127.0.0.1', port=7000)
# Hash-tagged keys go to same slot
rc.mset({'{user:1}:name': 'Alice', '{user:1}:age': '30'})
result = rc.mget('{user:1}:name', '{user:1}:age')
# Cluster pipeline (auto-split by node)
pipe = rc.pipeline()
pipe.set('{user:1}:score', '100')
pipe.set('{user:2}:score', '200')
results = pipe.execute()