Malachi.Cluster.Membership (malachi v0.22.7)

View Source

The pure state of SWIM-style cluster membership: who is :alive, :suspect, or :dead, with per-member incarnation numbers. No processes, timers, or network: this is the deterministic core that the failure detector and gossip transport (Malachi.Cluster.MembershipServer) drive.

Each member owns its incarnation: only that member raises its own, to refute a false suspicion. An update is {member, status, incarnation}, and merging updates is a join on the lexicographic order {incarnation, rank} where rank is alive < suspect < dead. That single rule yields the SWIM precedence:

  • a higher incarnation always wins (a fresh :alive overrides an old :suspect/:dead);
  • at equal incarnation, :suspect overrides :alive and :dead overrides both;
  • equal {incarnation, rank} changes nothing (idempotent).

Because the merge is a commutative, associative, idempotent join, applying a batch of gossiped updates converges regardless of order, what makes infection-style dissemination correct.

The one exception is an update about self: a :suspect/:dead about us is refuted by bumping our own incarnation above it and re-announcing :alive, so we never stay suspected while running. apply_update/2 returns that refutation as an effect to disseminate.

Summary

Types

Opaque k/v an admin attaches to a broker (e.g. rack, dc); disseminated with the member.

What an applied update produced: a state change or a self-refutation to disseminate.

t()

Functions

The sorted list of :alive members: the live broker set for placement and healing.

Applies one {member, status, incarnation} update under the SWIM precedence rules. Returns the new view and an effect: {:applied, update} if it changed our state (disseminate it onward), {:refute, update} if it was a false suspicion about us (announce the refutation), or :ignored.

The attributes of member (%{} if unknown or none set).

Confirms member :dead at its currently known incarnation.

The incarnation of member, or nil if unknown.

Applies a batch of updates (e.g. a received gossip), returning the new view and the list of updates that changed state (applied changes plus any self-refutation) to disseminate onward. Order-independent: the resulting view is the same for any permutation of updates.

Builds a membership view local to self, :alive at :incarnation (default 1). :peers seeds other members :alive at incarnation 0, as placeholders until they announce themselves. :attributes are self's own attributes (peers' attributes are learned via gossip).

Sets self's attributes, raising its own incarnation so the change wins the merge everywhere. Returns the new view and {:applied, update} to disseminate (attributes are owned by their member).

The status of member, or nil if unknown.

Marks member :suspect at its currently known incarnation (a no-op if already overridden).

The full view as a list of {member, status, incarnation, attributes} updates, for gossiping.

Types

attributes()

@type attributes() :: %{optional(term()) => term()}

Opaque k/v an admin attaches to a broker (e.g. rack, dc); disseminated with the member.

effect()

@type effect() :: {:applied, update()} | {:refute, update()} | :ignored

What an applied update produced: a state change or a self-refutation to disseminate.

incarnation()

@type incarnation() :: non_neg_integer()

member()

@type member() :: term()

member_state()

@type member_state() :: %{
  status: status(),
  incarnation: incarnation(),
  attributes: attributes()
}

status()

@type status() :: :alive | :suspect | :dead

t()

@type t() :: %Malachi.Cluster.Membership{
  members: %{required(member()) => member_state()},
  self: member()
}

update()

@type update() :: {member(), status(), incarnation(), attributes()}

Functions

alive_members(view)

@spec alive_members(t()) :: [member()]

The sorted list of :alive members: the live broker set for placement and healing.

apply_update(view, arg)

@spec apply_update(t(), update()) :: {t(), effect()}

Applies one {member, status, incarnation} update under the SWIM precedence rules. Returns the new view and an effect: {:applied, update} if it changed our state (disseminate it onward), {:refute, update} if it was a false suspicion about us (announce the refutation), or :ignored.

attributes(view, member)

@spec attributes(t(), member()) :: attributes()

The attributes of member (%{} if unknown or none set).

confirm(view, member)

@spec confirm(t(), member()) :: {t(), effect()}

Confirms member :dead at its currently known incarnation.

incarnation(view, member)

@spec incarnation(t(), member()) :: incarnation() | nil

The incarnation of member, or nil if unknown.

merge(view, updates)

@spec merge(t(), [update()]) :: {t(), [update()]}

Applies a batch of updates (e.g. a received gossip), returning the new view and the list of updates that changed state (applied changes plus any self-refutation) to disseminate onward. Order-independent: the resulting view is the same for any permutation of updates.

new(self, opts \\ [])

@spec new(
  member(),
  keyword()
) :: t()

Builds a membership view local to self, :alive at :incarnation (default 1). :peers seeds other members :alive at incarnation 0, as placeholders until they announce themselves. :attributes are self's own attributes (peers' attributes are learned via gossip).

:incarnation is what a restart needs. The default outranks a placeholder, which is all a first boot has to beat, but a node coming back has to outrank what its peers still remember of the member it was, and that number is not derivable from anything this process holds. It is resumed from disk by Malachi.Cluster.MemberIncarnation and handed in here.

set_attributes(view, attributes)

@spec set_attributes(t(), attributes()) :: {t(), effect()}

Sets self's attributes, raising its own incarnation so the change wins the merge everywhere. Returns the new view and {:applied, update} to disseminate (attributes are owned by their member).

status(view, member)

@spec status(t(), member()) :: status() | nil

The status of member, or nil if unknown.

suspect(view, member)

@spec suspect(t(), member()) :: {t(), effect()}

Marks member :suspect at its currently known incarnation (a no-op if already overridden).

updates(view)

@spec updates(t()) :: [update()]

The full view as a list of {member, status, incarnation, attributes} updates, for gossiping.