ExRingRing - Consistent Hash Ring implementation in Elixir.
This module provides the main public API for creating and manipulating consistent hash rings.
Rings are immutable values: they can be shared across processes and read concurrently without locks. Every modification returns a new ring.
Error handling
Invalid configuration fails fast with ArgumentError (unknown algorithm or
implementation, non-positive counts, non-%Node{} terms). Lookups never raise:
find_node/2 returns :error and collect_nodes/3 returns [] for empty or
all-phantom 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)
Summary
Functions
Adds a single node to the ring.
Adds nodes to the ring.
Collects N nodes for the given item in order of priority.
Checks if the ring has no nodes.
Finds the node responsible for the given item.
Gets the number of nodes in the ring.
Gets all nodes in the ring as a list.
Checks if the given term is a valid ring.
Converts a list of keys/tuples to a list of nodes.
Creates a new consistent hash ring.
Creates a new consistent hash ring from a list of keys.
Removes a single node from the ring.
Removes nodes from the ring by their keys.
Functions
@spec add_node( ExRingRing.Domain.Entities.Ring.t(), ExRingRing.Domain.Entities.Node.t() ) :: ExRingRing.Domain.Entities.Ring.t()
Adds a single node to the ring.
@spec add_nodes(ExRingRing.Domain.Entities.Ring.t(), [ ExRingRing.Domain.Entities.Node.t() ]) :: ExRingRing.Domain.Entities.Ring.t()
Adds nodes to the ring.
Existing nodes with the same key will be overwritten.
@spec collect_nodes(ExRingRing.Domain.Entities.Ring.t(), term(), non_neg_integer()) :: [ ExRingRing.Domain.Entities.Node.t() ]
Collects N nodes for the given item in order of priority.
Useful for replication - the first node is primary, subsequent nodes are replicas.
@spec empty?(ExRingRing.Domain.Entities.Ring.t()) :: boolean()
Checks if the ring has no nodes.
@spec find_node(ExRingRing.Domain.Entities.Ring.t(), term()) :: {:ok, ExRingRing.Domain.Entities.Node.t()} | :error
Finds the node responsible for the given item.
Returns {:ok, node} or :error if the ring is empty.
@spec get_node_count(ExRingRing.Domain.Entities.Ring.t()) :: non_neg_integer()
Gets the number of nodes in the ring.
@spec get_nodes(ExRingRing.Domain.Entities.Ring.t()) :: [ ExRingRing.Domain.Entities.Node.t() ]
Gets all nodes in the ring as a list.
Checks if the given term is a valid ring.
@spec list_to_nodes([term()]) :: [ExRingRing.Domain.Entities.Node.t()]
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 make( [ExRingRing.Domain.Entities.Node.t()], keyword() ) :: ExRingRing.Domain.Entities.Ring.t()
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)
Raises ArgumentError for invalid options or when a list element is not a
%ExRingRing.Domain.Entities.Node{} struct.
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_from_keys( [term()], keyword() ) :: ExRingRing.Domain.Entities.Ring.t()
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 remove_node(ExRingRing.Domain.Entities.Ring.t(), term()) :: ExRingRing.Domain.Entities.Ring.t()
Removes a single node from the ring.
@spec remove_nodes(ExRingRing.Domain.Entities.Ring.t(), [term()]) :: ExRingRing.Domain.Entities.Ring.t()
Removes nodes from the ring by their keys.