Systems Engineering

WritingSystem Design

How a Load Balancer Spreads the Load

Part one of load balancing strategies: the algorithms that spread requests evenly, round-robin, weighted, least-connections, least-response-time, and the power of two choices, each with its own visualizer.

By Sachin Gupta7 min read
Portrait of Sachin Gupta rendered in binary

A dedicated look at the load-balancing algorithms that spread traffic across servers. The static-vs-dynamic split, then each rule in turn with its own analogy and visualizer: round-robin, weighted round-robin, least-connections, least-response-time, and power-of-two-choices. Part one of two; part two covers the sticky ones (hashing and affinity).

The load balancer piece introduced its four jobs (load balancers, up close). The very first, choosing which server gets each request, is a small topic on its own, so here it is. This part covers the rules that spread traffic evenly; the next covers the sticky ones.

Back to the restaurant host at the door. A party arrives; which table do they get? There is no single answer, just a handful of simple rules, and each makes a different tradeoff. Before the rules, one split that sorts them all.

Static rules pick a server by a fixed formula (whose turn is it, what is its weight) without looking at how busy anyone is. They are cheap and predictable. Dynamic rules watch live signals (how many connections, how fast each server is answering) and steer toward the least loaded. They are smarter but need bookkeeping. Keep that split in mind as we go.

The family of load-spreading strategies, drawn as a tree. It splits into two branches. Static rules, which ignore live load: round-robin, weighted round-robin, and random. Dynamic rules, which watch live load: least connections, least response time, and power of two choices. A note reads: static is cheap and predictable; dynamic is smarter but needs bookkeeping.

Round-robin

Picture dealing cards around a table: one to each player in turn, then back to the first. That is round-robin. The load balancer keeps a pointer and sends each new request to the next server in order, around and around. It is the simplest rule there is, and it spreads requests perfectly evenly when the servers are identical and every request costs about the same.

Interactive · Round-robinOpen in Visualizers →
round-robinstaticauto
request#0load balancerround-robinserver 10 routedserver 20 routedserver 30 routed

Round-robin: each server gets the next request in turn, like dealing cards. Even and simple, but it ignores how busy each one is.

Its weakness is what it ignores: it does not care that one server is bogged down with a slow request. A busy server still gets its turn.

Weighted round-robin

Now imagine some players have a bigger table and can take more cards. Weighted round-robin deals in proportion to a weight you give each server, so a machine with twice the capacity gets twice the share. It is how you mix server sizes, and how you do canary releases: give the new version a weight of 1 and the old one a weight of 9, and only a tenth of traffic tries the new one.

Interactive · Weighted round-robinOpen in Visualizers →
weighted round-robinstaticauto
request#0load balancerweighted round-robinserver 1w=1 · 0server 2w=2 · 0server 3w=3 · 0

Weighted round-robin: bigger servers get a bigger share of the rotation. Server 3 has weight 3, so it is dealt three times as often as server 1.

The catch is that the weights are set by hand and do not react to real load. Wrong weights just entrench the imbalance.

Least connections

Here is the first dynamic rule. A good host seats the next party at the emptiest table, not just the next one in line. Least-connections sends each request to the server currently handling the fewest connections. It shines when requests are long-lived and uneven, like database sessions or WebSockets, where a plain rotation would pile long jobs onto an already-busy server.

Interactive · Least connectionsOpen in Visualizers →
least connectionsdynamicauto
request#0load balancerleast connectionsserver 124 connsserver 248 connsserver 38 conns

Least connections: send the request to whichever server is handling the fewest right now. Good when connections are long-lived and uneven.

Its blind spot: connection count is a rough guess at real work. A server with few but very heavy connections still looks idle.

Least response time

Sharper still: send the request to the server that is answering fastest right now. Least-response-time watches how quickly each server replies and favors the quick ones, which naturally steers traffic away from a server that is struggling, even before it fails a health check. It is the most responsive to trouble, but it needs constant latency measurement, and noisy numbers can make it flip-flop.

Interactive · Least response timeOpen in Visualizers →
least response timedynamicauto
request#0load balancerleast response timeserver 145 msserver 295 msserver 365 ms

Least response time: send to the server answering fastest right now. It naturally steers away from a slow or struggling server.

Power of two choices

The clever one, and the modern default. Checking every server to find the truly least-busy is expensive when there are thousands, and when many load balancers do it at once they can all pile onto the same "least-busy" server. The trick: do not check them all. Pick just two servers at random, and send the request to the less busy of the two. That tiny bit of choice gets you almost the evenness of checking everyone, for almost none of the cost, and it avoids the herd.

Interactive · Power of two choicesOpen in Visualizers →
power of two choicesdynamicauto
request#0load balancerpower of two choicesserver 124 connsserver 248 connsserver 38 conns

Power of two choices: pick two servers at random, then send to the less busy of the two. Almost as good as checking them all, at a fraction of the cost.

You will see this under different names: HAProxy calls its random this, Envoy uses it for its least-request policy, and NGINX writes it random two least_conn.

Where this goes

Those are the rules that spread traffic evenly: round-robin and its weighted form (static), and least-connections, least-response-time, and power-of-two-choices (dynamic). The other half of load balancing does the opposite: it tries to send the same client, or the same request, to the same server every time. That is stickiness, and it is part two: how a load balancer sticks.

Related