defmodule ExRingRing do @moduledoc """ ExRingRing - Consistent Hash Ring implementation in Elixir. This module provides the main public API for creating and manipulating consistent hash rings. ## Example # Create nodes nodes = ExRingRing.list_to_nodes([:a, :b, :c, :d, :e]) # Create a ring ring = ExRingRing.make(nodes) # Find a node for an item {:ok, node} = ExRingRing.find_node(ring, "item_1") # Collect multiple nodes for replication nodes = ExRingRing.collect_nodes(ring, "item_1", 3) # Add a node new_ring = ExRingRing.add_node(ring, ExRingRing.Domain.Entities.Node.make(:f)) # Remove a node new_ring = ExRingRing.remove_node(ring, :c) """ alias ExRingRing.Application.Services.HashRingService alias ExRingRing.Domain.Entities.{Node, Ring} @doc """ Creates a new consistent hash ring. ## Options - `:virtual_node_count` - Number of virtual nodes per physical node (default: 256) - `:max_hash_byte_size` - Maximum bytes of hash to use (default: 4) - `:hash_algorithm` - Hash algorithm: `:md5`, `:sha`, `:sha256`, `:crc32`, `:phash2` (default: `:md5`) - `:implementation` - Ring implementation: `:static` (fast reads) or `:dynamic` (fast writes) (default: `:static`) ## Examples iex> nodes = ExRingRing.list_to_nodes([:a, :b, :c]) iex> ring = ExRingRing.make(nodes) iex> ExRingRing.is_ring(ring) true iex> ring = ExRingRing.make_from_keys([:a, :b, :c], virtual_node_count: 128) iex> ExRingRing.is_ring(ring) true """ @spec make([Node.t() | term()], keyword()) :: Ring.t() def make(nodes, opts \\ []) do HashRingService.create_ring(nodes, opts) end @doc """ Creates a new consistent hash ring from a list of keys. ## Examples iex> ring = ExRingRing.make_from_keys([:a, :b, :c]) iex> ExRingRing.is_ring(ring) true """ @spec make_from_keys([term()], keyword()) :: Ring.t() def make_from_keys(keys, opts \\ []) do HashRingService.create_ring_from_keys(keys, opts) end @doc """ Checks if the given term is a valid ring. """ @spec is_ring(term()) :: boolean() def is_ring(ring), do: Ring.is_ring(ring) @doc """ Adds nodes to the ring. Existing nodes with the same key will be overwritten. """ @spec add_nodes(Ring.t(), [Node.t()]) :: Ring.t() def add_nodes(ring, nodes) do HashRingService.add_nodes(ring, nodes) end @doc """ Adds a single node to the ring. """ @spec add_node(Ring.t(), Node.t()) :: Ring.t() def add_node(ring, node) do HashRingService.add_node(ring, node) end @doc """ Removes nodes from the ring by their keys. """ @spec remove_nodes(Ring.t(), [term()]) :: Ring.t() def remove_nodes(ring, keys) do HashRingService.remove_nodes(ring, keys) end @doc """ Removes a single node from the ring. """ @spec remove_node(Ring.t(), term()) :: Ring.t() def remove_node(ring, key) do HashRingService.remove_node(ring, key) end @doc """ Gets all nodes in the ring as a list. """ @spec get_nodes(Ring.t()) :: [Node.t()] def get_nodes(ring) do HashRingService.get_nodes(ring) end @doc """ Gets the number of nodes in the ring. """ @spec get_node_count(Ring.t()) :: non_neg_integer() def get_node_count(ring) do HashRingService.get_node_count(ring) end @doc """ Finds the node responsible for the given item. Returns `{:ok, node}` or `:error` if the ring is empty. """ @spec find_node(Ring.t(), term()) :: {:ok, Node.t()} | :error def find_node(ring, item) do HashRingService.find_node(ring, item) end @doc """ Collects N nodes for the given item in order of priority. Useful for replication - the first node is primary, subsequent nodes are replicas. """ @spec collect_nodes(Ring.t(), term(), non_neg_integer()) :: [Node.t()] def collect_nodes(ring, item, count) do HashRingService.collect_nodes(ring, item, count) end @doc """ Converts a list of keys/tuples to a list of nodes. Accepts: - Keys: `[:a, :b, :c]` - Key-data pairs: `[{:a, "data_a"}, {:b, "data_b"}]` - Key-data-options tuples: `[{:a, "data_a", [weight: 2]}, {:b, "data_b"}]` ## Examples iex> ExRingRing.list_to_nodes([:a, :b, :c]) |> length() 3 iex> ExRingRing.list_to_nodes([{:a, "server_a"}, {:b, "server_b", [weight: 2]}]) |> length() 2 """ @spec list_to_nodes([term()]) :: [Node.t()] def list_to_nodes(list) do Enum.map(list, fn {key, data, opts} when is_list(opts) -> Node.make(key, data, opts) {key, data} -> Node.make(key, data) key -> Node.make(key) end) end end