Distributed Caching: Sharding, Redis Cluster and Hot Keys

Sharding a cache across nodes: modulo vs consistent hashing, Redis Cluster hash slots and hash tags, client vs proxy routing, and hot key fixes.

Intermediate 4 min read · Updated

A single Redis node with 64 GB of RAM serves small and medium workloads well. When the working set or the request rate outgrows one machine, you need several cache nodes, and the central question becomes: which node holds which key? A distributed cache splits the key space across nodes so that, to the application, it still behaves like one big cache.

Why shard the cache

  • Memory limit. One machine has finite RAM. A 200 GB working set can't fit on one node; spread over 4 nodes it's 50 GB each.
  • Throughput limit. One Redis node handles roughly 100,000–200,000 operations a second, depending on value size, command type and hardware. If the workload needs more, you must shard even if memory would fit.

Sharding gives each node a subset of the keys. For each key, the client works out which node owns it, sends the request there and gets the answer.

Hash-based routing (modulo)

The simplest scheme: hash the key and take the remainder by the number of nodes.

node = hash(key) % num_nodes
= 2Keyproduct:99hash(key) % 4Node 0Node 1Node 2Node 3
Modulo routing: hash(key) % 4 picks one of four nodes. Even, simple, and fragile when the node count changes.

Keys spread evenly, but adding or removing a node changes num_nodes, which changes the result for nearly every key. Going from 4 to 5 nodes remaps about 80% of keys (a key stays put only if h % 4 == h % 5). Every remapped key is now a miss on its new node, so the database takes a burst of traffic until the cache re-warms.

Consistent hashing

Consistent hashing fixes the remapping problem. Both nodes and keys are placed on a circular hash space (a ring), and each key belongs to the first node clockwise from its position. Adding a node between two others only takes keys from its clockwise neighbor; every other assignment stays put. Adding or removing a node moves only about 1/N of the keys; the other (N−1)/N stay where they are.

user:7cart:3feed:9Cache ACache BCache C
Consistent hashing ring: each key belongs to the next node clockwise. Adding a node only takes over the keys in one arc.

In practice each physical node is placed at many points on the ring (virtual nodes) so load stays even and a departing node's keys spread over all the others. The full algorithm is in consistent hashing; the same technique is used for sharding databases.

Redis Cluster: hash slots

Redis Cluster uses a fixed set of 16,384 hash slots instead of a continuous ring. Each key maps to a slot with CRC16(key) % 16384, and each node owns a range of slots. To scale from 3 to 4 nodes, you (or your managed service) move some slots from the existing nodes to the new one. Only keys in moved slots migrate; clients may see brief redirections during the move, and all other slots are unaffected.

NodesSlots per nodeKeys remapped on add
3 → 45,461 → 4,096~25%
4 → 54,096 → 3,277~20%
6 → 72,731 → 2,341~14%
3 nodesNode Aslots 0–5460Node B5461–10922Node C10923–163834 nodesA4,096 slotsB4,096 slotsC4,096 slotsD (new)4,096 slots
Adding a fourth node: each existing node hands about a quarter of its slots to node D; keys in unmoved slots never move.

Redis Cluster has tooling to assign and rebalance slots, and managed services often automate it. Client libraries learn the slot map from any node and send each request straight to the right node, with no proxy.

Hash tags put related keys on the same node: only the part inside braces is hashed, so {user:123}:profile and {user:123}:settings land in the same slot. That's required for multi-key operations such as MGET or transactions, which only work on co-located keys. See cache key design.

Client-side vs proxy-based routing

Client-side routingProxy-based routingdirect+1 hopAppsmart client + slot mapShard owning the keyAppplain clientProxyTwemproxy / EnvoyShard owning the key
Client-side routing: a cluster-aware client talks to the right node directly. Proxy routing: a simple client talks to a proxy, which forwards to the right shard.
  • Client-side routing: the client library holds the shard map (in Redis Cluster, the slot-to-node mapping) and sends each command straight to the right node. No extra hop, but every application needs a cluster-aware client.
  • Proxy-based routing: a proxy such as Twemproxy or Envoy sits between the application and the cache nodes. The application uses a simple client and talks only to the proxy, which routes each request. One extra network hop, but simpler clients and a central place for access control.

Redis Cluster deployments usually use client-side routing, because direct connections save latency. Choose a proxy when applications can't use a cluster-aware client or you want centralized control.

The hot key problem

Sharding spreads keys evenly by hash, not by popularity. If one key gets 50% of all reads (a viral product page, a breaking news story), its shard handles half the total load while the others idle. That shard becomes the bottleneck no matter how many nodes you add. See hot partitions.

50%All readsShard 1product:viral · 50% loadShard 2~17% loadShard 3~17% load
A hot key: one viral product takes half of all reads, so its shard saturates while the others sit idle.
  • Read replicas per shard: give each shard one or more replicas and spread the hot key's reads over the primary and its replicas. Helps, at the cost of complexity and memory.
  • Key splitting: store the hot value under several suffixed keys (product:viral:1 … product:viral:10) and have clients read a random one. Ten copies can spread load over up to 10 shards (depending on slot placement). The write path must update all copies: write amplification.
  • Local caching: keep the hot key in each application server's memory (an in-process LRU with a short TTL). This can cut Redis traffic for that key dramatically, depending on the local hit rate and TTL, but adds a second layer to keep consistent.
import random

HOT_COPIES = 10

def write_hot(key, value, ttl=60):
    for i in range(1, HOT_COPIES + 1):                     # write amplification: 10 writes
        redis.set(f"{key}:{i}", value, ex=ttl)

def read_hot(key):
    return redis.get(f"{key}:{random.randint(1, HOT_COPIES)}")   # spreads reads across shards

Next: keeping the distributed cache serving when nodes fail, in cache high availability.

Where it’s used

Redis Cluster and managed Redis

Hash-slot sharding with client-side routing; ElastiCache and Memorystore automate resharding.

Memcached fleets

Clients use consistent hashing (ketama) so adding a server moves only a fraction of keys.

Twitter's Twemproxy

A proxy that shards requests across many Redis or Memcached servers for clients that aren't cluster-aware.

Flash sales and breaking news

Hot keys are split into copies or cached in-process to avoid one overloaded shard.

In an interview

  • Shard when the working set or ops/sec exceed one node; say which limit you hit.
  • Prefer consistent hashing or hash slots over modulo, and say how many keys move when a node is added.
  • Mention hash tags when multi-key operations must work.
  • Bring up the hot key problem and a mitigation: replicas, key splitting or local caching.