Malachi. Cluster. MembershipServer
(malachi v0.8.13)
View Source
Runs SWIM-style membership live: a GenServer, one per broker, that drives the pure
Malachi.Cluster.Membership view with a failure detector and gossip dissemination.
Each protocol period it pings a random alive peer; if no ack arrives within ack_timeout,
it does not suspect immediately: it asks indirect_fanout random other peers to ping the target
on its behalf (indirect ping), and only marks the peer :suspect if no ack (direct or
relayed) arrives within indirect_timeout. This cuts false positives from a transiently lost
direct path. If the suspicion is not refuted within suspicion_timeout, it confirms the peer
:dead. Every message piggybacks the sender's view (a list of {member, status, incarnation} updates), so peers learn of joins, suspicions, deaths and refutations by
anti-entropy and the views converge. A node that is wrongly suspected learns of it from the
ack to its own next ping and refutes by bumping its incarnation.
Peers are reachable by GenServer references (a registered name locally, a {name, node} tuple
across nodes), so the same code runs in-process for tests and over distributed Erlang. Sends are
fire-and-forget: pinging a dead peer simply yields no ack, which is exactly how failure is
detected.
On startup a node joins by sending each seed (its :peers) a {:join, ...}; the seed adds
the joiner as :alive and replies with its full view, so the joiner learns the whole cluster at
once instead of waiting for gossip to converge. Join is best-effort, gossip is the safety net if
a seed is unreachable. (A node that restarts after being declared dead would rejoin at
incarnation 0, which an existing :dead entry outranks; durable/higher rejoin incarnations are a
later concern.)
The same gossip also piggybacks the cluster's versioned routing topology (a
Malachi.Cluster.RingTopology): every message carries it alongside the view updates, and peers keep the
higher version (last-version-wins), so a vnode split's ring change disseminates and converges the same
way membership does: NorthGuard's "minimal global state" spread over the SWIM dissemination path.
Scope: direct ping, indirect ping, suspicion, gossip (membership + topology), and a join handshake.
Summary
Functions
The sorted list of currently alive members (the live broker set).
The attributes known for member (%{} if unknown or none set).
Returns a specification to start this module under a supervisor.
Sets this node's own attributes, raising its incarnation so the change propagates and wins.
Gossip disseminates it on the next protocol period (no proactive push, like every other update).
Sets this node's view of the cluster topology (a Malachi.Cluster.RingTopology). Gossip disseminates
it on the next protocol period and every node converges on the highest version (last-version-wins), so
the rebalancing leader publishes a split's new ring here and the cluster adopts it.
Like start_link/1 but not linked to the caller (e.g. to start on a remote node).
Starts a membership server.
The current cluster routing topology, or nil if none has been set/received yet.
The current Malachi.Cluster.Membership view (for inspection/tests).
Functions
@spec alive_members(GenServer.server()) :: [Malachi.Cluster.Membership.member()]
The sorted list of currently alive members (the live broker set).
@spec attributes(GenServer.server(), Malachi.Cluster.Membership.member()) :: Malachi.Cluster.Membership.attributes()
The attributes known for member (%{} if unknown or none set).
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec set_attributes(GenServer.server(), Malachi.Cluster.Membership.attributes()) :: :ok
Sets this node's own attributes, raising its incarnation so the change propagates and wins.
Gossip disseminates it on the next protocol period (no proactive push, like every other update).
@spec set_topology(GenServer.server(), Malachi.Cluster.RingTopology.t()) :: :ok
Sets this node's view of the cluster topology (a Malachi.Cluster.RingTopology). Gossip disseminates
it on the next protocol period and every node converges on the highest version (last-version-wins), so
the rebalancing leader publishes a split's new ring here and the cluster adopts it.
@spec start(keyword()) :: GenServer.on_start()
Like start_link/1 but not linked to the caller (e.g. to start on a remote node).
@spec start_link(keyword()) :: GenServer.on_start()
Starts a membership server.
Options
:name(optional) - the local registered name to start the GenServer under.:self_ref(optional) - the reference this member is known by to peers and gossips as its own identity. Across nodes this must be a node-qualified{name, node()}(not a bare local name, which would resolve to a different server on each node). Defaults to:name, then the pid.:peers- seed peer references, learned as:alive.:attributes- this member's own attributes (opaque k/v, e.g.%{rack: "a"}); gossiped to peers.:protocol_period/:ack_timeout/:suspicion_timeout- detector timings in ms.:indirect_timeout- ms to wait for an indirect (relayed) ack (defaultack_timeout).:indirect_fanout- number of peers asked to relay a ping (default 3).
@spec topology(GenServer.server()) :: Malachi.Cluster.RingTopology.t() | nil
The current cluster routing topology, or nil if none has been set/received yet.
@spec view(GenServer.server()) :: Malachi.Cluster.Membership.t()
The current Malachi.Cluster.Membership view (for inspection/tests).