Health Checks and Failover

Health checks and failover: liveness vs readiness, deep vs shallow checks, failure detection, and automatic failover without flapping.

Intermediate 3 min read · Updated

Health checks let load balancers, orchestrators and failover systems decide which instances should receive traffic. Failover moves work from a failed component to a healthy one. Getting them right is subtle: checks that are too shallow miss real failures, and checks that are too deep can take down a whole fleet because of one shared dependency.

Kinds of checks

CheckQuestion it answersOn failure
LivenessIs the process alive and not deadlocked?Restart it
ReadinessCan it serve traffic right now (warmed up, connected, not overloaded)?Stop sending traffic, but don't restart
StartupHas a slow-starting process finished initialising?Wait before applying liveness checks
Deep / dependency checkCan it reach its database, cache, downstream services?Usually: report, alert; rarely: take out of rotation

Keep liveness checks trivial (the process can answer HTTP), or a slow dependency will trigger restarts that make things worse. Use readiness to stop traffic to an instance that's warming up, draining for a deploy, or temporarily overloaded.

Typical settings and examples:

  • Basic check: a service registry or load balancer calls GET /health every 10 seconds. 200 OK means healthy; a timeout or error counts as a failure, and three failures in a row remove the instance.
  • Deep check: the inventory service also tests its database connection and returns 503 Service Unavailable when the database is unreachable, so it is taken out of rotation even though the process is running. Use this with care (see the trap below).
  • Passive check: the load balancer watches real responses and marks an instance unhealthy when, say, 10% of its requests fail, catching problems that periodic probes miss.

The shared-dependency trap

If every instance's health check queries the database, a brief database slowdown makes all instances fail their checks at once. The load balancer removes them all, and a partial degradation becomes a total outage. Health checks should mostly reflect the instance itself; failures of shared dependencies are better handled with circuit breakers, fallbacks and alerts.

Load balancerInstance 1check: FAILInstance 2check: FAILInstance 3check: FAILDatabaseslow for 20 s
A deep check on a shared database turns one slow dependency into every instance failing its check simultaneously.

Many load balancers fail open: if all targets look unhealthy, they send traffic to all of them anyway, on the theory that a widespread failure is more likely a bad health check than every server dying at once. Know how yours behaves.

Detecting failure reliably

  • Require several consecutive failures before marking unhealthy (e.g. 3 checks, 5 s apart) and several successes before marking healthy again, to avoid flapping.
  • Combine active checks with passive checks (outlier detection on real traffic: eject an instance returning many 5xx or timing out).
  • Check from more than one place where possible; a single prober with a network problem can falsely declare everything down.
  • In clusters, use gossip-based or consensus-based membership rather than one central checker. See gossip and failure detection.

Failover patterns

ComponentFailover approach
Stateless app instancesLoad balancer stops routing to failed instances; orchestrator replaces them
Database primaryPromote a replica (Patroni, RDS Multi-AZ, Aurora), with fencing to stop the old primary. See leader election
Cache nodeClients rehash to other nodes (consistent hashing), or a replica is promoted (Redis Sentinel / Cluster)
Availability zoneSpread instances across zones; load balancers route around a failed zone
RegionDNS or global load balancer shifts traffic to another region. See disaster recovery

Graceful shutdown and draining

Planned failovers (deploys, scale-in) should never drop requests: mark the instance not ready, wait for the load balancer to stop sending new requests, let in-flight requests finish (with a timeout), close long-lived connections politely so clients reconnect elsewhere, then exit.

Where it’s used

Kubernetes

Liveness, readiness and startup probes per container; readiness gates Service endpoints so only ready pods get traffic.

AWS ELB / ALB

Health checks with healthy/unhealthy thresholds per target group, fail-open when all targets are unhealthy, and connection draining.

Envoy

Active health checking plus outlier detection that ejects hosts based on consecutive errors or latency.

Amazon RDS Multi-AZ

Automatic failover to a synchronous standby in another zone, usually within a minute or two, by flipping a DNS record.

In an interview

  • Distinguish liveness (restart) from readiness (route traffic) checks.
  • Avoid deep checks on shared dependencies that could fail every instance at once.
  • Use consecutive-failure thresholds and outlier detection to avoid flapping and false failovers.
  • Describe failover per component (instances, database primary, zone, region) and graceful draining for deploys.