Installation
Add ex_ring_ring to your dependencies in mix.exs:
def deps do
[
{:ex_ring_ring, "~> 0.1.0"}
]
endThen fetch the dependency:
mix deps.get
Basic Usage
Creating Nodes
Nodes are the members of your hash ring. Create them with ExRingRing.list_to_nodes/1:
# List of keys (data defaults to key, weight defaults to 1)
nodes = ExRingRing.list_to_nodes([:a, :b, :c])
# Key-data pairs
nodes = ExRingRing.list_to_nodes([
{:node1, "192.168.1.1"},
{:node2, "192.168.1.2"}
])
# With weights (higher weight = more virtual nodes = more load)
nodes = ExRingRing.list_to_nodes([
{:node1, "192.168.1.1", [weight: 2]},
{:node2, "192.168.1.2", [weight: 1]}
])Creating a Ring
# From pre-built nodes
nodes = ExRingRing.list_to_nodes([:a, :b, :c])
ring = ExRingRing.make(nodes)
# Directly from keys (convenience)
ring = ExRingRing.make_from_keys([:a, :b, :c])Finding Nodes
Once you have a ring, find which node owns a given key:
ring = ExRingRing.make_from_keys([:a, :b, :c, :d, :e])
# Find the primary node for an item
{:ok, node} = ExRingRing.find_node(ring, "user_42")
IO.puts(node.key) # => :b (deterministic)
# :error when ring is empty
empty_ring = ExRingRing.make([])
ExRingRing.find_node(empty_ring, "anything") # => :errorReplication
Collect N distinct nodes for an item (primary + replicas):
ring = ExRingRing.make_from_keys([:a, :b, :c, :d, :e])
# First 3 unique nodes for this item
nodes = ExRingRing.collect_nodes(ring, "my_data", 3)
# => [%Node{key: :e}, %Node{key: :a}, %Node{key: :d}]
# Collects all nodes when count exceeds ring size
ExRingRing.collect_nodes(ring, "my_data", 10)
# => [%Node{key: :e}, %Node{key: :a}, %Node{key: :d}, ...] # all 5 nodesAdding and Removing Nodes
ring = ExRingRing.make_from_keys([:a, :b, :c])
# Add a single node
ring = ExRingRing.add_node(ring, ExRingRing.Domain.Entities.Node.make(:d))
# Add multiple nodes at once
ring = ExRingRing.add_nodes(ring, ExRingRing.list_to_nodes([:e, :f]))
# Remove by key
ring = ExRingRing.remove_node(ring, :a)
# Remove multiple at once
ring = ExRingRing.remove_nodes(ring, [:b, :c])Note: Adding/removing nodes preserves consistent hashing — only
1/Nof keys remap on average, where N is the number of nodes.
Checking Ring State
ExRingRing.is_ring(ring) # => true
ExRingRing.get_node_count(ring) # => 3
ExRingRing.get_nodes(ring) # => [%Node{...}, ...]Complete Example
# Simulate a distributed cache with 5 nodes
nodes = ExRingRing.list_to_nodes([:cache_1, :cache_2, :cache_3, :cache_4, :cache_5])
ring = ExRingRing.make(nodes)
keys = Enum.map(1..100, &"session_#{&1}")
# Find where each session key lives
Enum.each(keys, fn key ->
{:ok, node} = ExRingRing.find_node(ring, key)
IO.puts("#{key} -> #{node.key}")
end)
# Simulate a node failure
ring = ExRingRing.remove_node(ring, :cache_3)
# Most keys still map to the same node — only ~20% remap