Sagents.Horde.MembershipManager (Sagents v0.17.1)
Copy MarkdownKeeps Horde cluster membership scoped to the nodes that actually run
Sagents.Supervisor, and keeps it current as nodes come and go.
This is the membership mechanism behind:
config :sagents, :distribution, :horde
config :sagents, :horde, members: :participationWhy this exists
Horde's built-in members: :auto derives membership from
Node.list([:visible, :this]) — every connected BEAM node, regardless of
whether it runs Horde. In a cluster that meshes multiple service roles into
one Erlang cluster, that pulls unrelated nodes into the Sagents Horde cluster:
it bloats the DeltaCrdt sync fan-out (a cause of registration timeouts) and,
for membership added but never reporting :alive, leaves dead entries that
are never pruned.
Membership here is instead derived from participation: every node that
starts Sagents.Supervisor joins an OTP :pg group, and this process adds
each node to Horde's members as it joins that group and removes it once it
leaves. Because a node runs
Sagents.Supervisor only where the host application chose to (e.g. gated to a
:web role), "nodes running Sagents" is "agent-hosting nodes" — no
node-name predicate required. :pg removes a node's entry automatically on
:nodedown, so dead nodes are pruned without any extra wiring.
What it manages
On startup and on every :pg join/leave it updates the members of all three
Sagents Horde instances through Horde.Cluster.set_members/2 so they stay
consistent:
Every update starts from what the instance holds at that moment and only
adds the nodes a :pg view shows or removes the nodes :pg reported as
gone. A view is never handed to Horde as the whole member set. :pg
discovers the other nodes' scopes asynchronously, so for a moment after this
process joins, the group it reads is a partial view of the cluster, while
Horde, which also learns members through CRDT replication from its peers,
may already hold the rest. Horde treats a member missing from set_members/2
as removed and drops that member's registrations cluster-wide, so applying a
partial view would unregister every agent on the nodes it has not seen yet.
It is started automatically by Sagents.Supervisor (together with its :pg
scope) when members: :participation is configured; you do not start it
yourself.
Partitioning
When config :sagents, :horde, partition: <value> is set, the :pg group is
keyed by that partition ({:sagents_members, value}). A node only joins and
monitors its own partition's group, so membership is isolated per partition —
e.g. nodes in one Fly.io region never become members of another region's Horde
cluster, even when all nodes share one connected BEAM cluster.
Summary
Functions
Returns a specification to start this module under a supervisor.
The :pg group joined by each participating node.
The :pg group for a given partition (nil => unpartitioned base group).
The Horde clusters this manager keeps membership-consistent.
Child spec for the :pg scope this manager relies on.
The :pg scope used for participation tracking.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
The :pg group joined by each participating node.
Keyed by the configured partition (Sagents.Horde.ClusterConfig.partition/0)
so nodes only cluster with same-partition peers. Unpartitioned membership uses
the base group atom.
The :pg group for a given partition (nil => unpartitioned base group).
The Horde clusters this manager keeps membership-consistent.
Child spec for the :pg scope this manager relies on.
Sagents.Supervisor starts this before the manager so the scope is
available when the manager joins and monitors it.
The :pg scope used for participation tracking.