Implementation Strategies

Copy Markdown View Source

ExRingRing provides two ring implementations: Static and Dynamic. They produce identical ring topologies (for the same nodes and configuration) but differ in their performance characteristics.

Comparison

PropertyStaticDynamic
Data StructureSorted tuple (array)Balanced tree (:gb_trees)
LookupO(log N) binary searchO(log N) tree traversal
Add NodeO(N log N) — rebuilds vnodesO(log N) — inserts vnodes
Remove NodeO(N) — filters vnodesO(log N) — deletes vnodes
MemoryLower — flat tupleHigher — tree nodes + pointers
Choose when…Reads >> writesWrites >> reads

Both implementations produce identical results for find_node and collect_nodes given the same nodes and configuration. The difference is only in performance.

Choosing an Implementation

Static (Default)

Best for rings that change infrequently but are queried often.

# Explicit static (also the default)
ring = ExRingRing.make(nodes, implementation: :static)

Good for:

  • Service discovery registries
  • Configuration distribution rings
  • Any ring with stable membership

Performance characteristics:

  • make/2 sorts all virtual nodes into a flat tuple — O(N log N) where N = nodes × vnodes
  • add_nodes/2 rebuilds the entire tuple — O(V log V) where V = total virtual nodes
  • remove_nodes/2 filters the tuple — O(V)
  • find_node/2 uses a binary-search-like jump across partitions — O(log V)

Dynamic

Best for rings with frequent membership changes.

ring = ExRingRing.make(nodes, implementation: :dynamic)

Good for:

  • Ephemeral nodes that join/leave frequently
  • Auto-scaling node pools
  • Real-time clustering

Performance characteristics:

  • make/2 inserts each virtual node into the tree — O(V log V)
  • add_node/2 inserts only new virtual nodes — O(log V) per insertion
  • remove_node/2 removes only affected virtual nodes — O(log V) per deletion
  • find_node/2 uses tree iterator and successor traversal — O(log V)

Verifying Equivalence

Both implementations produce the same lookup results:

nodes = ExRingRing.list_to_nodes([:a, :b, :c, :d, :e])

static_ring = ExRingRing.make(nodes, implementation: :static)
dynamic_ring = ExRingRing.make(nodes, implementation: :dynamic)

items = Enum.map(1..100, &"item_#{&1}")

Enum.each(items, fn item ->
  {:ok, s_node} = ExRingRing.find_node(static_ring, item)
  {:ok, d_node} = ExRingRing.find_node(dynamic_ring, item)
  assert s_node.key == d_node.key
end)