Overview

The hash ring behaviour is controlled through options passed to make/2 and make_from_keys/2. All options are optional.

# All defaults
ring = ExRingRing.make(nodes)

# Custom configuration
ring = ExRingRing.make(nodes,
  virtual_node_count: 512,
  max_hash_byte_size: 8,
  hash_algorithm: :sha256,
  implementation: :dynamic
)

Options Reference

OptionTypeDefaultDescription
:virtual_node_countpos_integer256Virtual nodes per physical node
:max_hash_byte_sizepos_integer4Significant bytes of hash value
:hash_algorithmatom:md5Hash function to use
:implementation:static | :dynamic:staticRing data structure

Hash Algorithms

AlgorithmByte SizePerformanceUse Case
:md516FastGeneral purpose (default)
:sha20ModerateWhen larger hash space needed
:sha25632SlowestMaximum hash quality
:crc324FastestHigh throughput, small hash ok
:phash24FastestErlang native, no binary conversion

The max_hash_byte_size option controls how many bytes of the hash are used. For example, with :md5 (16 bytes) and max_hash_byte_size: 4, only the first 4 bytes are used. This is clamped to the algorithm's maximum.

# Uses all 32 bytes of SHA256
ring = ExRingRing.make(nodes, hash_algorithm: :sha256, max_hash_byte_size: 32)

# Fast CRC32 with minimal hash space
ring = ExRingRing.make(nodes, hash_algorithm: :crc32)

Virtual Nodes

Each physical node is represented by multiple virtual nodes spread around the ring. More virtual nodes = better distribution at the cost of memory.

# 128 virtual nodes per physical node (less memory, slightly less balanced)
ring = ExRingRing.make(nodes, virtual_node_count: 128)

# 1024 virtual nodes (more balanced, more memory)
ring = ExRingRing.make(nodes, virtual_node_count: 1024)

Node Weights

Node weights scale the number of virtual nodes assigned to a node, giving it proportionally more hash space.

# 3-argument form: key, data, weight
node = ExRingRing.Domain.Entities.Node.make(:big, "10x capacity", 10)

# With keyword options: key, data, [weight: n]
node = ExRingRing.Domain.Entities.Node.make(:small, "1x capacity", weight: 1)

# Using list_to_nodes
nodes = ExRingRing.list_to_nodes([
  {:server_a, "10gb_ram", [weight: 2]},
  {:server_b, "10gb_ram", [weight: 2]},
  {:server_c, "4gb_ram",  [weight: 1]}
])

Weights may be fractional. A positive weight always yields at least one virtual node, even when round(virtual_node_count * weight) would round down to zero — so every active node stays reachable. Zero (or smaller) weights make the node phantom.

Phantom Nodes (Weight 0)

A node with weight 0 exists in the ring but is invisible to find_node/2 and collect_nodes/3. Useful for standby/failover members.

nodes = [
  ExRingRing.Domain.Entities.Node.make(:active, "primary", weight: 1),
  ExRingRing.Domain.Entities.Node.make(:standby, "failover", weight: 0)
]

ring = ExRingRing.make(nodes)

ExRingRing.get_nodes(ring)        # => [%Node{key: :active}, %Node{key: :standby}]
ExRingRing.get_node_count(ring)    # => 2
ExRingRing.find_node(ring, "key")  # => {:ok, %Node{key: :active}}  (never :standby)

To promote a phantom to an active node, simply add it with a positive weight:

ring = ExRingRing.add_node(ring, ExRingRing.Domain.Entities.Node.make(:standby, "now_active", weight: 1))

Error Behavior

Configuration errors are programmer errors and fail fast at ring creation with ArgumentError:

SituationResult
Unknown :hash_algorithmraises ArgumentError
Unknown :implementationraises ArgumentError
:virtual_node_count / :max_hash_byte_size not positive integersraises ArgumentError
Negative node weightraises ArgumentError in Node.make/3
Non-%Node{} term passed to make/2 or add_node(s)/2,3raises ArgumentError
find_node/2 on an empty or all-phantom ringreturns :error
collect_nodes/3 on an empty or all-phantom ringreturns []
remove_node/2 for an absent keyno-op

Runtime lookups never raise: rings are immutable values, safe to share across processes without synchronization.