system-design · beginner

Failover — Switching to a Healthy Spare

The Central Question

Consider a core enterprise database cluster running on the ReliabilityLab platform (reliabilitylab.com) processing 10,000,000 transactions per day:


At 2:00 AM, a physical motherboard power failure strikes the Primary Database Server (10.0.1.20), causing the physical machine to instantly power off.

If the system lacks automated failover mechanics:

  1. Application servers continue attempting to send SQL write transactions to 10.0.1.20.
  2. Every transaction fails with a Connection Refused exception.
  3. Even though the Standby Server (10.0.1.21) contains 100% of up-to-date data, it remains idle because no automated system promoted it to primary or updated application connection routes.
  4. The platform suffers a 100% total outage lasting 3 hours until an engineer wakes up to execute manual shell scripts.

Having redundant spare hardware is useless without automated failover mechanics.

Failover is the automated operational state machine that detects component failures, promotes a healthy standby node, re-routes incoming traffic, and isolates the failed node to maintain continuous service availability.

This lesson answers one central question: How do automated failover systems execute the 4-stage control loop (Detect $\rightarrow$ Decide $\rightarrow$ Shift $\rightarrow$ Rejoin) using Quorum Consensus, Virtual IP (VIP) BGP Routing, and STONITH Fencing to achieve sub-minute RTO without triggering Split-Brain data corruption?


The 4-Stage Failover Control Loop

Every robust failover system operates as a continuous 4-stage control loop:

flowchart TD
  Detect[1. DETECT: Probe health & detect failure] --> Decide[2. DECIDE: Consensus election of new Leader]
  Decide --> Shift[3. SHIFT: Re-route Virtual IP & DNS traffic]
  Shift --> Rejoin[4. REJOIN: Fence & rebuild old Primary as Standby]
  Rejoin --> Detect

Figure 1: The 4-stage failover operational control loop.

Deconstructing the 4 Stages

  1. Detect (Health Probes & Heartbeats):
- Consensus monitors send periodic health probes (`GET /health` or TCP ping) every $T_{\text{probe}}$ seconds. - If a primary fails $K$ consecutive health probes, the detection layer flags the node as `SUSPECT_DEAD`.
  1. Decide (Quorum Election & Consensus):
- A consensus manager (such as Raft, Paxos, or Etcd) gathers votes from cluster nodes. - If a majority **Quorum** ($Q = \lfloor \frac{N}{2} \rfloor + 1$) agrees the primary is dead, the manager elects the Standby node with the highest log sequence number (WAL LSN) as the new Primary.
  1. Shift (Traffic Re-Routing):
- The networking layer shifts client traffic to the new Primary using **Virtual IP (VIP) Floating** via BGP/ARP, or updates internal service discovery DNS entries.
  1. Rejoin (Fencing & Standby Provisioning):
- The failed node is isolated via **STONITH Fencing** to prevent late-arriving writes. When the old node boots back up, it is re-provisioned as a read-only Standby follower.

Active-Passive vs. Active-Active Topologies

Failover architectures structure redundant nodes into two primary topologies:

flowchart TD
  FailoverTopologies[Failover Cluster Topologies] --> ActivePassive[1. Active-Passive / Active-Standby]
  FailoverTopologies --> ActiveActive[2. Active-Active Cluster]
  
  ActivePassive --> APDesc["ONE Primary node accepts writes.<br/>Standby sits idle or handles reads.<br/>Simple data consistency; non-zero RTO."]
  ActiveActive --> AADesc["ALL nodes accept writes simultaneously.<br/>Zero RTO failover.<br/>Requires complex multi-master conflict resolution."]

Figure 2: Architectural comparison of Active-Passive vs Active-Active topologies.


Split-Brain Syndrome and Fencing (STONITH)

The most catastrophic failure in a failover system occurs when a network partition isolates the primary node from the consensus monitors without shutting down the primary process:

flowchart TD
  subgraph Network Partition Split-Brain
    PrimaryNode[Old Primary Node 10.0.1.20] -.-x|Network Partition Cut| Monitor[Consensus Heartbeater]
    Monitor -->|Declares Primary Dead| StandbyNode[Standby Node 10.0.1.21]
    StandbyNode -->|Promoted to New Primary| NewPrimary[New Primary Node 10.0.1.21]
    
    ClientA[Client A] -->|Writes to Old Primary| PrimaryNode
    ClientB[Client B] -->|Writes to New Primary| NewPrimary
    
    style PrimaryNode fill:#f8d7da,stroke:#dc3545
    style NewPrimary fill:#f8d7da,stroke:#dc3545
  end

Figure 3: Split-Brain Syndrome causing dual primary write corruption.

Split-Brain Syndrome Mechanics

The Solution: Fencing & STONITH

To prevent split-brain corruption, automated failover systems enforce **Fencing** before promoting a new leader:

Traffic Switching Mechanics: Virtual IP (VIP) Floating vs. DNS Failover

Shifting traffic to a new primary node requires network-level routing updates:

flowchart LR
  subgraph Virtual IP Floating ARP Shift
    Router[Network Switch / Router] -->|Virtual IP: 10.0.1.100| NodeA[Primary Node 10.0.1.20]
    NodeA -.-x|Fails| Dead[CRASH]
    Router -.->|Gratuitous ARP Update| NodeB[Standby Node 10.0.1.21]
    Note["VIP 10.0.1.100 shifts instantly in < 1 second!"]
  end

Figure 4: Instant Virtual IP floating using Gratuitous ARP updates.

1. Virtual IP (VIP) Floating (Sub-Second Failover)

2. DNS-Based Failover (Multi-Region / Cross-Subnet)

Consensus Election Mechanics (Raft / Etcd)

Automated failover managers deploy distributed consensus algorithms (such as Raft or Paxos) to elect new leaders authoritatively. Under Raft, cluster nodes transition through three states: `Follower`, `Candidate`, and `Leader`. When followers stop receiving periodic heartbeat RPCs from the leader within the **Election Timeout** (typically 150-300ms), a follower increments its election term, transitions to `Candidate`, and requests votes. If it receives votes from a majority Quorum ($Q = \lfloor \frac{N}{2} \rfloor + 1$), it is elected as the new `Leader`, broadcasting heartbeats to assert authority.

Leader Lease Timeouts & Clock Drift

To prevent two nodes from claiming leadership during network blips, Raft clusters use **Leader Leases**. The elected primary node is granted a time-bounded lease (e.g. 5 seconds). The primary must continuously renew its lease before expiry. If a network partition occurs, the primary's lease expires naturally, forcing it to step down automatically before standby nodes attempt new elections.

Complete Worked Example: Go Automatic Failover Controller with Quorum Election

Let's inspect a complete Go implementation of an automatic failover controller for the ReliabilityLab platform (reliabilitylab.com).

package main

import (
"context"
"fmt"
"sync"
"time"
)

type NodeRole int

const (
RoleStandby NodeRole = iota
RolePrimary
)

type ClusterNode struct {
ID string
Address string
Role NodeRole
IsAlive bool
WALOffset int64
}

type FailoverController struct {
mu sync.Mutex
nodes map[string]*ClusterNode
activeLeader string
quorumSize int
}

func NewFailoverController(nodes map[string]ClusterNode) FailoverController {
n := len(nodes)
return &FailoverController{
nodes: nodes,
activeLeader: "node-1",
quorumSize: (n / 2) + 1, // Quorum: (N/2)+1
}
}

func (fc *FailoverController) MonitorLoop(ctx context.Context, checkInterval time.Duration) {
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
fc.evaluateClusterHealth()
}
}
}

func (fc *FailoverController) evaluateClusterHealth() {
fc.mu.Lock()
defer fc.mu.Unlock()

leader, exists := fc.nodes[fc.activeLeader]
if exists && !leader.IsAlive {
fmt.Printf("[ALERT] Primary Node %s is DEAD! Initiating Automated Failover...\n", fc.activeLeader)
fc.executeFailover()
}
}

func (fc *FailoverController) executeFailover() {
// 1. STONITH Fencing: Fence old primary
fmt.Printf("[STONITH FENCING] Hard-powering off dead primary %s...\n", fc.activeLeader)
fc.nodes[fc.activeLeader].Role = RoleStandby

// 2. Quorum Election: Elect standby node with highest WALOffset
var bestCandidate *ClusterNode
aliveCount := 0

for _, node := range fc.nodes {
if node.IsAlive {
aliveCount++
if bestCandidate == nil || node.WALOffset > bestCandidate.WALOffset {
bestCandidate = node
}
}
}

// Verify Quorum Consensus
if aliveCount < fc.quorumSize {
fmt.Printf("[FATAL FAILOVER ERROR] Cannot reach Quorum! Alive Nodes: %d / Required: %d. ABORTING FAILOVER.\n", aliveCount, fc.quorumSize)
return
}

// Promote New Leader
bestCandidate.Role = RolePrimary
fc.activeLeader = bestCandidate.ID

// 3. Shift Traffic: Virtual IP ARP update
fmt.Printf("[SUCCESS] Promoted Node %s to PRIMARY! VIP Shifted to %s\n", bestCandidate.ID, bestCandidate.Address)
}


Failure Modes and Engineering Mitigations

Failure ScenarioRoot CauseSystem SymptomDetection SignalMitigation Strategy
1. Split-Brain Data CorruptionNetwork partition isolates old primary; monitor promotes new primary while old primary keeps accepting writes.Dual primary nodes write conflicting transactions to databases simultaneously.Duplicate primary key errors and divergent transaction logs.Enforce STONITH Fencing (hardware power cut) before promoting standby nodes.
2. Flapping Failover OscillationHealth check timeout is configured too low; failover triggers during minor CPU spikes.Cluster rapidly switches primary status back and forth every 30 seconds.High failover transition rate metrics in cluster control logs.Enforce Hysteresis Counters (e.g. 5 consecutive failed probes required).
3. Cascading Failover CrashStandby node cannot absorb 100% of primary traffic load upon failover.Promoted standby node immediately crashes from CPU overload upon receiving VIP traffic.Secondary node crash alerts immediately following primary failover.Provision standby nodes with 100% Parity Hardware Capacity matching primary nodes.
4. Sub-Quorum Failover DeadlockNetwork partition isolates 3 out of 5 cluster nodes, leaving remaining nodes unable to reach quorum ($3/5$).Failover controller refuses to promote standby node; system remains offline ($RTO \to \infty$).Quorum consensus failure logs in failover manager.Deploy an odd number of voting nodes ($N = 3, 5, 7$) distributed across independent availability zones.

What You Should Remember

  1. Automated failover requires a 4-stage control loop: Detect failures $\rightarrow$ Decide consensus $\rightarrow$ Shift traffic $\rightarrow$ Rejoin old node as standby.
  2. Prevent Split-Brain with STONITH Fencing: Power off or isolate the old primary node before promoting a standby node to prevent dual-master data corruption.
  3. Enforce Majority Quorum Consensus: Quorum requires $Q = \lfloor \frac{N}{2} \rfloor + 1$ votes to elect a new leader and prevent split-brain partitions.
  4. Use Virtual IP (VIP) Floating for instant shift: VIP floating via Gratuitous ARP updates shifts network traffic in $< 1\text{ second}$ without waiting for DNS TTL expirations.
  5. Ensure hardware parity for standby nodes: Provision standby nodes with identical CPU, RAM, and IOPS specs as primary nodes to prevent overload crashes upon failover.

Glossary of Terms

TermDefinition
FailoverThe automated process of detecting a primary node failure and promoting a standby node to take over operations.
Split-Brain SyndromeA catastrophic state where network partitions create two active primary nodes writing conflicting data simultaneously.
STONITH ("Shoot The Other Node In The Head")A fencing technique that forcefully cuts power or revokes disk access from a failed primary node before failover.
QuorumThe minimum number of cluster nodes ($Q = \lfloor \frac{N}{2} \rfloor + 1$) required to make authoritative cluster decisions.
Virtual IP (VIP) FloatingA networking technique that assigns a single IP address to an active primary node, shifting instantly to a standby node via ARP.
Gratuitous ARPAn un-solicited Address Resolution Protocol message sent to update local network switches with a new MAC address mapping for a Virtual IP.

Practice Scenario and Self-Assessment

Architecture Scenario

You are designing the failover system for a PostgreSQL database cluster (`db.reliabilitylab.com`): **Questions**:
  1. Calculate the Quorum size required for this 3-node PostgreSQL cluster.
  2. Design the STONITH fencing sequence to guarantee zero split-brain writes when 10.0.1.10 becomes unreachable over the management network.

Interactive Self-Assessment

It guarantees the old primary is dead and cannot accept client writes, preventing Split-Brain data corruption.

It speeds up SQL B-Tree index creation on the standby node.

It automatically updates public DNS servers across all worldwide ISPs.

It compresses Write-Ahead Log files by up to 90% using Gzip.

It issues a Gratuitous ARP packet to local switches, shifting IP routing to the new node's MAC address instantly in <1s without DNS TTL delays.

It forces client browsers to clear their HTTPS TLS certificate caches.

It automatically restarts the operating system hypervisor on standby nodes.

It converts SQL database tables into un-indexed CSV disk files.


What to Learn Next

Track: Reliability and Operations

Previous: Duplicate Requests & the Idempotency Gap

Next: Fault Tolerance — Keep Working When Parts Fail

Series: Reliability & Failover

  1. Availability — Nines, Error Budgets, and Redundancy
  2. Reliability — Correct Results Under Stress
  3. Fault Tolerance — Keep Working When Parts Fail
  4. Failover — Switching to a Healthy Spare (this guide)
  5. Multi-Region Failover — Surviving a Region Outage
  6. Disaster Recovery — RPO, RTO, and Backups That Work

All series

By Shubham Jain

All articles · Study paths

Shubham Jain · Learning Lab