Engineering Atlas › System Design Fundamentals

Spreading state through gossip

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

The basic round

Every T seconds (often 1 s), a node picks k random peers (the fanout, often 1 to 3) and sends them a message. There are three styles. Push sends what I know. Pull asks what you know. Push-pull does both in one exchange and converges fastest, because push is fast while few nodes know and pull is fast while few do not.

With fanout 1 and push-pull, an update reaches 1,000 nodes in roughly 10 to 15 rounds. Doubling the cluster adds about one round. That logarithmic behaviour is why gossip scales to thousands of nodes.

Rumour-mongering versus anti-entropy

Rumour-mongering spreads new updates only: a node gossips a fresh item for a limited number of rounds, then stops. It is cheap but can leave a few nodes uninformed. Anti-entropy periodically compares full state between pairs and repairs differences, often using digests or Merkle trees so only the differing parts are sent. Production systems run both: rumours for speed, anti-entropy as the backstop. Van Renesse and colleagues' Scuttlebutt scheme exchanges per-node version digests first, so each side sends only entries newer than the other has seen (van Renesse et al.).

Membership with heartbeats

Each node keeps a table of (node, heartbeat counter, local time last increased). It increments its own counter each round and gossips the table. On receipt, a node keeps the higher counter per entry and records when it last saw it increase. If a peer's counter has not increased for T_fail (say 10 s), the peer is suspected, and after a further T_cleanup its entry is removed. Comparing counters, never remote timestamps, keeps this free of clock-sync assumptions.

Amazon's Dynamo found it useful to split membership in two. Joining or permanently removing a node is an explicit admin action written to persistent storage, and gossip only spreads that history, one random peer per second; temporary failures are detected locally by whoever fails to reach a node while forwarding requests. Early designs kept a gossiped global view of failures, but the explicit join and leave made it unnecessary, and seed nodes known to everyone stop the ring from splitting into groups that never hear of each other (Dynamo paper).

SWIM: separating detection from dissemination

Full-table heartbeat gossip sends O(N) data per message, which grows heavy at thousands of nodes. SWIM fixes this (SWIM paper). Each round a node pings one random member. If no ack arrives in time, it asks k other members to ping the target on its behalf (indirect probe), which filters out a single bad link. If those fail, the target is marked suspect, not dead, and the suspicion is gossiped. The suspect can refute it by gossiping a higher incarnation number. Membership changes ride piggybacked on the ping and ack messages, so dissemination costs no extra packets. HashiCorp's memberlist adds Lifeguard, which lengthens timeouts when the local node itself seems slow; in HashiCorp's experiments the full set of changes cut false positives by 50 to 100 times (Lifeguard paper).

Down the stack

Layer 1The contract

Gossip promises eventual, probabilistic delivery: with high probability every live node learns an update within O(log N) rounds, and per-node load stays roughly constant as the cluster grows. It promises nothing about ordering between updates, nothing about when a particular node hears, and nothing about agreement: two nodes can briefly disagree about who is alive. Failure detection is a trade between detection time and false-positive rate, never a guarantee. Anything built on top must tolerate stale and conflicting views, which is why gossiped state is usually versioned so that "highest version wins" merges are safe in any order.

Layer 2The mechanism

HashiCorp's memberlist, used by Consul and Serf, implements SWIM with Lifeguard. Its default LAN profile probes one member every second with a 500 ms probe timeout, asks 3 other members for an indirect probe on failure, and gossips queued broadcasts to 3 random members every 200 ms (memberlist config). Each broadcast is retransmitted 4 times the ceiling of log10(N+1), 16 times in a 2,000-node cluster, so it reaches everyone with high probability and then drops out of the queue. Message types include ping, indirect-ping, ack, suspect, alive and dead, encoded with msgpack and packed several to a datagram as a compound message. Every 30 s a node also does a full-state push-pull with a random peer over TCP as anti-entropy. Lifeguard keeps a local health score, raised when this node misses its own acks, and stretches its probe timeouts by that score (up to 8 times by default). The suspicion timeout has a floor of 4 × log10(N) probe intervals and starts at six times that floor, then shrinks toward the floor as independent members confirm the suspicion; two confirmations are expected (memberlist state.go).

Cassandra uses a different design. Each second a node gossips with a random live peer and sometimes with an unreachable node or a seed, using a three-message exchange (GossipDigestSyn, GossipDigestAck, GossipDigestAck2). Each endpoint's state carries a generation (the node's start time, so a restart outranks its old self) and a version that increments with every local change. Liveness uses the phi accrual detector: instead of a fixed timeout it estimates how unlikely the current silence is given recent heartbeat inter-arrival times, and convicts above a threshold of 8 by default (cassandra.yaml).

Layer 3The machine

memberlist keeps UDP payloads to about 1,400 bytes so a datagram fits in a 1,500-byte Ethernet MTU without IP fragmentation, since losing one fragment loses the whole datagram. A node therefore sends only a handful of small packets per second regardless of cluster size, a few kilobytes per second, while push-pull state is O(N) and goes over TCP. UDP has no retransmission, so the protocol's redundancy, not the kernel's, provides reliability: a dropped ping is covered by indirect probes through different network paths.

Most false positives come from the observer, not the network. A node starved of CPU by a noisy neighbour, a long GC pause, or a full socket receive buffer processes acks late and accuses healthy peers. Lifeguard's measurements showed large reductions in false positives by detecting that local slowness. Firewalls that allow TCP but drop UDP produce a classic failure: push-pull succeeds, probes fail, and nodes flap.

Why it's built this way

  1. Any all-to-all scheme costs O(N²) messages, so each node talks to a constant number of random peers and epidemic spread gives O(log N) delivery.
  2. Random peers and lossy links mean any single message can vanish, so updates are retransmitted several times and periodic anti-entropy repairs whatever slipped through.
  3. A missed ping cannot distinguish a dead node from a bad link or a slow observer, so SWIM adds indirect probes, suspicion with refutation, and Lifeguard's self-awareness.
  4. Fragmented UDP is fragile and TCP connections to every peer are costly, so frequent small messages fit one datagram and the rare full sync uses TCP.

Diagram

sequenceDiagram
    participant A as Node A
    participant B as Node B
    participant C as Node C
    participant D as Node D
    A->>B: ping
    Note over A,B: no ack within timeout
    A->>C: ping-req for B
    A->>D: ping-req for B
    C->>B: ping
    D->>B: ping
    Note over C,D: no ack from B
    C-->>A: no response
    A->>C: gossip B suspect, incarnation 4
    B->>A: refute, B alive, incarnation 5

Worked example

A 2,000-node Consul LAN pool with gossip interval 200 ms, fanout 3, and messages of about 1 KB with piggybacked updates.

Compare full-table heartbeat gossip: each message carries 2,000 entries of about 30 bytes, or 60 KB, and at 3 per second per node that is 180 KB/s per node and 360 MB/s cluster-wide. That difference is why large clusters use SWIM-style designs with piggybacked deltas.

Cassandra uses gossip every second with up to 3 peers for endpoint state such as load, schema version, and token ownership, plus a phi-accrual failure detector that outputs a suspicion level rather than a yes or no.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
Push-pull gossipFast convergence, simpleProbabilistic delivery timeGeneral metadata spread
Heartbeat table gossipEasy to implementO(N) message sizeClusters under a few hundred nodes
SWIM with suspicionConstant message size, fewer false positivesMore states and timersLarge or churny membership
Central registry (etcd, ZooKeeper)Strong consistency, exact membershipCoordinator load and availabilityMembership must be exact, such as locks
Anti-entropy with Merkle treesRepairs any divergence, bandwidth-efficientCPU cost to build treesReplica repair in Dynamo-style stores

Pitfalls in production

Leadership lens

Check yourself

  1. Why does push-pull converge faster than push alone?

    Show answer

    Late in the spread most nodes already know the update, so pushes are wasted, while uninformed nodes pulling from informed ones still hit quickly.

  2. Why does SWIM mark a node suspect instead of dead immediately?

    Show answer

    A missed ping can come from a slow link or a busy prober. Suspicion gives the node time to refute with a higher incarnation, cutting false removals.

  3. Why compare heartbeat counters rather than remote timestamps?

    Show answer

    Counters need no clock synchronisation; each node measures staleness with its own local clock when a counter last increased.

  4. What happens to gossip load per node as the cluster grows tenfold with SWIM?

    Show answer

    Message count and size per node stay roughly constant; only convergence and detection time grow, logarithmically.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →