Current section
Files
Jump to
Current section
Files
lib/ex_ring_ring.ex
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