sehaj/learn
System design/03 Networking and APIs

03.06 · with code · 12 min

Load balancers

How load balancers work: layer 4 versus layer 7, the algorithms that pick a server, health checks, and how to drain a server without dropping anyone. With a simulator.

Two kinds of load balancer

A load balancer can make its decision at two different levels, named after layers of the network model:

  • Layer 4 (transport) looks only at connections: IP addresses and ports. It picks a server when a TCP connection opens and passes the bytes through without reading them. Very fast and protocol-agnostic, but it can't see URLs, headers or cookies, and every request on one connection goes to the same server.
  • Layer 7 (application) ends the connection itself, reads each HTTP request, and decides per request. It can route /api/* to one group of servers and /static/* to another, add headers, terminate TLS, retry a failed request on another server, and spread requests from one long-lived connection across many servers.
Layer 4Layer 7
SeesIPs, ports, connectionsFull HTTP requests
DecidesOnce per connectionOnce per request
TLSPasses it throughUsually terminates it
Can route byAddress and portPath, host, headers, cookies
CostVery low per packetMore work per request

Large sites often use both in layers: a layer 4 tier spreads connections across a fleet of layer 7 proxies, which then route requests to applications. GitHub's setup below is exactly this.

Choosing a server

The algorithm matters most when requests or servers aren't all the same. The common ones:

  • Round robin: each server in turn. Even when every request costs the same, and blind to everything else.
  • Weighted round robin: bigger servers get proportionally more turns.
  • Random: surprisingly decent, but uneven over short stretches.
  • Least connections: the server with the fewest requests in progress right now. Adapts to slow requests and slow servers, because a server that's struggling holds more open requests and so gets fewer new ones.
  • Two random choices: pick two servers at random and send to the less busy one. Nearly as good as least connections, and it doesn't need every load balancer to have a perfectly up-to-date view of every server, which matters when there are many load balancers.
  • Hashing (by client IP, user ID, or URL): the same key always goes to the same server. Useful when servers keep a per-key cache. Plain hashing reshuffles almost every key when a server is added; consistent hashing fixes that (05.03 Consistent hashing).

Try them. Four servers, requests arriving at random, each server working through its own queue one request at a time:

LabLoad balancing algorithms

Next server in turn, whatever it's doing.

Each server, with round robin

Server 125% · q6
Server 225% · q6
Server 325% · q8
Server 425% · q7

Share of requests · longest queue seen

p99 latency, same traffic

Round robin48 ms
Random162 ms
Least connections45 ms
Two random choices49 ms

p50 with round robin: 14 ms · each request is ~10 ms of work

Try this

The slow-server case is the one to remember. Round robin keeps sending a quarter of all traffic to a server that can only handle a seventh of it, so its queue grows without limit. Algorithms that look at how busy each server is route around the problem automatically.

Health checks, in more detail

Health checks decide which servers are in the pool at all. Two kinds work together:

  • Active checks: the load balancer requests /health every few seconds, and removes a server after, say, three failures in a row; it adds it back after a few successes. The thresholds stop one slow check from flapping a server in and out.
  • Passive checks: the load balancer watches real traffic, and backs off a server that's returning errors or timing out, without waiting for the next check.

Be careful what /health tests. If it checks the database and the database blips, every server fails its health check at once and the load balancer has nothing left to send traffic to. Many teams keep the health check to "this process can serve requests" and handle dependency failures in the request path instead (03.11 Backpressure and load shedding).

Draining: removing a server politely

To deploy or retire a server, you drain it: stop sending it new requests or connections, let the ones in progress finish (up to a time limit), then take it out. Without draining, every deploy cuts off requests mid-flight. For long-lived connections like WebSockets (03.05 Realtime: polling, SSE and WebSockets), draining can take a long time, so servers usually close connections gently and let clients reconnect elsewhere.

Seen in production

GitHub

2018

GLB Director

GLB Director is a layer 4 load balancer that spreads a single IP address across many machines. It sits in front of proxies like haproxy and nginx rather than replacing them. The director tier keeps no per-connection state: it uses rendezvous hashing over a table of primary and secondary servers, and a "second chance" mechanism lets packets for existing connections reach the server that holds them while that server drains.

What we foundA stateless layer 4 tier in front of layer 7 proxies, using rendezvous hashing so proxies can be drained without breaking connections.

GLB: GitHub's open source load balancergithub.blog ↗
Predict first

One of your eight app servers has a failing disk and is answering five times slower than the others. Health checks still pass. Which algorithm limits the damage best?

Commit to an answer before you read on.

Recap

Interview questions

MidfreqMust-knowConcept

What's the difference between a layer 4 and a layer 7 load balancer?

MidfreqGood to knowConcept

Which load balancing algorithm would you choose, and why?

Report a mistakeEdit this page