The Problem
In distributed systems, servers crash. Networks fail. How do you know if a server is still alive or has failed?
Solution: The Heartbeat Protocol.
- The Beat: Every T seconds (e.g., 1s), Server A sends a "Heartbeat" signal to a monitor.
- The Timeout: If the monitor doesn't receive a signal for a threshold (e.g., 3 consecutive beats), it assumes Server A is dead.
Implementation Strategies
Push Model
Server actively sends heartbeats to monitor.
import time
import threading
class HeartbeatSender:
def __init__(self, server_id, interval=1.0):
self.server_id = server_id
self.interval = interval
self.running = False
def start(self):
self.running = True
threading.Thread(target=self._send_loop, daemon=True).start()
def _send_loop(self):
while self.running:
print(f"[{self.server_id}] Heartbeat sent")
time.sleep(self.interval)
Pull Model
Monitor actively pings servers.
class HeartbeatMonitor {
async checkServer(serverUrl) {
try {
const response = await fetch(`${serverUrl}/health`, {
signal: AbortSignal.timeout(5000)
});
return response.ok;
} catch {
return false; // Server is down
}
}
}
Advanced: Adaptive Timeout
import statistics
class AdaptiveMonitor:
def __init__(self):
self.latencies = []
def calculate_timeout(self):
if len(self.latencies) < 5:
return 3.0
mean = statistics.mean(self.latencies)
stddev = statistics.stdev(self.latencies)
return mean + (3 * stddev) # 99.7% confidence
Real-World Examples
Kubernetes
- Kubelet sends heartbeat every 10s
- Master checks every 5s
- Grace period: 40s before marking node NotReady
- Pod eviction after 5 minutes
Redis Sentinel
- Pings master every 1s
- 5s timeout → Subjectively Down
- Quorum agreement → Objectively Down
- Triggers automatic failover
Cassandra
- Uses Phi Accrual Failure Detector
- Gossip protocol every 1s
- Phi threshold: 8 (suspicion level)
- Adaptive to network conditions
Common Patterns
Flapping Detection
Require N consecutive failures before marking dead:
class FlappingDetector:
def __init__(self, required_failures=3):
self.consecutive_failures = 0
def heartbeat_missed(self):
self.consecutive_failures += 1
return self.consecutive_failures >= self.required_failures
Choosing the Timeout: The Math That Matters
The single most important design decision in a heartbeat system is the timeout multiplier — how many missed intervals you tolerate before declaring death.
Consider a heartbeat interval of 1 second. Real networks are not clean: a garbage-collection pause on the sender, a retransmitted TCP segment, or a brief spike in CPU steal time on a cloud VM can all delay a single heartbeat by hundreds of milliseconds. If you declare a node dead after one missed beat, you will trigger failovers constantly — and failovers are expensive. A database promotion can mean seconds of write unavailability, cache warm-up storms, and connection churn across every client.
A practical framework:
| Timeout | Detection Speed | False Positives | Best For |
|---|---|---|---|
| 1–2 intervals | Sub-second | Very high | In-memory coordination (same rack) |
| 3–5 intervals | A few seconds | Moderate | Service meshes, load balancer health checks |
| 10+ intervals | Tens of seconds | Rare | Cross-region monitoring, node eviction |
The rule of thumb used by most production systems: detection timeout ≥ 3 × heartbeat interval, and the action taken on detection (eviction, failover) should have an additional, longer grace period. Kubernetes embodies this layering: a node is marked NotReady after 40 seconds, but pods are not evicted for another 5 minutes — because rescheduling every pod on a node is far more disruptive than tolerating a slow node for a few minutes.
Heartbeats and Split-Brain
A heartbeat only proves that a path between two machines is alive. It cannot distinguish "the server died" from "the network between us broke." This is the root of the split-brain problem: if a primary database stops receiving heartbeat acknowledgments from its monitor, is the primary dead, or is the monitor partitioned away?
Acting on a single observer's opinion is how you end up with two primaries accepting writes simultaneously. Production systems solve this in one of three ways:
- Quorum agreement — Redis Sentinel requires a majority of sentinels to agree a master is down (
ODOWN) before failover begins. A single sentinel's opinion (SDOWN) triggers nothing. - Leases — instead of "I'm alive" messages, the node holds a time-bound lock (a lease) granted by a coordination service like ZooKeeper or etcd. When the lease expires, the node must stop acting as leader even if it is healthy, because it can no longer prove it holds the role. This converts failure detection from a guess into a guarantee, at the cost of requiring loosely synchronized clocks.
- Fencing tokens — every time leadership changes, a monotonically increasing token is issued. Downstream systems reject writes carrying an old token, so even a "zombie" former leader that believes it is still alive cannot corrupt data.
Where Heartbeats Live in Real Architectures
Heartbeats appear at every layer of the stack, often under different names:
- Load balancers (health checks): an API gateway or L7 balancer polls
/healthendpoints every few seconds and removes failing backends from rotation. This is a pull-model heartbeat. - Consensus protocols: in Raft, the leader's
AppendEntriesmessages double as heartbeats. If followers miss them for an election timeout (typically 150–300 ms), they start a new election. The heartbeat is the authority signal. - Cluster membership: Cassandra and Consul piggyback liveness information on a gossip protocol, so each node maintains a probabilistic view of the whole cluster without a central monitor — no single point of failure in the failure detector itself.
- Message consumers: Kafka consumers send heartbeats to the group coordinator; miss the
session.timeout.mswindow and your partitions are rebalanced to another consumer. Many production incidents trace back to a slow message handler starving the heartbeat thread — which is why Kafka separatesmax.poll.interval.ms(processing liveness) from the heartbeat session (process liveness).
Common Pitfalls in Production
- Sharing a thread between work and heartbeats. If the event loop that processes requests also sends heartbeats, heavy load makes a busy node look dead — and killing busy nodes under peak load is a self-inflicted cascading failure. Always send heartbeats from a dedicated thread or sidecar.
- Checking liveness but not readiness. A process can be alive but useless (e.g., disconnected from its database). Kubernetes distinguishes liveness probes (restart me) from readiness probes (stop routing to me). Conflating them causes restart loops during downstream outages.
- Synchronized heartbeat storms. If 10,000 agents all report at the top of each second, the monitor sees a spike-and-silence pattern. Add per-node jitter (±10–20% of the interval) to smooth load.
- Ignoring the failure detector's own failure. Who monitors the monitor? Centralized monitors need their own redundancy — or use decentralized detection (gossip, quorum) so no single observer's opinion is authoritative.
Key Metrics
- Heartbeat success rate
- Average latency and latency variance (feeds adaptive timeouts)
- Timeout frequency
- False positive rate (nodes marked dead that were actually alive)
- Time-to-detection for confirmed failures
Interview Tips
- Timeout tradeoff: Short = fast detection, more false positives. Long = fewer false positives, slower detection.
- Adaptive timeouts: Use mean + 3*stddev for dynamic networks (like Cassandra)
- Network partitions: Require quorum to avoid split-brain scenarios
- Push vs Pull: Push scales better for many servers; Pull better for dashboards
- Real examples: Kubernetes uses 40s grace period, Cassandra uses Phi Accrual Detector
Related Concepts
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
Distributed Unique ID Generation
How to generate unique IDs in a distributed system without coordination. Twitter Snowflake, UUID v4 vs v7, Clock skew issues, and production implementations.
BitTorrent Protocol (P2P File Sharing)
Complete guide to peer-to-peer file sharing using BitTorrent protocol, covering torrent structure, piece exchange, tit-for-tat algorithm, DHT for decentralization, and real-world implementations powering massive file distribution networks.
System Design: Dropbox (Google Drive)
Designing a file synchronization service like Dropbox or Google Drive. Key concepts: Block-level Deduplication, Delta Sync, and Strong Consistency.