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
| Property | Static | Dynamic |
|---|---|---|
| Data Structure | Sorted tuple (array) | Balanced tree (:gb_trees) |
| Lookup | O(log N) binary search | O(log N) tree traversal |
| Add Node | O(N log N) — rebuilds vnodes | O(log N) — inserts vnodes |
| Remove Node | O(N) — filters vnodes | O(log N) — deletes vnodes |
| Memory | Lower — flat tuple | Higher — tree nodes + pointers |
| Choose when… | Reads >> writes | Writes >> reads |
Both implementations produce identical results for
find_nodeandcollect_nodesgiven 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/2sorts all virtual nodes into a flat tuple — O(N log N) where N = nodes × vnodesadd_nodes/2rebuilds the entire tuple — O(V log V) where V = total virtual nodesremove_nodes/2filters the tuple — O(V)find_node/2uses 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/2inserts each virtual node into the tree — O(V log V)add_node/2inserts only new virtual nodes — O(log V) per insertionremove_node/2removes only affected virtual nodes — O(log V) per deletionfind_node/2uses 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)