Skip to content

Instantly share code, notes, and snippets.

@chgeuer
Last active July 10, 2025 11:22
Show Gist options
  • Save chgeuer/cd5a1d4e0419e496da70b6f88b5bcd7d to your computer and use it in GitHub Desktop.
Save chgeuer/cd5a1d4e0419e496da70b6f88b5bcd7d to your computer and use it in GitHub Desktop.

Rendevouz hashing

Mix.install([
  {:combination, "~> 0.0.3"}
], consolidate_protocols: false)

Section

defmodule Rendezvous do
  @spec get_node(binary) :: binary
  def get_node key do
    [Node.self | Node.list]
    |> Enum.map(&to_string/1)
    |> get(key)
  end

  @spec list([any], binary) :: [any]
  def list(buckets, key) do
    buckets
    |> Enum.map(&get_bucket_with_hash(&1, key)) 
    |> Enum.sort_by(fn {_bucket, hash} -> hash end)
    |> Enum.map(fn {bucket, _hash} -> bucket end)
  end
  
  @spec get([any], binary) :: any
  def get(buckets, key) do
    buckets
    |> list(key)
    |> hd
  end

  @hash_algorith :md5

  defp get_bucket_with_hash(bucket, key) do
    {bucket, :crypto.hash(@hash_algorith, [to_string(bucket), key])}
  end
end
defmodule ComputeNode do
  defstruct [:region, :zone, :id]
end

defimpl String.Chars, for: ComputeNode do
  def to_string(%ComputeNode{region: region, zone: zone, id: id}) when is_integer(id) do    
    "#{region}-#{zone}-#{String.pad_leading(Integer.to_string(id), 4, "0")}"
  end
end
defmodule AllPermutations do
  def generate_all(set) do
    list = MapSet.to_list(set)
    
    Range.new(2, Enum.count(set))
    |> Enum.flat_map(fn subset_size ->
      list
      |> combinations(subset_size)
      |> Enum.flat_map(&permutations/1)
    end)
  end

  # Generate all combinations of size k from a list
  defp combinations(_, 0), do: [[]]
  defp combinations([], _), do: []
  defp combinations([h | t], k) do
    (for(tail <- combinations(t, k - 1), do: [h | tail])) ++ combinations(t, k)
  end

  # Generate all permutations of a list
  defp permutations([]), do: [[]]
  defp permutations(list) do
    for elem <- list, rest <- permutations(list -- [elem]), do: [elem | rest]
  end
end
datacenters = 
  for region <- ["AMS", "DUB"], 
    az <- ["A", "B"],
    id <- Range.new(1, 2) do
    %ComputeNode{region: region, zone: az, id: id}
  end
  # |> Enum.map(&to_string/1)
datacenters
|> Rendezvous.list("Joe")
|> Enum.map(&to_string/1)
|> Enum.join(" / ")
key = "Christian"

MapSet.new(datacenters)
|> AllPermutations.generate_all()
|> Enum.map(&Rendezvous.list(&1, key))
|> Enum.map(fn l -> Enum.map(l, &to_string/1) end)
|> Enum.map(&Enum.join(&1, " >> "))
|> Enum.uniq()
|> Enum.sort()
|> Enum.each(&IO.puts/1)
keys = 
  Range.new(1, 20)
  |> Enum.map(&Integer.to_string/1)
  |> Enum.map(fn x -> String.pad_leading(x, 4, "0") end)
  |> Enum.map(fn x -> "actor_#{x}" end)
  
keys
|> Enum.map(fn key ->
  order = 
    Rendezvous.list(datacenters, key)
    |> Enum.join(" / ")
  {key, order}
end)
|> Enum.map(fn {key, order} -> "#{String.pad_trailing(key, 15)}: #{order}" end)
|> Enum.each(&IO.puts/1)
histogram =
  Range.new(1, 1_000_000)
  |> Enum.map(&Integer.to_string/1)
  |> Enum.map(fn x -> String.pad_leading(x, 4, "0") end)
  |> Enum.map(fn x -> "actor_#{x}" end)
  |> Enum.map(fn key ->
    order = 
      datacenters
      |> Rendezvous.list(key)
    {key, order}
  end)
  |> Enum.group_by(fn {_key, order} -> order end)
  |> Enum.map(fn {k, vals} -> 
    vals =
      vals
      |> Enum.map(fn {k,_v} -> k end)
    {k, Enum.count(vals)}
  end)
  |> Map.new()

frequencies =
  histogram
  |> Enum.map(fn {order, count} -> {hd(order), count} end)
  |> Enum.reduce(%{}, fn {city, value}, acc ->
    Map.update(acc, city, value, &(&1 + value))
  end)
@chgeuer
Copy link
Author

chgeuer commented Jul 10, 2025

This gives

%{
  %ComputeNode{region: "AMS", zone: "A", id: 1} => 124804,
  %ComputeNode{region: "AMS", zone: "B", id: 1} => 125279,
  %ComputeNode{region: "DUB", zone: "A", id: 1} => 125143,
  %ComputeNode{region: "DUB", zone: "B", id: 1} => 125053,
  %ComputeNode{region: "AMS", zone: "A", id: 2} => 125568,
  %ComputeNode{region: "AMS", zone: "B", id: 2} => 124330,
  %ComputeNode{region: "DUB", zone: "A", id: 2} => 124504,
  %ComputeNode{region: "DUB", zone: "B", id: 2} => 125319
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment