Sagents.Horde.MembershipManager (Sagents v0.9.0)

Copy Markdown

Keeps 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: :participation

Why 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 sets Horde's members to exactly the nodes in that group. 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 calls Horde.Cluster.set_members/2 on all three Sagents Horde instances so they stay consistent:

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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

group()

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.

group_for(partition)

The :pg group for a given partition (nil => unpartitioned base group).

hordes()

The Horde clusters this manager keeps membership-consistent.

pg_scope_spec()

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.

scope()

The :pg scope used for participation tracking.

start_link(opts \\ [])