Major feature addition. For full details read `./docs/development_history/DISTRIBUTION.md` Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
6.8 KiB
SWIM Membership Protocol
SWIM (Scalable Weakly-consistent Infection-style Membership) handles failure detection and membership dissemination. Each node periodically probes a random peer; if the peer doesn't respond, indirect probes through relays confirm or deny reachability before the node is suspected and eventually declared dead.
Probe Cycle
See swim_probe_cycle.svg for the full state machine.
The probe cycle is a pure state machine driven by ticks:
┌──────────────────────────────────────────────────────────────────────┐
│ ProbePhase │
│ │
│ Idle ──probe_interval──► WaitingDirectAck ──timeout──► │
│ ▲ │ │
│ │ ack received │
│ │ │ │
│ └────────────────────────────┘ │
│ │
│ WaitingDirectAck ──timeout──► WaitingIndirectAck ──timeout──► │
│ │ │
│ indirect ack │
│ │ │
│ back to Idle │
│ │
│ WaitingIndirectAck ──timeout──► Suspect(target) + start timer │
│ │
│ Suspicion timer ──timeout──► DeclareDead(target) │
└──────────────────────────────────────────────────────────────────────┘
Probe targets are selected via round-robin over a shuffled member list. Each
probe cycle picks one target and sends a direct Ping. If no Ack arrives
within probe_timeout ticks, indirect PingReq messages are sent through
k relay nodes. If no indirect ack arrives either, the target enters the
Suspect state.
A ring buffer of the last 16 probe targets is maintained for dashboard visualization.
Member States & Incarnation
┌─ MemberState ────────────────────────────────────────────────────────────┐
│ │
│ Alive ─────► Suspect ─────► Dead │
│ │
│ Priority ordering: Dead (2) > Suspect (1) > Alive (0) │
│ Within the same incarnation, higher-priority state always wins. │
│ │
│ Incarnation number: bumped only by the node itself to refute │
│ suspicion. Higher incarnation unconditionally overrides any state. │
│ │
└──────────────────────────────────────────────────────────────────────────┘
The membership list is a CRDT with merge semantics:
- Higher incarnation wins unconditionally.
- Same incarnation: higher-priority state wins (
Dead > Suspect > Alive). - Lower incarnation is silently ignored.
When a node receives a Suspect about itself, it bumps its incarnation and
disseminates an Alive update — this is self-refutation.
Dissemination Queue
Membership updates are not sent in dedicated messages. Instead, they are piggybacked onto existing protocol messages (Pings, Acks, PingReqs).
Each update has a transmit budget of Lambda * ceil(log2(n)) sends, where
Lambda (default 3) is the dissemination multiplier and n is the cluster
size. The budget ensures logarithmic scaling: a 10-node cluster sends each
update ~12 times; a 1000-node cluster sends it ~30 times.
Updates are sorted by priority when piggybacking, so Dead notifications
propagate faster than routine Alive heartbeats.
See crates/distribution/DESIGN_NOTES.md for the full rationale.
Lifeguard Health Multiplier
Inspired by the Hashicorp Lifeguard paper, the HealthMultiplier tracks
the local node's network health:
- Each successful ack decreases the health score.
- Each nack/timeout increases it (capped at
max_health_score). - The score produces a multiplier
1 + scorethat stretches probe intervals and timeouts.
// Dynamic suspicion timeout:
// clamp(base * ceil(log2(n+1)) * multiplier, min, max)
pub fn dynamic_suspicion_timeout(&self, cluster_size: usize) -> u64
This prevents unhealthy nodes from generating false accusations: a node that can't reach peers slows its own probing rather than suspecting everyone.
Join Protocol
A new node contacts seed addresses with SendJoinRequest. The receiving
node adds the newcomer to its member list, enqueues the event for
dissemination, and responds with a SendJoinResponse containing the full
membership snapshot. The joiner applies the snapshot, learning about all
existing cluster members in one round-trip.
Where Things Live
| Type | File | Role |
|---|---|---|
SwimProbe |
swim/probe.rs |
Probe cycle state machine |
SwimConfig |
swim/probe.rs |
Protocol tuning knobs |
SwimEvent / SwimAction |
swim/probe.rs |
State machine I/O |
MemberList |
swim/member_list.rs |
Membership CRDT |
MemberEntry |
swim/member_list.rs |
Single member record |
SwimNode |
swim/node.rs |
Integrated SWIM facade |
NodeAction |
swim/node.rs |
High-level network actions |
DisseminationQueue |
swim/dissemination.rs |
Piggybacked update queue |
HealthMultiplier |
swim/lifeguard.rs |
Local health tracking |
LifeguardConfig |
swim/lifeguard.rs |
Lifeguard tuning knobs |
MemberState |
types.rs |
Alive / Suspect / Dead enum |
NodeRecord |
types.rs |
Wire-format membership record |