Menu
Course/Distributed Systems/Consistent Hashing

Consistent Hashing

Distribute data across nodes with minimal remapping: the hash ring, virtual nodes, load balancing, and real-world usage in Cassandra and DynamoDB.

15 min readHigh interview weight

The Problem with Naive Hashing

The simplest way to distribute keys across N nodes is `node = hash(key) % N`. This works fine when N is fixed. But when you add or remove a node, N changes, and almost every key maps to a different node. If you have 1 million keys and add one node (N becomes N+1), nearly all keys must be remapped — causing a massive cache miss storm or data reshuffling that can take hours.

The Hash Ring

Consistent hashing solves this by arranging the hash space as a ring (typically 0 to 2^32 - 1 or 0 to 2^64 - 1). Both nodes and keys are hashed onto this ring. Each key is owned by the first node clockwise from the key's position on the ring. When a node is added or removed, only the keys between the new/removed node and its predecessor need to be remapped.

Loading diagram...
Consistent hash ring — each key maps to the first clockwise node

Adding node D at hash=250: only keys between hash=200 (Node B) and hash=250 (Node D) need to move from Node C to Node D. All other keys are unaffected. In a 4-node ring, adding one node remaps only 1/4 of keys on average, instead of nearly all keys with modulo hashing.

Virtual Nodes (Vnodes)

A naive consistent hash ring has two problems: uneven load distribution (nodes land at non-uniform positions on the ring) and hot spots when a node fails (all its keys move to one successor). Virtual nodes solve both: each physical node is placed at multiple positions on the ring (e.g., 256 positions in Cassandra). These virtual positions are spread around the ring, ensuring even distribution.

  • Even load: with 256 vnodes per node, load variance is much smaller than with 1 position per node.
  • Graceful failure: when a node fails, its 256 virtual positions redistribute their keys across many other nodes — not just one successor.
  • Heterogeneous hardware: more powerful nodes can be assigned more virtual positions to carry proportionally more load.
typescript
class ConsistentHashRing {
  private ring: Map<number, string> = new Map(); // hash -> nodeId
  private sortedHashes: number[] = [];
  private replicationFactor: number;
  private vnodes: number;

  constructor(replicationFactor = 3, vnodes = 256) {
    this.replicationFactor = replicationFactor;
    this.vnodes = vnodes;
  }

  addNode(nodeId: string): void {
    for (let i = 0; i < this.vnodes; i++) {
      const virtualKey = `${nodeId}#${i}`;
      const hash = this.hash(virtualKey);
      this.ring.set(hash, nodeId);
      this.sortedHashes.push(hash);
    }
    this.sortedHashes.sort((a, b) => a - b);
  }

  getNode(key: string): string {
    const hash = this.hash(key);
    // Find first node clockwise from key's hash
    const idx = this.sortedHashes.findIndex(h => h >= hash);
    const ringIdx = idx === -1 ? 0 : idx; // wrap around
    return this.ring.get(this.sortedHashes[ringIdx])!;
  }

  getReplicaNodes(key: string): string[] {
    // Returns replicationFactor distinct nodes for this key
    const hash = this.hash(key);
    const seen = new Set<string>();
    const nodes: string[] = [];
    let idx = this.sortedHashes.findIndex(h => h >= hash);
    if (idx === -1) idx = 0;

    while (nodes.length < this.replicationFactor) {
      const nodeId = this.ring.get(this.sortedHashes[idx % this.sortedHashes.length])!;
      if (!seen.has(nodeId)) { seen.add(nodeId); nodes.push(nodeId); }
      idx++;
    }
    return nodes;
  }

  private hash(key: string): number {
    // In production: use MurmurHash3 or xxHash
    let h = 0;
    for (const c of key) h = Math.imul(31, h) + c.charCodeAt(0) | 0;
    return Math.abs(h);
  }
}

Real-World Usage

SystemUsageNotes
Apache CassandraData partitioning across nodes256 vnodes per node by default; Murmur3 hash
Amazon DynamoDBPartition key → storage node mappingManaged; uses consistent hashing internally
Memcached clientsClient-side key → server mappingKetama algorithm is a consistent hash variant
Amazon ElastiCacheCluster slot assignmentRedis Cluster uses 16384 fixed slots (not pure consistent hashing)
DiscordShard selection for user connectionsConsistent hash maps user ID to shard
ℹ️

Consistent hashing vs Redis Cluster slots

Redis Cluster uses a fixed 16,384 slot space where each key's CRC16 is taken modulo 16384. This is not pure consistent hashing but achieves similar goals — slots are reassigned when nodes are added/removed, affecting only the keys in moved slots. The fixed slot space simplifies cluster membership gossip.

💡

Interview Tip

Consistent hashing comes up in almost every distributed storage system design question. Key points to convey: (1) naive `hash % N` remaps all keys on topology change; (2) consistent hashing remaps only K/N keys on average; (3) virtual nodes solve uneven distribution and hot failover; (4) real systems (Cassandra, DynamoDB) use this. If asked to design a distributed cache, always mention consistent hashing with vnodes as the partitioning strategy.

📝

Knowledge Check

4 questions

Test your understanding of this lesson. Score 70% or higher to complete.

Ask about this lesson

Ask anything about Consistent Hashing