Skip to main content
Load Balancinglesson 4 of 4 · 2 min read

Health Checks and Failure Handling

Asking whether a machine is alive

A balancer still routing to a dead server is worse than no balancer, so it keeps asking.

Every few seconds each machine gets a probe, usually a request to /health that should come back with a 200. Miss a few in a row and the machine leaves the rotation. Pass a few in a row and it earns its way back.

Those two knobs are the whole story. because they are the whole story. How long you serve errors is roughly the interval times the number of failures you tolerate. Check every 10 seconds, allow 3 failures, and real people eat errors for up to 30 seconds. Tighten them and a brief pause on a healthy machine ejects it. Loosen them and outages linger.

What /health should check

Decide carefully what /health actually checks, because this is where teams take themselves down. A shallow check answers 200 if the process is running.

A deep check tries the database and the cache first. That feels more honest, and it is how you lose an entire fleet at once. The database stutters for 20 seconds, every machine fails its check together, the balancer ejects all of them, and a blip becomes a full outage that outlasts the blip.

Shallow checks belong on your balancer and deep dependency checks for your monitoring, which is the convention almost everyone lands on eventually.

Failure handling goes past detection. Draining lets a machine you are removing finish the requests it already has instead of cutting them off mid-response.

Some balancers also watch real traffic and pull a machine that starts throwing errors faster than the probe cycle would. And one that fails open, sending traffic everywhere when every single check fails, is making a sensible bet that the checks are lying. A whole fleet rarely dies at the same instant.

the shape of it
Load balancerprobe every 10 sServer 1200 OKServer 23 failed checksServer 3200 OKGET /healthejectedGET /health
After three failed probes the balancer stops routing to server 2 until it passes checks again.
the endpoint that decides whether your fleet stays up
Java
// Shallow: is this process alive and serving? That is all the
// balancer needs to know.
get("/health", (req, res) -> res.status(200).body("ok"));

// Deep: checks the database too. Looks more honest, and when the
// database blips for 20 seconds every machine fails together and
// the balancer ejects the entire fleet.
get("/health/deep", (req, res) -> {
  db.ping();                    // use this for monitoring,
  cache.ping();                 // never for the balancer
  return res.status(200);
});

// Detection lag = interval x threshold. 10s x 3 = up to 30 seconds
// of real users getting errors before the machine is pulled.

Worked example

Tariq gets paged at 3:40 am: the site is serving 503s from the ALB itself, no healthy targets. All 8 API nodes are running fine. The timeline reconstructs cleanly. RDS ran a 25-second failover at 3:31. The team's /health endpoint did a SELECT 1 against that database, so all 8 nodes failed 3 consecutive checks together and the ALB ejected the entire fleet. The database recovered at 3:32, but each node then needed 3 passing checks at 10-second intervals to be re-admitted, so the outage stretched to nearly 4 minutes for a 25-second blip. The fix: /health now returns 200 whenever the process can serve requests, and database connectivity moved to a CloudWatch alarm that pages a human instead of the balancer.

Load Balancing: wrapping up

In the real world

  • 01Google's Maglev paper describes the software L4 balancers fronting Google services since 2008, using consistent hashing so connections survive individual balancer failures at millions of packets per second per machine.
  • 02AWS splits the layers into products: NLB for L4 TCP and UDP at very high connection counts, ALB for L7 path routing, host routing, and weighted target groups used for canary releases.
  • 03GitHub built GLB, an L4 director tier feeding HAProxy at L7, specifically so they could drain and patch individual proxies without killing long-lived Git connections.
  • 04Cloudflare's Unimog balances at L4 across every server in an edge data center, and any machine can forward packets, so balancing capacity grows with the fleet itself.
  • 05Netflix's Zuul is an L7 gateway doing routing, canary traffic shaping, and retries in front of its microservices, the same request-reading role an ALB plays in smaller stacks.

Questions people ask

Isn't the load balancer just a new single point of failure?

It would be if you ran exactly one. Production setups run balancers in redundant pairs with a floating IP, or active-active behind DNS or anycast. Managed balancers like AWS ALB spread themselves across availability zones behind one name. The balancer's failure domain ends up smaller than any single app server's.

When is round robin not good enough?

When request durations vary a lot. Round robin balances the number of requests per server, not the amount of work, so a few slow requests can pile onto one node while others idle. Least connections handles that case by routing to whichever backend has the fewest requests in flight.

Should my health check endpoint test the database?

Not the one the load balancer uses. If every node's check depends on the database, a short database blip fails all checks at once and the balancer ejects the whole fleet, turning a 20-second hiccup into minutes of total downtime. Keep the balancer check shallow and alert on dependencies separately.

Quick review

Round Robin:
requests cycle through servers equally. Assumes equal capacity
Weighted Round Robin:
assign weights (e.g., 3:1 for different hardware). Admin-configured
Least Connections:
route to server with fewest active connections. Best for variable request lengths
Least Response Time:
route to fastest-responding server. Dynamic and self-correcting
IP Hash / Consistent Hash:
same client always hits same server (sticky sessions without cookies)
L4 (Transport):
routes by IP+port, can't inspect content. Ultra-fast. AWS NLB, HAProxy TCP mode
L7 (Application):
routes by URL path, headers, cookies. Can do A/B, canary, host-based routing. AWS ALB, Nginx
Health checks:
LB polls /health every 5 to 30 s, ejects a node after 2 to 3 consecutive failures, re-adds after it passes again. Detection lag = interval × threshold
the trade-off

LB itself becomes a potential SPOF. Deploy in HA pairs (active-passive or active-active).

in the room

Any system with ≥2 backend servers. Usually the first step when scaling beyond one machine.