defmodule Ring do
@range 4_294_967_296
def new(nodes, vnodes \\ 100) do
points = for node <- nodes, i <- 1..vnodes, do: {hash({node, i}), node}
Enum.reduce(points, :gb_trees.empty(), fn {h, node}, tree ->
:gb_trees.enter(h, node, tree)
end)
end
def owner(ring, key) do
case :gb_trees.next(:gb_trees.iterator_from(hash(key), ring)) do
{_hash, node, _iter} -> node
# Past the last point: wrap round to the first.
:none -> elem(:gb_trees.smallest(ring), 1)
end
end
# `:erlang.phash2/2` gives the same hash for the same
# term on every node and every platform, which is
# exactly what agreeing without talking needs.
defp hash(term), do: :erlang.phash2(term, @range)
end
rings = %{four => Ring.new(four), five => Ring.new(five)}
ring_owner = fn key, nodes -> Ring.owner(rings[nodes], key) end
IO.inspect(moved.(ring_owner, four, five), label: "ring: keys moved going from 4 to 5 nodes")