If you have ever spun up a Redis Cluster, you probably ran into a strange, highly specific hardcoded number: 16,384.
No matter if you configure a tiny cluster of 3 nodes or a massive enterprise deployment of 500 nodes, the total number of Redis hash slots remains exactly the same.
Why 16,384? Why not 65,536 (2¹⁶), which is the standard boundary for almost every other distributed database, network protocol, and sharding architecture on earth?
To understand why Salvatore Sanfilippo (antirez), the creator of Redis, chose this specific number, we have to look past the surface-level documentation. We have to map out a technical journey from a single server to a decentralized mesh network, looking directly at the math, the memory structs, and the harsh realities of network physics.
Act 1: The Humble Beginning (The Single Instance)
Imagine you are building a new application. You need a fast database, so you spin up a single instance of Redis on a single cloud server.
At this stage, life is simple. To your application, Redis is a giant, super-fast dictionary in memory. You connect to that one server, and you start saving data:
[Your App] ----> ( Redis Server: 10.0.0.1 )
└── "user:101" -> { name: "Alice" }
└── "user:102" -> { name: "Bob" }
Every time you write a key-value pair, it lands in the exact same memory pool. When you ask for "user:101", Redis looks inside its internal memory table, grabs the value, and hands it right back.
Act 2: The Success Wall (When One Server Isn't Enough)
Your application goes viral. Suddenly, you aren't storing thousands of keys; you are storing hundreds of millions of keys.
You hit a physical wall. Your Redis server runs out of RAM. You upgrade the server to a bigger instance (Vertical Scaling), but eventually, you hit the ultimate ceiling: you cannot buy a single machine with enough RAM or CPU power to handle your traffic.
You have to Scale Horizontally. You need to split your massive dictionary across multiple independent Redis servers (Nodes).
┌──> [ Node 1 (10.0.0.1) ] -> Stores keys A to I
[Your App] ────┼──> [ Node 2 (10.0.0.2) ] -> Stores keys J to R
└──> [ Node 3 (10.0.0.3) ] -> Stores keys S to Z
This is where things get messy. If you have 3 different servers, how does your app know which server holds "user:101"? If you guess wrong, you waste time jumping from server to server. We need a predictable way to evenly distribute keys across any number of servers.
Act 3: The Naive Solution (Modulo Hashing)
To solve the distribution problem with technical precision, your first instinct might be to use traditional Modulo Hashing.
When your application wants to store a key, it passes the string through a hashing algorithm to turn it into a consistent, predictable integer. Then, you use the modulo operator (%) against the total number of Redis nodes you currently own ($N$).
$$\text{Node Index} = \text{Hash}(\text{key}) \pmod N$$
If you have $N = 3$ nodes:
- $\text{Hash}(\text{"user:101"}) = 125,482 \implies 125,482 \pmod 3 = 1 \longrightarrow \textbf{Node 1}$
- $\text{Hash}(\text{"user:102"}) = 984,311 \implies 984,311 \pmod 3 = 2 \longrightarrow \textbf{Node 2}$
The Fatal Flaw: The Resharding Catastrophe
This math works flawlessly—until your traffic doubles again, and you need to add a 4th node ($N = 4$).
Suddenly, the denominator in your modulo operation changes from 3 to 4. Let's recalculate:
- $\text{Hash}(\text{"user:101"}) = 125,482 \implies 125,482 \pmod 4 = 2 \longrightarrow \textbf{Node 2}$ (Was Node 1)
- $\text{Hash}(\text{"user:102"}) = 984,311 \implies 984,311 \pmod 4 = 3 \longrightarrow \textbf{Node 3}$ (Was Node 2)
By changing $N$ from 3 to 4, up to 75% of your existing keys instantly map to the wrong node. Your application will suffer a massive cache miss storm. To fix this, you would have to take the database offline and move terabytes of data around just to align them with the new math. This is a production nightmare.
Act 4: Abstracting the Infrastructure (The Invention of Hash Slots)
Redis avoids this catastrophic remapping by decoupling your keys from the physical servers. It introduces a mathematical abstraction layer called Hash Slots.
Instead of hashing a key directly to a node, Redis hashes every key into a fixed pool of exactly 16,384 logical slots. This mapping never changes, no matter how many physical servers you add or remove.
Any Key ---> CRC16(Key) % 16384 ---> Hash Slot (0 to 16383)
The physical nodes do not own keys anymore; they own blocks of slots.
How the Fixed Pie is Sliced
Scenario A: The 4-Node Cluster
If you deploy a cluster with 4 master nodes, Redis divides the 16,384 slots evenly among them. Each node gets a massive chunk:
- Node 1: Owns Slots 0 to 4,095 (4,096 slots)
- Node 2: Owns Slots 4,096 to 8,191 (4,096 slots)
- Node 3: Owns Slots 8,192 to 12,287 (4,096 slots)
- Node 4: Owns Slots 12,288 to 16,383 (4,096 slots)
Scenario B: The 100-Node Cluster
If your application grows massively and you spin up 100 master nodes, the total number of slots is still 16,384. Redis divides the pie into much smaller pieces:
[16,384 \text{ slots} \div 100 \text{ nodes} \approx 163 \text{ slots per node}]
- Node 1: Owns Slots 0 to 162
- Node 2: Owns Slots 163 to 325
- ...
- Node 100: Owns Slots 16,221 to 16,383
Now, when you add Node 101, you don't rewrite the hashing math. You simply instruct the existing 100 nodes to hand over 1 or 2 slots to the newcomer. Only the keys residing within those specific moved slots need to be transferred across the network. The remaining 99% of your cluster’s data remains completely untouched and online.
Act 5: What Exactly is inside a Hash Slot?
A common point of confusion is what a slot actually looks like under the hood. A slot does not contain your data, your text, or your values. Instead, a slot contains a list of references (pointers) to the memory locations of your keys.
Inside a single Redis node, there are two distinct layers of dictionaries (hash tables) holding everything together:
-
The Global Keyspace Dictionary (
db->dict): This is the master dictionary for the node. The key is your actual string (like"user:101"), and the value is the actual data object stored in RAM. -
The Cluster Slots Dictionary (
cluster->slots): This is an array of exactly 16,384 entries managed by the node. Each active slot in this array points to a smaller, dedicated sub-dictionary tracking the keys belonging to it.
[Slot 4500] ───> Points to a list of keys: ["user:101", "user:999", "session:abc"]
│
└─► (Redis then looks up "user:101" in the master table to find the actual data)
Because you have millions of unique keys but only 16,384 slots, multiple keys are mathematically guaranteed to land in the same slot.
Exploding Slots and Hash Tags
While Redis's internal hash tables handle millions of keys per slot effortlessly, you can run into a Hot Slot bottleneck if you aren't careful. Since all keys in a slot live on one physical node, a sudden spike in traffic to keys sharing a slot can peg that single node’s CPU to 100% while the rest of your cluster sits idle.
However, you can also leverage this behavior to your advantage using Hash Tags. If you put part of a key inside curly braces { }, Redis will only hash what is inside the braces:
-
user:{101}:profile$\longrightarrow$ Redis only hashes"101"$\longrightarrow$ Slot 7312 -
user:{101}:orders$\longrightarrow$ Redis only hashes"101"$\longrightarrow$ Slot 7312
By forcing these different keys into the exact same slot, you guarantee they will live on the exact same physical node. This is the only way Redis allows you to run multi-key operations (like transactions) in a clustered environment.
Act 6: The Smart Client (Navigating the Slot Map)
How does your application actually talk to this setup without wasting time? If your application had to query a random node every time just to ask where a slot lives, your network latency would double. Redis Cluster solves this by pushing the intelligence out to the client library (like Jedis, redis-py, or ioredis).
1. The Initial Handshake
When your application boots up, the Redis client library connects to just one known node in the cluster and executes a special command: CLUSTER SLOTS. The node responds with the complete, current routing table of the cluster, which the client caches locally in your application's memory.
2. Zero-Latency Routing
When your code executes redis.get("user:101"), the client library intercepts the call locally inside your app's process:
- It runs the key through the hashing math:
CRC16("user:101") % 16384. - The result is Slot 4500.
- It checks its local cache, sees that Slot 4500 belongs to Node A (
10.0.0.1), and routes the TCP request directly to Node A. Under normal operations, your app achieves O(1) routing lookup.
3. Handling the MOVED Redirection
What happens if an administrator is actively moving Slot 4500 from Node A to Node B while your app is running? Your client’s local cache becomes outdated.
Your client sends the request for "user:101" to Node A. Node A looks at its internal memory and realizes it just transferred that slot to Node B. Instead of processing the command, Node A fires back a specific error:
-MOVED 4500 10.0.0.2:6379
The client library automatically catches this -MOVED error behind the scenes, updates its local memory cache to note that Slot 4500 now lives at 10.0.0.2, and transparently re-runs the request against Node B.
Act 7: The Architect's Compromise (Why 16,384?)
This brings us to our ultimate architectural riddle: If the total number of slots is a fixed pool, why choose 16,384? Why not make the fixed pool 65,536 (2¹⁶)?
The answer lies in the harsh realities of decentralized networks and the internal mechanics of the Redis Gossip Protocol.
1. The Cost of the Wire (The 2KB vs 8KB Bitmap)
Redis Cluster operates without a centralized orchestrator (like ZooKeeper). To detect dead nodes and handle failovers, every node must constantly broadcast its state to other nodes via heartbeat packets.
Inside every single heartbeat packet, a node must tell the rest of the cluster exactly which slots it currently owns out of that fixed pool. It does this using a raw binary checklist (a bitmap), where a 1 means "I own this slot" and a 0 means "I don't."
Inside the Redis source code, this state tracking looks like this:
struct clusterNode {
char slots[CLUSTER_SLOTS/8]; // The raw configuration bitmap
// ... other node metadata like IP, port, flags
};
Let's look at how the math changes based on the size of our fixed pool:
- With a 16,384 fixed pool: Each node sends a checklist that is 16,384 bits long. This requires exactly $16,384 \div 8 \text{ bits/byte} = \mathbf{2,048\text{ bytes (2KB)}}$ of data inside the header of every single packet.
- With a 65,536 fixed pool: Each node would have to send a checklist that is 65,536 bits long. The packet header size for every single heartbeat immediately boots to $65,536 \div 8 \text{ bits/byte} = \mathbf{8,192\text{ bytes (8KB)}}$.
2. The Fallacy of Compression
You might ask: “Why not just use 65,536 slots and compress the 8KB bitmap before sending it?”
In a pristine, newly launched cluster, a node's slots are perfectly contiguous (e.g., Node 1 owns slots 0 to 655). A bitmap with a solid block of 1s followed by all 0s compresses beautifully down to a few bytes.
However, in a mature production cluster that has undergone months of scaling, sharding, and node failovers, slots get heavily fragmented. Node 1 might end up owning slots 5, 22, 109, 455, 1200, and so on. Mathematically, a highly fragmented bit array behaves like random noise and cannot be compressed efficiently. The CPU overhead required to constantly compress and decompress this data on Redis's single-threaded architecture would severely bottleneck throughput, and the packet would still hit the network at close to its full 8KB size.
3. The Math of a Gossip Storm
A 6KB difference feels trivial on a single web request. But Redis nodes execute their cluster cron loop every 100 milliseconds (10 times a second) to shuffle gossip packets across the mesh.
Let's calculate the network traffic generated purely by background heartbeats in a standard cluster of 200 nodes:
- Total Packets: If each of the 200 nodes sends just 1 baseline ping per 100ms, that is 10 pings per node per second. That's $200 \times 10 = 2,000$ PING packets per second sent cluster-wide. Since every PING requires a corresponding PONG response, the total volume hits 4,000 packets per second.
- The 2KB Setup (16,384 slots): Including gossip metadata, a heartbeat packet sits at roughly 2.5KB. $$4,000 \text{ packets/sec} \times 2.5\text{KB} = \mathbf{\sim 10 \text{ MB/s of background noise}}$$
- The 8KB Setup (65,536 slots): If we change the bitmap to 8KB, the packet size swells to roughly 8.5KB. $$4,000 \text{ packets/sec} \times 8.5\text{KB} = \mathbf{\sim 34 \text{ MB/s of background noise}}$$
In a healthy cluster, 34 MB/s is manageable. But networks are never perfectly healthy. Imagine a minor network switch blip occurs, causing packets to drop for a few seconds.
Suddenly, all 200 nodes realize they haven't heard from their peers. Their "Urgent Check" loops trigger. Instead of pinging 1 node every 100ms, every node aggressively flings forced PINGs to all 199 other nodes simultaneously to figure out who is alive.
At 2KB, the network spike hits ~40 MB/s. The infrastructure absorbs it effortlessly, nodes hear back from each other, and the cluster stabilizes.
At 8KB, the network spike violently jumps to Over 160 MB/s (1.3 Gbps) of pure background chatter. This background surge can completely saturate the network interface cards (NICs) of smaller VM instances. Because the network is saturated with heartbeat bitmaps, your actual client requests (GET and SET commands) get queued up, causing massive latency spikes. Worse, because the heartbeats themselves get dropped in the traffic jam, nodes will falsely assume their healthy peers are dead, triggering a chaotic chain-reaction of accidental master failovers.
4. The 1,000-Node Pragmatic Limit
Sanfilippo designed Redis Cluster with a strict architectural ceiling: the system is not intended to scale beyond 1,000 master nodes.
Let's look at the math at that extreme limit:
- At 1,000 master nodes, a 16,384 slot pool means each node manages roughly 16 slots. This provides more than enough granularity for smooth, incremental data rebalancing.
- You would only need a 65,536 slot pool if you planned to scale to 10,000+ master nodes (so each node could have at least a few slots). But at 10,000 nodes, the $O(N^2)$ Gossip protocol would completely collapse the network under a tidal wave of heartbeat packets anyway.
Conclusion
Choosing 16,384 was the ultimate engineering compromise. It provided enough slots to cleanly distribute data across a realistic maximum of 1,000 nodes, while ensuring that the mandatory, uncompressible gossip bitmaps stayed at a lightweight 2KB—protecting the cluster from killing its own network bandwidth when things go wrong.
Top comments (1)
Redis’s 16,384‑slot limit is basically an admission that gossip traffic behaves like fluid dynamics; push the bitmap any higher, and the cluster cavitates. Love this, keep it up!