Redis Cluster: Hash Slots and Data Distribution12 min read
Module A-9·12 min read
The 16,384 hash slot model, CRC16 key hashing, hash tags for co-locating keys on the same slot, MOVED vs ASK redirections, multi-key command constraints in Cluster, and cluster-enabled client configuration.
A-9 — Redis Cluster: Hash Slots and Data Distribution
Who this module is for: Your Redis dataset has grown beyond what a single node can hold in RAM, or your write throughput exceeds what a single node can handle. Redis Cluster is the answer — but it introduces constraints around multi-key operations, key routing, and command support that you must understand before adopting it. This module covers the Cluster model end-to-end.
Why Cluster?
A single Redis node has two hard limits:
RAM — the entire dataset must fit in one machine's memory. A 1TB dataset cannot fit on a 256GB node.
Write throughput — all writes go through one single-threaded event loop. At some point, you hit the wall (~1M ops/sec on modern hardware).
Redis Cluster solves both by distributing data across multiple primary nodes, each responsible for a subset of the keyspace.
The Hash Slot Model
Redis Cluster partitions the keyspace into 16,384 hash slots (numbered 0–16383). Every key belongs to exactly one slot:
slot = CRC16(key) % 16384
Hash slots are assigned to primary nodes. A 3-node cluster might assign:
text
When a client issues GET user:1001, Redis computes CRC16("user:1001") % 16384 = 7638. Slot 7638 belongs to Node 2. The client sends the GET to Node 2.
MOVED and ASK Redirections
Cluster clients must track which node owns which slots. When a client sends a command to the wrong node, the node responds with a MOVED redirection:
text
The client updates its local slot-to-node map and resends the command to the correct node. A well-implemented Cluster client handles this automatically and transparently — application code does not see MOVED errors.
ASK redirections are similar but temporary — used during slot migration (when a slot is being moved from one node to another). ASK means "ask this specific node for this specific command, but do not update your slot map."
text
Hash Tags: Controlling Key Placement
Multi-key commands (MGET, MSET, pipelines, Lua scripts, transactions) require all keys to be on the same node. In Cluster mode, this means all keys must be in the same hash slot.
Hash tags force a specific portion of the key to be used for slot computation:
text
text
With hash tags, all keys for a specific user share the same slot — enabling multi-key operations:
typescript
Design rule: For related keys that must be accessed together, use hash tags from the start. Retrofitting hash tags requires migrating all existing keys.
Multi-Key Command Constraints
Commands that operate on multiple keys fail in Cluster if the keys span different slots:
text
Workarounds:
Hash tags — co-locate related keys on the same slot
Application-side batching — split multi-key operations by slot, execute per-node, merge results in the application
Single-key alternatives — replace MGET with a pipeline of GET commands (each routed to the correct node automatically by the Cluster client)
typescript
Cluster Setup
A minimal production Cluster requires 6 nodes: 3 primaries + 3 replicas (one replica per primary).
bash
Cluster-Aware Client: ioredis.Cluster
typescript
Cluster Node Commands
text
text
cluster_state:fail means at least one slot has no reachable primary — the cluster is refusing some or all write commands.
Automatic Failover in Cluster
Each primary in a Cluster has one or more replicas. When a primary fails, its replicas detect the failure via gossip (covered in A-11) and one replica promotes itself after a timeout:
text
After failover, the new primary owns all slots previously owned by the failed node. If the failed primary had multiple replicas, only one promotes — the others continue replicating from the new primary.
If a primary fails with no available replica (the replica also failed), the cluster enters a cluster_state:fail state for those slots. No writes are accepted for the missing slots until the node recovers.
Limitations of Redis Cluster
No cross-slot operations: Multi-key commands must use hash tags. This is a design-time constraint — it requires forethought about which keys need to be co-located.
Limited database support: Cluster only uses database 0. The SELECT command is not available in Cluster.
Lua script constraints: EVAL scripts in Cluster must have all KEYS in the same slot. Multi-slot Lua scripts fail with CROSSSLOT. This limits some patterns that work fine on standalone Redis.
No blocking cross-node operations on multiple keys: a single-key BLPOP mylist is routed by any cluster-aware client to the exact node that owns mylist's slot, so a producer pushing to it and a consumer blocking on it always end up talking to the same node — that part works fine. The real constraint is multi-key blocking commands: BLPOP list1 list2 list3 fails outright with a CROSSSLOT error unless all three keys happen to hash to the same slot (which you'd normally force with hash tags, e.g. {orders}:list1). You can't blockingly wait across keys that live on different nodes.
Complexity: Cluster adds operational complexity — monitoring multiple nodes, handling resharding, managing replica assignments. For most workloads that fit within a single node's capacity, Sentinel (with a single large primary) is operationally simpler.
When to Use Cluster
Use Cluster when:
Dataset size exceeds available RAM on a single node
Write throughput exceeds ~500K ops/second on a single node
You need to shard a dataset too large for one node's memory across multiple nodes in the same region (Cluster is a single-region sharding mechanism, not a geo-distribution solution — see A-12 for what actual multi-region active-active requires)
Do not use Cluster when:
Your dataset fits on a single node — Sentinel is simpler
You rely heavily on multi-key commands without hash tag planning
Your application uses SELECT to separate databases (not supported in Cluster)
You use complex Lua scripts accessing multiple keys across slots
MOVED redirections: permanent reroute to correct node; ASK redirections: temporary reroute during slot migration
Hash tags {tag} force slot computation on the tag — co-locate related keys: {user:1001}:profile and {user:1001}:session share a slot
Multi-key commands (MGET, MSET, Lua EVAL) require all keys on the same slot — use hash tags or application-side batching
Minimal production Cluster: 3 primaries + 3 replicas (one replica per primary)
Automatic failover when a primary fails — replica promotes after cluster-node-timeout; cluster enters fail state if no replica available
ioredis.Cluster handles MOVED/ASK transparently; scaleReads: 'slave' routes reads to replicas
Cluster limitations: no cross-slot ops without hash tags, only database 0, higher operational complexity than Sentinel
Next: A-10 — Resharding, Node Addition, and Live Slot Migration — adding nodes to a running cluster, migrating slots with zero downtime, and the ASKING redirection that enables live traffic during migration.
Knowledge Check
An application frequently uses the MGET command to fetch a user's profile and session data simultaneously: MGET user:1001:profile user:1001:session. After migrating from a standalone Redis instance to Redis Cluster, this command starts throwing CROSSSLOT errors. What is the most idiomatic way to fix this issue?
When a Redis Cluster client sends a command to a node that does not own the hash slot for the requested key, what is the expected behavior?
What happens to a Redis Cluster if a primary node crashes and it has no replicas available to promote?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
GET user:1001 → sent to Node 1 (wrong node)
Node 1 → -MOVED 7638 10.0.1.52:6379
↑ slot ↑ correct node address
GET product:999 → slot being migrated
Node 1 → -ASK 12345 10.0.1.53:6379
Client: sends ASKING command to 10.0.1.53, then resends GET product:999
Note: ASKING is not stored; next request for slot 12345 still goes to Node 1
until migration completes and a MOVED is issued
CRC16(key) % 16384 → uses the full key
# With hash tag {tag}: only the portion inside {} is hashed
CRC16(tag) % 16384
user:1001:profile → CRC16("user:1001:profile") % 16384 = slot A
user:1001:session → CRC16("user:1001:session") % 16384 = slot B (different!)
{user:1001}:profile → CRC16("user:1001") % 16384 = slot X
{user:1001}:session → CRC16("user:1001") % 16384 = slot X (same slot!)
// Without hash tags: MGET fails in Cluster (keys on different slots)redis.mget('user:1001:profile','user:1001:session')// CROSSSLOT error// With hash tags: MGET works (all keys on the same slot)redis.mget('{user:1001}:profile','{user:1001}:session')// ✓
MGET key1 key2 key3 → CROSSSLOT error if keys are on different nodes
MSET key1 v key2 v → CROSSSLOT error
RPOPLPUSH list1 list2 → CROSSSLOT error
SUNIONSTORE dest src1 src2 → CROSSSLOT error if different slots
Lua scripts via EVAL → CROSSSLOT error if KEYS span different nodes
MULTI/EXEC transactions → keys must be on same slot
// Replace MGET with a pipeline in Cluster (each GET routed correctly)const pipeline = redis.pipeline();['user:1001','user:1002','user:1003'].forEach(key => pipeline.get(key));const results =await pipeline.exec();
# Start 6 Redis instances with cluster mode enabled# redis.conf for each:cluster-enabled yescluster-config-file nodes.conf
cluster-node-timeout 5000 → ms to declare a node down (default 15000)# Create the cluster (Redis 5.0+):redis-cli --cluster create \10.0.1.1:6379 10.0.1.2:6379 10.0.1.3:6379 \10.0.1.4:6379 10.0.1.5:6379 10.0.1.6:6379 \ --cluster-replicas 1# Output will show slot distribution and ask for confirmation
import{ Cluster }from'ioredis';const redis =newCluster([{ host:'10.0.1.1', port:6379},{ host:'10.0.1.2', port:6379},{ host:'10.0.1.3', port:6379},// You only need to list some nodes — the client discovers the full topology],{ redisOptions:{ password: process.env.REDIS_PASSWORD, commandTimeout:500,},clusterRetryStrategy:(times)=> Math.min(times *100,3000), scaleReads:'slave',// route reads to replicas});// Commands work transparently — MOVED redirects handled automaticallyawait redis.set('user:1001','data');await redis.get('user:1001');// Multi-key with hash tagsawait redis.mget('{user:1001}:profile','{user:1001}:session');
CLUSTER INFO → cluster state, slot coverage, etc.
CLUSTER NODES → full topology: node IDs, IPs, slots, roles
CLUSTER MYID → this node's ID
CLUSTER KEYSLOT key → compute the hash slot for a key
CLUSTER COUNTFAILUREREPORTS node-id
CLUSTER SLOTS → slot-to-node mapping (deprecated; use CLUSTER SHARDS in 7.0+)
CLUSTER SHARDS → (Redis 7.0+) slot ranges + node info
127.0.0.1:6379> CLUSTER INFO
cluster_enabled:1
cluster_state:ok → 'fail' means cluster is in a degraded state
cluster_slots_assigned:16384 → all slots assigned
cluster_slots_ok:16384
cluster_slots_pfail:0 → slots where primary is probably down
cluster_slots_fail:0 → slots where primary is confirmed down
cluster_known_nodes:6
cluster_size:3 → number of primaries
cluster_current_epoch:12
cluster-node-timeout 5000 → if no PING response for 5s, node is considered down
# Failover completes in approximately 1-2× cluster-node-timeout