Heartbeats and failure detection: an interactive timeout simulator
Distributed systems detect failed nodes from heartbeats: each node sends a small message every second, and if none arrives for a timeout, it is declared dead. The catch is that a dead node and a slow one look the same from outside. A short timeout notices crashes quickly but also declares healthy nodes dead whenever they pause or the network hiccups; a long one rarely errs but leaves a real failure unnoticed for longer. There is no timeout that is both fast and never wrong.
The playground simulates a day of heartbeats from a node that never fails. Change the conditions and the timeout, and count how often it is wrongly declared dead, against how long a real crash would take to notice.
Flaky network: 2% of heartbeats lost, rare multi-second delays. The node never fails, yet with a 2 s timeout the monitor declares it dead 1,061 times in a day, and each false alarm can trigger a failover, a leader election or a rebalance that is itself disruptive. When the node really does crash, the monitor notices after 1.5 s on average, up to 2 s. A timeout of 5 s would have had no false alarms today, at the price of noticing a real crash 3 s later.
Why healthy nodes go quiet
- Lost or delayed packets: one lost heartbeat makes a two-second silence. In the flaky preset, with 2% of heartbeats lost, a 2 second timeout raises 1,061 false alarms in a day; a 1.5 second timeout, 2,088.
- Stop-the-world pauses: a garbage collector, a paused virtual machine or heavy swapping stops the node, heartbeats included, for seconds. The node is fine and will carry on as if nothing happened.
- Overload: a node too busy to send heartbeats on time looks dead, and failing it over moves its load onto the others, which can then look dead too.
The cost of each kind of mistake
A false alarm is not harmless. Declaring a node dead typically starts a failover, a leader election, a rebalance of partitions or the rescheduling of its work, each of which costs time and load, and the “dead” node may still be acting on its own, which is why leases and fencing tokens exist. A slow detection costs availability: requests keep going to a dead node, or nothing takes over its work, for the whole timeout. With a timeout of T and a heartbeat every second, a crash is noticed after about T minus half a second on average.
Detecting better than a fixed timeout
- Phi accrual: instead of a yes-or-no timeout, compute a suspicion level from how unusual the current silence is given the history of heartbeat intervals, and act when it crosses a threshold. Cassandra and Akka use it; the threshold adapts to how noisy each link really is.
- Ask others before deciding: SWIM-style membership, used by Consul and Serf, has other nodes probe a suspect indirectly before it is declared dead, so one bad link does not condemn a healthy node.
- Suspect first, then confirm: mark a silent node suspect, stop sending it new work, and only declare it dead after a longer timeout. Kubernetes waits a configurable time before evicting pods from an unreachable node.
- Use a lease: a node holds a lease it must renew, and gives up its role on its own when it cannot, so the rest of the system can safely take over once the lease has expired.
A phi accrual detector in code
import math, statistics, time
class PhiAccrualDetector:
"""Suspicion that grows with silence, scaled to how regular heartbeats have been."""
def __init__(self, window=100):
self.intervals, self.window, self.last = [], window, None
def heartbeat(self, now=None):
now = now or time.monotonic()
if self.last is not None:
self.intervals = (self.intervals + [now - self.last])[-self.window:]
self.last = now
def phi(self, now=None):
now = now or time.monotonic()
mean = statistics.mean(self.intervals)
std = max(statistics.pstdev(self.intervals), 0.1 * mean)
# chance a heartbeat this late would still arrive, under a normal approximation
p_later = 0.5 * math.erfc((now - self.last - mean) / (std * math.sqrt(2)))
return -math.log10(max(p_later, 1e-300))
# declare the node dead once phi passes 8: about a 1 in 100 million chance it was just lateIn a system design interview
When a design relies on noticing failures, give the numbers and the trade-off: “Nodes heartbeat every second; a node silent for 5 seconds is marked suspect and stops getting new work, and after 15 seconds its partitions are reassigned. Shorter would fail over on every long GC pause; longer would leave partitions unserved.” Expect the follow-ups: what the old node does if it was only paused, how you avoid split brain, and how detection interacts with leader election.
Read the guide to gossip and failure detection, see gossip for how membership spreads and Raft for election timeouts in practice, or watch a strong candidate design a job scheduler.
Questions people ask
How do heartbeats detect failures?
Each node regularly sends a small message, a heartbeat, to a monitor or to its peers, typically every second. If no heartbeat arrives for longer than a timeout, the node is suspected or declared dead and its work is moved elsewhere. Detection is always a guess, because a crashed node and a slow or unreachable one look the same.
How do you choose a heartbeat timeout?
Long enough that normal hiccups, such as a lost packet or a garbage-collection pause, do not trigger it, and short enough that a real failure is noticed before it hurts users. That usually means several heartbeat intervals, often 3 to 10 seconds, with a separate, shorter suspicion step before drastic action like failover.
What causes false failure detections?
Lost or delayed heartbeats on a congested or flaky network, stop-the-world pauses from garbage collection or virtual machine migration, and overload that delays sending heartbeats. The node is healthy but silent long enough to pass the timeout, and declaring it dead can trigger needless failovers.
What is a phi accrual failure detector?
A detector that outputs a suspicion level, phi, instead of a yes-or-no answer. It tracks the distribution of recent heartbeat intervals and computes how unlikely the current silence would be for a live node; the application acts when phi passes a threshold. It adapts to each link’s normal jitter and is used by Cassandra and Akka.
What is the trade-off in failure detection?
Speed against accuracy. A short timeout notices crashes quickly but declares healthy nodes dead more often; a long timeout rarely errs but leaves real failures unnoticed longer. In an asynchronous network no detector can be both perfectly fast and never wrong, so systems choose based on which mistake costs more.
How does SWIM detect failures?
Each node periodically pings a random member. If it gets no reply, it asks a few other members to ping that node indirectly; only if they also fail is the node marked suspect, and it is declared dead if it does not refute the suspicion in time. Membership changes spread by gossip, keeping load per node constant.
What happens if a node is wrongly declared dead?
Its work is reassigned while it may still be running, which can cause duplicate work or two nodes acting as leader. Systems guard against this with leases the old node must hold to act, fencing tokens that reject its stale writes, and by having nodes that lose contact stop serving on their own.