Redis Cluster returns `CROSSSLOT Keys in request don't hash to the same slot` when you attempt a multi-key operation (like MGET, MSET, transactions, or SUNION) on keys that hash to different hash slots. Redis Cluster partitions data across 16384 slots, and multi-key commands require all keys to map to the same slot unless you use hash tags. This guide explains slot hashing, hash tags, and how to fix key design issues.
Redis Cluster divides the key space into 16384 hash slots, each assigned to a specific master node. When you issue a command that involves multiple keys—such as `MGET`, `MSET`, `MULTI/EXEC`, `SUNION`, or `DEL`—Redis must ensure all those keys belong to the same hash slot; otherwise, the operation cannot be performed atomically on a single node. The error "CROSSSLOT Keys in request don't hash to the same slot" appears when the client library or Redis itself detects that the keys involved in a multi‑key command hash to different slots. This is a fundamental limitation of Redis Cluster: operations cannot span multiple nodes in a single atomic transaction. To work around this limitation, you can either: 1. Use **hash tags** (curly braces `{}` inside the key name) to force keys to the same slot, 2. Restructure your data so that related keys naturally hash together, 3. Use client‑side routing to send each key to its correct node individually, or 4. Avoid multi‑key commands altogether in favor of single‑key operations.
Use redis-cli to check which slot each key belongs to:
# Connect to any Redis Cluster node
redis-cli -c -h <host> -p <port>
# Check slot for individual keys
CLUSTER KEYSLOT user:123:profile
CLUSTER KEYSLOT user:123:sessions
CLUSTER KEYSLOT session:456:data
# Example output:
# (integer) 650
# (integer) 8734
# (integer) 200If the slot numbers differ, those keys cannot be used together in a multi‑key command unless you employ hash tags.
Redis allows you to wrap a portion of the key in curly braces {}; only the text inside the braces is used for slot calculation:
# Without hash tags – different slots
CLUSTER KEYSLOT user:123:profile # slot 7592
CLUSTER KEYSLOT user:123:sessions # slot 10411
# With hash tags – same slot
CLUSTER KEYSLOT user:{123}:profile # slot 3300 (based on '123')
CLUSTER KEYSLOT user:{123}:sessions # slot 3300 (based on '123')Update your application to use hash tags for related keys:
// Instead of (will cause CROSSSLOT)
const keys = ['user:123:profile', 'user:123:sessions'];
redis.mget(...keys);
// Use hash tags (all hash to same slot)
const keys = ['user:{123}:profile', 'user:{123}:sessions'];
redis.mget(...keys); // Works!Now MGET, MSET, and other multi‑key commands will work because both keys hash to slot 3300.
When using MULTI/EXEC transactions, all keys accessed within the transaction must hash to the same slot:
// Without hash tags – fails with CROSSSLOT
redis.multi()
.get('user:123:name')
.get('session:456:token')
.exec();
// With hash tags – all keys in same slot
redis.multi()
.get('user:{123}:name')
.get('session:{123}:token')
.exec();Apply hash tags consistently across your transaction to ensure atomicity.
If hash tags are not feasible, break multi‑key commands into single‑key operations routed to the correct node:
// Instead of MGET that fails with CROSSSLOT
redis.mget('key1', 'key2', 'key3'); // Fails if keys in different slots
// Use individual GET calls
const values = await Promise.all([
redis.get('key1'),
redis.get('key2'),
redis.get('key3')
]);For deletions and set operations:
# Instead of DEL key1 key2 key3 (fails if in different slots)
DEL key1
DEL key2
DEL key3Most Redis Cluster clients (ioredis, redis-py-cluster, node-redis) handle this routing automatically when you use single‑key commands.
For better performance, batch keys by their hash slot before executing multi-key operations:
const redis = require('ioredis');
const cluster = new redis.Cluster([...nodes]);
// Get multiple values efficiently by grouping by slot
const keys = ['user:{1}:name', 'user:{1}:email', 'user:{2}:name', 'user:{2}:email'];
// Group by hash tag (all user:{1} keys go together)
const groupedBySlot = groupByHashTag(keys);
const results = {};
for (const [tag, keyGroup] of Object.entries(groupedBySlot)) {
const values = await cluster.mget(...keyGroup);
results[tag] = values;
}This approach reduces network round‑trips while respecting slot boundaries.
Modern Redis clients automatically handle slot routing. Verify your client's cluster support:
// ioredis example – automatically handles cluster routing
const Redis = require('ioredis');
const cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: 7000 },
{ host: '127.0.0.1', port: 7001 },
]);
// Use hash tags for multi-key commands
await cluster.mget('user:{1}:name', 'user:{1}:email');# redis-py-cluster example
from rediscluster import RedisCluster
startup_nodes = [
{'host': '127.0.0.1', 'port': 7000},
{'host': '127.0.0.1', 'port': 7001},
]
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)
# Use hash tags
rc.mget('user:{1}:name', 'user:{1}:email')These clients maintain a slot‑to‑node map and handle key routing transparently.
Redis Cluster's slot‑hashing algorithm is CRC16 modulo 16384. The same algorithm is used consistently for all keys. Hash tags work by extracting the substring between the first { and the next } (or end of string if no closing brace) and hashing only that substring.
Performance considerations: Splitting a 100‑key MGET into 100 individual GET requests increases network round‑trips significantly. If you cannot use hash tags, consider batching keys by slot on the client side and issuing separate multi‑key commands per slot to balance consistency and performance.
Data skew warning: Aggressive hash tagging can cause uneven data distribution. For example, using {user}:profile, {user}:session, {user}:settings for all users puts all user data in one slot, potentially causing that slot to become a bottleneck. Use hash tags strategically based on your access patterns.
Migration strategy: When moving from standalone Redis to Redis Cluster, audit all multi‑key commands (MGET, MSET, SUNION, SINTER, RPOPLPUSH, BRPOPLPUSH, EVAL, etc.), transactions, and pipelined requests. Tools like redis‑cli --cluster check can help identify cross‑slot violations before going live.
Redis Enterprise workaround: Redis Enterprise has built-in support for some cross-slot commands (MGET, MSET, DEL, UNLINK, EXISTS, TOUCH), allowing them to work across slots without hash tags. Open-source Redis Cluster does not support this.
ERR Unbalanced XREAD list of streams
How to fix "ERR Unbalanced XREAD list" in Redis
ERR syntax error
How to fix "ERR syntax error" in Redis
ConnectionError: Error while reading from socket
ConnectionError: Error while reading from socket in redis-py
ERR unknown command
How to fix ERR unknown command in Redis
Command timed out
How to fix 'Command timed out' in ioredis