Coding problem · Medium · Design & Data Structures

Consistent Hash Ring for Shard Routing

Medium Practical tasks

A messaging app routes each conversation to a database shard with a consistent hash ring. Every shard owns several virtual node positions on a ring of integers; a key is owned by the first position at or after the key's hash, wrapping around to the smallest position after the end.

  • addNode(name, points) adds a shard with the given ring positions (its virtual nodes).
  • removeNode(name) removes the shard and all its positions.
  • getNode(keyHash) returns the owning shard name, or null if the ring is empty. If several positions are equal, the shard with the alphabetically smaller name wins.

Hashing is done by the caller, so the problem focuses on the ring itself. See Resharding and Moving Data Online.

Example 1

Input: ["HashRing","addNode","addNode","getNode","getNode","getNode","getNode","getNode","removeNode","getNode","getNode"]
[[],["db1",[10,50]],["db2",[30,70]],[5],[10],[11],[60],[80],["db1"],[5],[80]]
Output: [null,null,null,"db1","db1","db2","db2","db1",null,"db2","db2"]
Explanation: 80 is past the last position (70), so it wraps to db1's position 10. After db1 is removed, everything belongs to db2.

Hints

Hint 1

Keep a sorted list of (position, name) pairs.

Hint 2

Binary-search for the first position ≥ keyHash; if none, wrap to index 0.

Hint 3

Removing a node = filtering its positions out of the sorted list.

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time getNode O(log V); add/remove O(V log V) · Space O(V)

Store every virtual node as a (position, name) pair in a list sorted by position (then name). Lookup is a binary search for the first position ≥ the key's hash, wrapping to the first entry when the hash is past the end — O(log V). Adding a shard only takes over the arcs just before its new positions, so only those keys move; with modulo hashing almost every key would move. Many virtual nodes per shard smooth out the distribution.

Reference solution

class HashRing {
  constructor() {
    this.points = []; // [position, name], sorted
  }

  addNode(name, points) {
    for (const p of points) this.points.push([p, name]);
    this.points.sort((a, b) => a[0] - b[0] || (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));
  }

  removeNode(name) {
    this.points = this.points.filter(([, n]) => n !== name);
  }

  getNode(keyHash) {
    if (this.points.length === 0) return null;
    let lo = 0, hi = this.points.length;           // first index with position >= keyHash
    while (lo < hi) {
      const mid = (lo + hi) >> 1;
      if (this.points[mid][0] < keyHash) lo = mid + 1; else hi = mid;
    }
    return this.points[lo % this.points.length][1];
  }
}

Common mistakes

  • Forgetting the wrap-around past the largest position.
  • Using > instead of ≥ for the owning position.
  • Unstable tie-breaking when positions collide.

Follow-up questions

  • Weight shards by giving bigger servers more virtual nodes.
  • Return the next N distinct shards for replication.