System Design
Heartbeats: How Distributed Systems Stay Alive
A node goes silent. Is it dead, or just slow? Your whole system's stability rests on how it answers that one deceptively hard question.
Here's a question that sounds simple and absolutely is not: in a system of a thousand machines, when one of them dies, how do the others find out?
There's no alarm that rings. A crashed server doesn't send a farewell message - it just goes silent. And silence is ambiguous. A node that's stopped responding might be dead. Or it might be perfectly alive but stuck behind a slow network link, or paused mid-garbage-collection, or just overloaded for a second. The entire stability of a distributed system rests on answering one question correctly and quickly: is that node dead, or just slow? Get it wrong in either direction and you have a problem. This is failure detection, the necessary first step before any of the failure handling I've written about elsewhere can kick in.
The heartbeat
The standard mechanism is beautifully simple. Each node periodically sends a small "I'm alive" message - a heartbeat - to whoever is monitoring it (a central monitor, or its peers). As long as the heartbeats keep arriving on schedule, everyone assumes the node is healthy. When the heartbeats stop, the monitor concludes the node has failed and reacts - reroutes its traffic, promotes a replica, triggers a leader election, rebalances its data.
sequenceDiagram
participant N as Node B
participant M as Monitor
N->>M: heartbeat (t=0)
N->>M: heartbeat (t=1)
N->>M: heartbeat (t=2)
Note over N: Node B crashes 💥
Note over M: t=3 - no beat
Note over M: t=4 - no beat (timeout exceeded)
M->>M: mark Node B DEAD → trigger failover
This sequence diagram shows heartbeat-based failure detection. Node B sends heartbeats to a monitor at times zero, one, and two. Then Node B crashes. The monitor sees no heartbeat at time three, and again none at time four, exceeding its timeout. The monitor then marks Node B as dead and triggers failover.
That's the whole concept. The difficulty - and where the real engineering lives - is in one number: how long do you wait before declaring a node dead?
The dead-vs-slow dilemma
That timeout is a genuine trade-off with no perfect setting, and you can feel both failure modes by sliding it:
HEARTBEAT_INTERVAL = 1.0 # node beats once per second
def verdict(last_seen, now, timeout):
return "DEAD" if (now - last_seen) > timeout else "ALIVE"
# A node's last heartbeat arrived at t=10.0. It's now t=12.5 - about 2 beats missed.
last_seen, now = 10.0, 12.5
for timeout in [1.5, 3.0, 5.0]:
print(f"timeout={timeout}s -> node marked {verdict(last_seen, now, timeout)}")
Run it. With a short timeout (1.5s) the node is declared DEAD after missing barely one beat - fast detection, but dangerous: if that node was merely slow for a moment, you just marked a healthy node dead and triggered an unnecessary failover. With a long timeout (5s) the same node is still considered ALIVE - safe against false alarms, but if it really is dead, you'll keep routing traffic to a corpse for five full seconds.
Important: This is the core tension of failure detection:
- Timeout too short → false positives. You declare healthy-but-slow nodes dead, causing needless failovers, data reshuffling, and system churn - sometimes a cascade where the churn itself slows nodes down and gets them falsely marked dead.
- Timeout too long → slow detection. Dead nodes keep receiving requests that silently fail, and recovery is sluggish.
There is no universally correct value. It's tuned to your network's real-world latency and how costly a wrong guess is.
I learned to respect this the hard way: an aggressive timeout that looked great in testing caused a storm of false failovers in production the first time the network had a bad few seconds. Tuning failure detectors is unglamorous and it matters enormously.
Smarter detection
Because a fixed timeout is so blunt, real systems get cleverer:
- Phi-accrual failure detection. Instead of a binary alive/dead verdict, this approach outputs a suspicion level - a number that rises the longer a node is silent - and adapts to the network's observed timing. On a network that's normally jittery, it's more forgiving; on a tight, reliable one, it's quicker to suspect. Systems like Cassandra and Akka use this so they don't have to commit to one rigid timeout.
- Gossip protocols. In a large cluster, having every node monitor every other node is N² connections - it doesn't scale. With gossip, each node heartbeats a few random peers and they share what they've heard, so health information spreads through the cluster like a rumor. No central monitor, no all-to-all connections, and it scales to thousands of nodes.
- Push vs pull. Push means nodes send their own heartbeats (the default). Pull means a monitor actively pings nodes and waits for a reply. Push scales better; pull gives the monitor more control over timing.
Interview: If a design question involves a cluster of nodes - a distributed database, a leader-follower setup, a coordinator managing workers - mentioning heartbeats unprompted is a strong signal. Even better: "I'd detect node failure with heartbeats, and I'd tune the timeout carefully - too aggressive and a slow node gets falsely marked dead, triggering unnecessary failover." That one caveat shows you understand detection isn't free.
Detection is step one, not the whole story
Heartbeats answer "is it down?" Everything that happens next - rerouting traffic, electing a new leader, promoting a replica, rebalancing data - is failure handling, which is its own toolkit (timeouts, circuit breakers, graceful degradation, and the rest). But none of it can start until detection fires, and detection is only as good as that timeout you chose. Get the heartbeat wrong and you either react to failures that aren't real or miss the ones that are.
It's a small mechanism doing a load-bearing job - which is exactly why it shows up in nearly every distributed system worth studying. We connect detection and handling into one coherent picture, with the tuning trade-offs spelled out, across System Design Fundamentals.
Go deeper
- Heartbeat (computing) - the mechanism and its common uses.
- Failure detector - the theory of detecting failures in asynchronous systems, including accrual detectors.
- Gossip protocol - how health information spreads in large clusters without central monitoring.
- Apache Cassandra - a production system using phi-accrual failure detection and gossip in the real world.