Predict the output
Our simulation hashes 1,000 keys with MD5. How many change node with plain modulo when going from 4 to 5 nodes?
import bisect
import hashlib
def h(s: str) -> int:
return int.from_bytes(hashlib.md5(s.encode()).digest()[:8], "big")
class Ring:
def __init__(self, nodes: list[str], vnodes: int) -> None:
self.points = sorted(
(h(f"{n}#{i}"), n) for n in nodes for i in range(vnodes)
)
self.hashes = [p for p, _ in self.points]
def owner(self, key: str) -> str:
i = bisect.bisect(self.hashes, h(key)) % len(self.points)
return self.points[i][1]
keys = [f"user:{i}" for i in range(1000)]
mod_before = {k: h(k) % 4 for k in keys}
mod_after = {k: h(k) % 5 for k in keys}
print(
"modulo, 4 -> 5 nodes:",
sum(mod_before[k] != mod_after[k] for k in keys),
)Hint 1 of 1
For a key to stay, h % 4 must equal h % 5.
Choose an answer, then open Show answer below to check it.
Illustrative; not a load test
Text version
| Key | Before | After | Moved? |
|---|---|---|---|
| item:1 | B | B | No |
| item:2 | B | B | No |
| item:3 | A | D | Yes, to D |
| item:4 | A | A | No |
| item:5 | A | D | Yes, to D |
| item:6 | A | A | No |
| item:7 | B | B | No |
| item:8 | B | B | No |
| item:9 | C | C | No |
| item:10 | B | B | No |
| item:11 | B | B | No |
| item:12 | B | B | No |
Keys per node: A 2, B 7, C 1, D 2.
| Key | Before | After | Moved? |
|---|---|---|---|
| item:1 | B | A | Yes, to A |
| item:2 | B | A | Yes, to A |
| item:3 | A | A | No |
| item:4 | A | A | No |
| item:5 | A | A | No |
| item:6 | A | A | No |
| item:7 | B | A | Yes, to A |
| item:8 | B | A | Yes, to A |
| item:9 | C | C | No |
| item:10 | B | A | Yes, to A |
| item:11 | B | A | Yes, to A |
| item:12 | B | A | Yes, to A |
Keys per node: A 11, C 1.
| Key | Before | After | Moved? |
|---|---|---|---|
| item:1 | A | A | No |
| item:2 | A | A | No |
| item:3 | A | D | Yes, to D |
| item:4 | A | A | No |
| item:5 | A | D | Yes, to D |
| item:6 | A | A | No |
| item:7 | B | D | Yes, to D |
| item:8 | B | B | No |
| item:9 | C | C | No |
| item:10 | B | B | No |
| item:11 | B | B | No |
| item:12 | B | B | No |
Keys per node: A 4, B 4, C 1, D 3.
| Key | Before | After | Moved? |
|---|---|---|---|
| item:1 | A | A | No |
| item:2 | A | A | No |
| item:3 | A | A | No |
| item:4 | A | A | No |
| item:5 | A | A | No |
| item:6 | A | A | No |
| item:7 | B | A | Yes, to A |
| item:8 | B | A | Yes, to A |
| item:9 | C | C | No |
| item:10 | B | A | Yes, to A |
| item:11 | B | A | Yes, to A |
| item:12 | B | A | Yes, to A |
Keys per node: A 11, C 1.
11 of 12 would move with hash mod N (3 → 4).
Show answer
Answer: A modulo, 4 -> 5 nodes: 807
Correct. Roughly 4 in 5 keys move, because the remainder changes for most hashes.
With hash % n, changing n changes the remainder for most hashes, so most keys move. In this run 807 of 1,000 move, while a ring would move only the share the new node takes over, about 1 in 5.
Why not the others?
- B
modulo, 4 -> 5 nodes: 200. That's about what an ideal ring moves, not modulo. - C
modulo, 4 -> 5 nodes: 0. Changing the divisor reshuffles most keys. - D
modulo, 4 -> 5 nodes: 1000. Keys whose hash gives the same remainder for 4 and 5 stay put.