Skip to main content
SDMastery

Consistent Hashing Implementation

Working Source-code implementations of consistent hashing with virtual nodes, hash ring, and key distribution.

Quick answer: Consistent Hashing Implementation

Consistent Hashing Implementation is covered here as a practical system design resource. Working Source-code implementations of consistent hashing with virtual nodes, hash ring, and key distribution. Use it as a curated reference while connecting the original engineering lesson to practical system design decisions.

Focus on the problem being solved, the main design choice, the failure mode, and the tradeoff you would defend in front of a senior engineer. This page is part of the DosiBridge system design knowledge graph, so it links related concepts, interview problems, tradeoffs, and references instead of leaving the topic isolated.

Diagram showing the key components and data flow in a Consistent Hashing Implementation system design
High-level overview of Consistent Hashing Implementation

Consistent Hashing — Code Lab

Consistent hashing is a technique that minimizes key redistribution when nodes are added or removed from a distributed system. This implementation demonstrates the core algorithm with virtual nodes for balanced distribution.

How the Algorithm Works

System architecture diagram for Consistent Hashing Implementation showing how services, databases, and caches connect
System architecture for Consistent Hashing Implementation
  1. Hash Ring: Both servers and keys are mapped onto a circular hash space (0 to 2{}32 - 1)
  2. Virtual Nodes: Each physical server is mapped to multiple positions on the ring for even distribution
  3. Key Assignment: A key is assigned to the first server found by walking clockwise from the key's hash position
  4. Node Changes: When a server is added/removed, only K/N keys need to be remapped (K = total keys, N = total nodes)

Complexity Analysis

OperationTime ComplexitySpace Complexity
Add NodeO(K log N)O(N * V)
Remove NodeO(K log N)O(N * V)
Lookup KeyO(log N * V)O(1)

Where N = number of nodes, V = virtual nodes per server, K = total keys.

Step-by-step diagram showing how Consistent Hashing Implementation processes a request from start to finish
How Consistent Hashing Implementation works step by step

Java Implementation

java
package implementations.java.consistent_hashing;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class ConsistentHashing {
    private final int numReplicas; // Number of virtual nodes per server
    private final TreeMap<Long, String> ring; // Hash ring storing virtual nodes
    private final Set<String> servers; // Set of physical servers

    public ConsistentHashing(List<String> servers, int numReplicas) {
        this.numReplicas = numReplicas;
        this.ring = new TreeMap<>();
        this.servers = new HashSet<>();

        // Add each server to the hash ring
        for (String server : servers) {
            addServer(server);
        }
    }

    private long hash(String key) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(key.getBytes());
            byte[] digest = md.digest();
            return ((long) (digest[0] & 0xFF) << 24) |
                   ((long) (digest[1] & 0xFF) << 16) |
                   ((long) (digest[2] & 0xFF) << 8) |
                   ((long) (digest[3] & 0xFF));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 algorithm not found", e);
        }
    }

    public void addServer(String server) {
        servers.add(server);
        for (int i = 0; i < numReplicas; i++) {
            long hash = hash(server + "-" + i); // Unique hash for each virtual node
            ring.put(hash, server);
        }
    }

    public void removeServer(String server) {
        if (servers.remove(server)) {
            for (int i = 0; i < numReplicas; i++) {
                long hash = hash(server + "-" + i);
                ring.remove(hash);
            }
        }
    }

    public String getServer(String key) {
        if (ring.isEmpty()) {
            return null; // No servers available
        }

        long hash = hash(key);
        // Find the closest server in a clockwise direction
        Map.Entry<Long, String> entry = ring.ceilingEntry(hash);
        if (entry == null) {
            // If we exceed the highest node, wrap around to the first node
            entry = ring.firstEntry();
        }
        return entry.getValue();
    }

    public static void main(String[] args) {
        List<String> servers = Arrays.asList("S0", "S1", "S2", "S3", "S4", "S5");
        ConsistentHashing ch = new ConsistentHashing(servers, 3);

        // Step 2: Assign requests (keys) to servers
        System.out.println("UserA is assigned to: " + ch.getServer("UserA"));
        System.out.println("UserB is assigned to: " + ch.getServer("UserB"));

        // Step 3: Add a new server dynamically
        ch.addServer("S6");
        System.out.println("UserA is now assigned to: " + ch.getServer("UserA"));

        // Step 4: Remove a server dynamically
        ch.removeServer("S2");
        System.out.println("UserB is now assigned to: " + ch.getServer("UserB"));
    }
}

Python Implementation

python
import hashlib
import bisect

class ConsistentHashing:
    def __init__(self, servers, num_replicas=3):
        """
        Initializes the consistent hashing ring.

        - servers: List of initial server names (e.g., ["S0", "S1", "S2"])
        - num_replicas: Number of virtual nodes per server for better load balancing
        """
        self.num_replicas = num_replicas  # Number of virtual nodes per server
        self.ring = {}  # Hash ring storing virtual node mappings
        self.sorted_keys = []  # Sorted list of hash values (positions) on the ring
        self.servers = set()  # Set of physical servers (used for tracking)

        # Add each server to the hash ring
        for server in servers:
            self.add_server(server)

    def _hash(self, key):
        """Computes a hash value for a given key using MD5."""
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_server(self, server):
        """
        Adds a server to the hash ring along with its virtual nodes.

        - Each virtual node is a different hash of the server ID to distribute load.
        - The server is hashed multiple times and placed at different positions.
        """
        self.servers.add(server)
        for i in range(self.num_replicas):  # Creating multiple virtual nodes
            hash_val = self._hash(f"{server}-{i}")  # Unique hash for each virtual node
            self.ring[hash_val] = server  # Map hash to the server
            bisect.insort(self.sorted_keys, hash_val)  # Maintain a sorted list for efficient lookup

    def remove_server(self, server):
        """
        Removes a server and all its virtual nodes from the hash ring.
        """
        if server in self.servers:
            self.servers.remove(server)
            for i in range(self.num_replicas):
                hash_val = self._hash(f"{server}-{i}")  # Remove each virtual node's hash
                self.ring.pop(hash_val, None)  # Delete from hash ring
                self.sorted_keys.remove(hash_val)  # Remove from sorted key list

    def get_server(self, key):
        """
        Finds the closest server for a given key.

        - Hash the key to get its position on the ring.
        - Move clockwise to find the nearest server.
        - If it exceeds the last node, wrap around to the first node.
        """
        if not self.ring:
            return None  # No servers available

        hash_val = self._hash(key)  # Hash the key
        index = bisect.bisect(self.sorted_keys, hash_val) % len(self.sorted_keys)  # Locate nearest server
        return self.ring[self.sorted_keys[index]]  # Return the assigned server

# ----------------- Usage Example -------------------

# Step 1: Initialize Consistent Hashing with servers
servers = ["S0", "S1", "S2", "S3", "S4", "S5"]
ch = ConsistentHashing(servers)

# Step 2: Assign requests (keys) to servers
print(ch.get_server("UserA"))  # Maps UserA to a server
print(ch.get_server("UserB"))  # Maps UserB to a server

# Step 3: Add a new server dynamically
ch.add_server("S6")
print(ch.get_server("UserA"))  # Might be reassigned if affected

# Step 4: Remove a server dynamically
ch.remove_server("S2")
print(ch.get_server("UserB"))  # Might be reassigned if affected

Key Design Decisions

Comparison table for Consistent Hashing Implementation contrasting approaches, tradeoffs, and when to use each
Comparing key aspects of Consistent Hashing Implementation
  • MD5 Hashing: Both implementations use MD5 to distribute keys uniformly across the ring. MD5 is not cryptographically secure, but its distribution properties are excellent for hashing.
  • Virtual Nodes: Without virtual nodes, 3 physical servers might hash to adjacent positions, leaving large gaps. With 100-200 virtual nodes per server, distribution is nearly uniform.
  • TreeMap / Sorted List: Java uses TreeMap (red-black tree) for O(log n) ceiling lookups. Python uses bisect on a sorted list for the same complexity.

Production Considerations

  • Thread Safety: The Java implementation is not thread-safe. In production, wrap mutations in synchronized blocks or use ConcurrentSkipListMap.
  • Hash Function: For production, consider xxHash or MurmurHash3 — they are faster than MD5 while maintaining good distribution.
  • Monitoring: Track the number of keys per node to detect imbalance. Alert if any node holds more than 2x its fair share.
  • Rebalancing: When adding a node, only keys between the new node and its predecessor need to migrate. Implement this as a background process.
Data flow diagram for Consistent Hashing Implementation showing how requests and responses move through the system
Data flow through Consistent Hashing Implementation
  1. How does consistent hashing differ from modular hashing (key % N)?
  2. What problem do virtual nodes solve?
  3. How would you implement rebalancing when a new node is added?
  4. How is consistent hashing used in distributed caching (Memcached)?

Source

Component diagram for Consistent Hashing Implementation showing each building block and its responsibility
Key components of Consistent Hashing Implementation

Code from System-Design-Overview by Ashish Pratap Singh. Explanations are original.

Original DosiBridge notes for Consistent Hashing Implementation

This section is written specifically for this page, not copied from a general system-design checklist. The useful way to study Consistent Hashing Implementation is to connect the concept to a production service where growth exposes the first weak assumption in the original design. That framing changes the design discussion from definitions to operating choices.

What makes this topic distinct

  • Practice Loop: For Consistent Hashing Implementation, decide the practice loop before choosing tools; otherwise the design looks clean but fails when consistent changes.
  • Implementation Exercise: For Consistent Hashing Implementation, decide the implementation exercise before choosing tools; otherwise the design looks clean but fails when hashing changes.
  • Review Checkpoint: For Consistent Hashing Implementation, decide the review checkpoint before choosing tools; otherwise the design looks clean but fails when code changes.
  • Failure Drill: For Consistent Hashing Implementation, decide the failure drill before choosing tools; otherwise the design looks clean but fails when consistent changes.
  • Comparison Target: For Consistent Hashing Implementation, decide the comparison target before choosing tools; otherwise the design looks clean but fails when hashing changes.

Production review question

If a team adopts Consistent Hashing Implementation, what observable signal proves it helped instead of only making the architecture diagram more fashionable?

Practical metric to track

Track consistent-code health as a named dashboard or review item: combine one user-facing metric, one saturation metric, and one correctness metric. For Consistent Hashing Implementation, this keeps the discussion grounded in behavior that can be measured after deployment instead of generic advice that sounds good in an interview but never gets verified.