If you've used standalone Redis and then switched to Redis Cluster, you may have encountered this error:
CROSSSLOT Keys in request don't hash to the same slot
Why does the same code work locally but fail in Redis Cluster?
Let's understand how Redis distributes keys and why certain operations require keys to share the same hash slot.
1. What Are Redis Hash Slots?
Standalone Redis stores all keys on a single server. Redis Cluster distributes them across multiple primary nodes using 16,384 hash slots.
Each key is assigned a slot using:
HASH_SLOT = CRC16(key) % 16384
For example (illustrative slot numbers):
Redis Cluster
|
+-- Node A (Slots 0-5460)
| +-- user:1
|
+-- Node B (Slots 5461-10922)
| +-- user:2
|
+-- Node C (Slots 10923-16383)
+-- user:3
Why use hash slots instead of assigning keys directly to nodes?
Scalability and rebalancing.
When adding or removing nodes, Redis can migrate hash slots between nodes without recalculating every key's placement.
A slot can contain millions or even billions of keys, subject to memory and Redis keyspace limits. However, one slot always belongs to one primary node at a time.
2. Why Does CROSSSLOT Happen?
Consider a Lua script that increments a topic's score while preventing repeated increments using a cooldown key.
if redis.call('EXISTS', KEYS[2]) == 1 then
return 0
end
redis.call('ZINCRBY', KEYS[1], 1, ARGV[1])
redis.call('SET', KEYS[2], '1', 'EX', 30)
return 1
With these keys:
KEYS[1] = app:topics:ranking
KEYS[2] = app:topics:cooldown:123
Both keys may belong to different hash slots.
Redis Cluster cannot execute a Lua script atomically across multiple slots, so it rejects the operation with CROSSSLOT.
Solution: Hash Tags
Redis supports hash tags using {}.
When a key contains a valid hash tag, Redis hashes only the content inside the braces.
app:{topics}:ranking
app:{topics}:cooldown:123
Both keys now hash using topics, guaranteeing the same slot.
You can verify this:
CLUSTER KEYSLOT app:{topics}:ranking
CLUSTER KEYSLOT app:{topics}:cooldown:123
Both return the same slot number.
No changes to the Lua script are necessary.
3. What About MULTI/EXEC and Pipelines?
Redis Cluster has different restrictions depending on the operation.
| Operation | Different hash slots allowed? | Atomic? |
|---|---|---|
Independent GET / SET
|
Yes | Individually |
MGET / MSET
|
No | Yes |
MULTI/EXEC |
No | Yes |
EVAL (Lua) |
No | Yes |
ioredis cluster.pipeline()
|
Only if slots share a node | No |
| ioredis automatic pipelining | Yes, across nodes | No |
MULTI/EXEC
Transactions require all keys to share the same slot.
// May fail with CROSSSLOT
await redis
.multi()
.set('user:1', 'John')
.set('user:2', 'Jane')
.exec();
Using hash tags:
await redis
.multi()
.set('{users}:1', 'John')
.set('{users}:2', 'Jane')
.exec();
Pipeline
Unlike transactions, pipelines are not atomic. They reduce network round trips by batching commands.
With ioredis, an explicit cluster pipeline requires its commands to target slots served by the same node.
const pipeline = redis.pipeline();
pipeline.set('user:1', 'John');
pipeline.set('user:2', 'Jane');
await pipeline.exec(); // May fail across nodes
For independent operations across nodes, use automatic pipelining:
const redis = new Redis.Cluster(nodes, {
enableAutoPipelining: true,
});
await Promise.all([
redis.set('user:1', 'John'),
redis.set('user:2', 'Jane'),
]);
ioredis automatically groups eligible commands by destination node.
4. Be Careful With Hash Tags
Hash tags solve CROSSSLOT, but overusing them can create performance bottlenecks.
Consider:
app:{topics}:ranking
app:{topics}:cooldown:1
app:{topics}:cooldown:2
app:{topics}:cooldown:3
...
app:{topics}:cooldown:1000000
All these keys belong to one hash slot, meaning one primary node handles them all.
Even if you add more Redis nodes, Redis cannot distribute a single slot across multiple primaries.
For user-specific operations, a better design is:
user:{123}:profile
user:{123}:settings
user:{456}:profile
user:{456}:settings
Each user's related keys share a slot, while different users can be distributed across the cluster.
If you need an atomic operation involving one global key and many user-specific keys, you'll need to accept that shared-slot trade-off or redesign the operation.
5. Why Doesn't This Happen With Local Redis?
Standalone Redis doesn't enforce cluster hash-slot restrictions because all keys are stored on one server.
However, a local Redis Cluster with only one primary node still enforces hash-slot rules, even though all 16,384 slots belong to the same node.
If production uses Redis Cluster, consider testing against cluster mode locally to catch these problems early.
Conclusion
Redis Cluster uses 16,384 hash slots to distribute keys and simplify scaling.
Remember:
- Hash slots determine where keys are stored.
- Lua scripts and MULTI/EXEC require all keys to share the same slot.
-
Hash tags
{}guarantee that related keys share a slot. - Pipelines improve performance but are not atomic.
- Overusing hash tags can concentrate traffic on a single node.
The next time you encounter CROSSSLOT, don't just add {} to every key. Consider which operations truly require atomicity and how your key design affects scalability.
Top comments (0)