System designCore9 min

Load balancing

Six servers, one address: how a load balancer decides which server gets each request, notices when one dies, and lets you add or remove servers without anyone noticing. And why the obvious way to share work is not the best one.

1 · The job

One address in front, many servers behind

A food delivery app gets 3,000 requests a second at lunchtime. One server cannot keep up, so you run six. But the app on every phone knows only one address, api.food.example. Something has to stand at that address and hand each request to one of the six.

That something is a load balancer. It has four jobs:

  • Spread the work so no server is swamped while another idles.
  • Route around failure: stop sending requests to a server that has died.
  • Hide change: add a seventh server, or take one out for an upgrade, without any client knowing.
  • Be one stable address for the outside world, however the servers behind it change.
PhonesWebsiteLoad balancerapi.food.exampleServer 1Server 2Servers 3 to 6PhonesWebsiteLoad balancerapi.food.exampleServer 1Server 2Servers 3 to 6

Clients know one address. Behind it, the balancer picks a server for every connection or request.

It only works if any server can answer any request. A server that keeps a user's cart in its own memory forces the balancer to send that user back to the same server every time. Keep that state in a shared store instead, and the servers become interchangeable (see Scalability).

2 · L4 or L7

A layer 4 balancer moves connections; a layer 7 balancer moves requests

Balancers come in two kinds, named after the network layer they read (see How data moves across a network). The difference decides what each can do.

Aspect

Layer 4 (TCP)

Layer 7 (HTTP)

Reads

Addresses and ports only

The whole request: path, headers, cookies

Decides per

Connection

Request

Can route by path or user

No

Yes: /menu to one pool, /orders to another

Handles TLS

Passes encrypted bytes through

Usually decrypts it, so servers don't have to

Cost

Very cheap, millions of connections

More work per request

A layer 4 balancer picks a server when a connection opens, by hashing its addresses and ports, and every byte on that connection then goes to that server until it closes. That is a problem for modern clients. HTTP/2 and gRPC send hundreds of requests down one long-lived connection, so a layer 4 balancer pins all of them to whichever server it picked first. A layer 7 balancer reads each request and spreads them, even when they share a connection.

Phone appLayer 7 balancerMenu serversGET /menu/…Order serversPOST /ordersone HTTP/2 connection/menu/ordersPhone appLayer 7 balancerMenu serversGET /menu/…Order serversPOST /ordersone HTTP/2 connection/menu/orders

Reading the path lets one address serve two pools: cheap menu reads and slower order writes scale separately.

3 · Picking a server

Round robin is fair to servers, not to requests

The obvious rule is round robin: server 1, then 2, then 3, and round again. Each server gets the same number of requests. With weighted round robin, a server twice as big gets twice as many.

It would be perfect if every request cost the same. They don't. Fetching the menu takes 5 ms; building a big order history can take 500 ms. In large real systems the most expensive requests cost a thousand times the cheapest. Round robin hands the next request to server 3 even while server 3 is still grinding through three slow ones, and server 4 sits idle.

Better rules look at how busy each server is right now:

  • Least outstanding requests: send to the server with the fewest requests in progress. It needs an up-to-date count for every server.
  • Power of two choices: pick two servers at random and send to the less busy of the two. Comparing just two gets most of the benefit of checking them all, and because each balancer picks a different random pair, several balancers don't all pile onto the same "least busy" server at once.
  • Hashing: send all requests with the same key (a user, a cart) to the same server, useful when servers cache per key (see Consistent hashing).
Random
610 ms
Round robin
495 ms
Power of two
215 ms
Least outstanding
172 ms

Slowest 1% of requests (p99), modeled: six servers at 80% busy, each working on one request at a time, request cost varying around a 10 ms median (log-normal, σ = 1). Same servers, same traffic: only the rule for picking a server changes.

Round robin is not wrong. When requests cost about the same and servers are identical, it is simple and even, which is why it is the default in most balancers. Reach for a load-aware rule when request costs vary widely.

4 · Health checks

A balancer only helps if it notices a server has died

If server 4 crashes, a balancer that keeps sending it a sixth of the traffic fails a sixth of your users. It finds out in two ways:

  • Active checks: every few seconds to half a minute it asks each server for a small page, often /health. After a couple of failures in a row the server is taken out of rotation; after several successes it goes back in.
  • Passive checks: it watches real traffic. A server whose requests keep failing or timing out is set aside for a while, for longer each time it happens again.

Between the crash and the second missed check, requests still go to server 4. With checks every 30 seconds that window is up to a minute; passive checks close it much sooner.

Three traps catch most teams:

Trap

What happens

Fix

The check tests too much

It also checks the shared database; one bad database minute fails every server at once

Check what is local, plus only dependencies the server cannot work without

The server fails fast

A broken server answers every request with an instant error, looks idle, and attracts more traffic

Count recent errors as load, or eject servers on errors

Everyone is unhealthy

Too few servers pass the checks

Most balancers fail open: they send traffic to all of them, betting a struggling server beats none

5 · Servers come and go

Add and remove servers without anyone noticing

Servers leave for upgrades and arrive when you scale out. Both moments can hurt users if the balancer handles them carelessly.

  1. Drain before removing

    Stop sending new requests to the leaving server but let the ones in progress finish. One major cloud balancer waits up to five minutes by default before cutting it off.

  2. Warm up before full load

    A new server has cold caches. A least-busy rule sees it with zero requests and floods it. Slow start ramps its share up over a few minutes.

  3. Then let the rule take over

    Once warm, the new server gets its full share like any other.

Sticky sessions (sending one user to the same server every time, by cookie) make this harder. Load stays uneven after you add servers, because existing users are stuck to the old ones, and a user whose server dies loses whatever was kept there. They are a crutch for keeping state on servers; moving that state into a shared store removes the need.

6 · The balancer itself

The balancer must not be the one box nobody copied

Every request passes through the balancer, so a single balancer is a single point of failure (see Redundancy & failure domains).

Three usual answers, from small to large:

  • A pair with a floating address. Two balancers share one virtual IP; the standby takes it over within seconds if the active one stops answering.
  • Many nodes behind DNS. Managed cloud balancers run a node in every zone, and DNS hands out all of their addresses.
  • Two tiers. At very large scale, a fast layer 4 tier spreads connections across a fleet of layer 7 balancers, which then read each request. The layer 4 tier keeps no shared state: every node computes the same choice from a consistent hash, so any of them can take any packet.
DNSLayer 4 tierconnections, hashedLayer 7 tierrequests, routedServersseveral addressesDNSLayer 4 tierconnections, hashedLayer 7 tierrequests, routedServersseveral addresses

Each tier is many machines, and each does the job it is cheapest at: layer 4 moves connections, layer 7 reads requests.

Inside a company's own network, services often skip the middle box altogether: the calling service learns the list of servers and balances its own requests (client-side load balancing). It saves a network hop and a box to run, at the price of a smarter client in every language you use.

Check yourself

5 questions

1. Your service talks gRPC over long-lived HTTP/2 connections. Behind a layer 4 balancer, one server is at 95% and the other five are nearly idle. Why?
2. Requests to your API take anywhere from 5 ms to 2 seconds. Which rule keeps the slowest requests fastest?
3. Your /health endpoint checks the shared database. The database has a 30-second hiccup. What happens?
4. After a bad deploy, one server returns an error instantly for every request. With least outstanding requests, what happens to its traffic?
5. You add a seventh server to handle lunchtime. Why might it struggle in its first minute?

Takeaways

Remember this

  • A load balancer gives many servers one stable address: it spreads work, routes around failures, and hides servers coming and going.
  • Layer 4 balances connections and is cheap; layer 7 reads each request, so it can route by path and spread HTTP/2 and gRPC requests across servers.
  • Round robin is even only when requests cost the same; when they vary, least outstanding requests or power of two choices keeps the slowest requests far faster.
  • Health checks should test the server, not the whole system; watch for fast-failing servers that attract traffic, and expect balancers to fail open when nothing is healthy.
  • Drain servers before removing them, warm them up before full load, avoid sticky sessions, and never run a single balancer.