Design Desk

Networking & Delivery

Load Balancers

The stateless app tier was never the bottleneck. Something still has to decide, request by request, which of ten identical machines gets it.

Networking & DeliveryFoundation7 min readPrerequisites: Scalability

After this lesson, you should be able to:

  • Explain what decision a load balancer makes on every request once an app tier is stateless and horizontally scaled.
  • Compare round robin and least-connections: what each assumes about request cost, and where that assumption breaks.
  • Diagnose why a freshly recovered or newly joined instance gets flooded under least-connections.
  • Recognize that a health check runs on an interval, not continuously, leaving a detection blind spot for a request already in flight.

At a Glance

  • The app is stateless and could run on ten machines instead of one, but something still has to decide which machine gets each incoming request.
  • The routing algorithm is the real decision: round robin, least-connections, and consistent hashing all spread load differently, and the wrong one recreates the hot spot you added machines to avoid.
  • A health check failing mid-request isn't hypothetical. A request already in flight to a dying instance either gets retried somewhere else or it doesn't, and that has to be decided deliberately, not left to whatever the default happens to be.

01 The Problem

A webinar platform's app tier is stateless, sessions live in Redis, nothing important sits in one process's memory, so running ten instances instead of one should be trivial. It is trivial to run ten instances. What isn't trivial: a client only knows one hostname, so something in front of those ten instances has to receive every request and decide, per request, which instance actually handles it.

Two ways that decision goes wrong, both real. Round robin sends the first request to instance 1, the second to instance 2, on down the line, evenly, in order, regardless of what each instance is currently doing. During a big webinar, a handful of clients open long-lived signaling connections that each hold an instance busy for the length of the call, and round robin keeps handing that instance new requests anyway, unaware it's already loaded, while an instance two slots over sits nearly idle. Separately, mid-request, one instance starts failing its health check, maybe it's out of memory, maybe a deploy is rolling through it, and a request already in flight to it either gets retried somewhere else immediately or the client just gets a dropped connection. Nothing about "add a load balancer" answers which of those two happens by default.

This is what a load balancer actually has to get right: not just spreading requests around, but which algorithm fits how your requests behave, and what happens the moment one of the machines it's routing to stops answering.

02 What It Is

A load balancer sits in front of a pool of identical, interchangeable instances and decides, for every incoming request or connection, which instance handles it. It does that with a routing algorithm, the rule for picking a target, and a health check, a way to notice an instance has stopped answering and stop sending it traffic. Round robin cycles through instances in order. Least-connections sends each request to whichever instance currently has the fewest open requests. Consistent hashing routes based on a hash of something in the request, a client ID or session key, so the same client tends to land on the same instance. Each optimizes for a different shape of traffic.

Choosing the load balancer, the product or the software, is the easy part. The routing algorithm is the real engineering decision, because it encodes an assumption about your traffic: that every request costs about the same (round robin's assumption, wrong for the webinar's long-lived connections), or that current open-connection count is the best signal of capacity (least-connections), or that the same client landing on the same instance matters more than perfectly even load (consistent hashing). Pick the algorithm that matches an assumption your traffic doesn't meet, and you've rebuilt the exact hot spot the extra instances were supposed to fix.

In Plain Terms

A restaurant host seats parties as they arrive by walking down the row of tables in order, one, two, three, one, two, three, whether or not table two still has four people lingering over dessert. A host who actually looks at which tables are free before seating the next party solves that, but only if she checks often enough, and a brand-new table that just got wiped down looks empty even in the ninety seconds before someone finishes carrying dishes to it, so it can get seated with a party before it's actually ready.

03 How It Works

Rendering diagram…

Round robin sends the next request to whichever instance is next in line, busy or not. Least-connections checks current load first. And a health check failing mid-request means a request already sent to that instance needs an explicit retry policy, the load balancer noticing the failure doesn't automatically mean the request already in flight gets saved.

Try it: switch to Least Connections, mark a server down, send a few requests to the remaining three, then mark it back up and send one more.

Algorithm
Load Balancer

Server 1

0

connections

Up

Server 2

0

connections

Up

Server 3

0

connections

Up

Server 4

0

connections

Up

No requests sent yet.

04 Trade-offs

 Round robinLeast-connections
What it optimizes forEven request count across instancesEven current load across instances
Assumption it makesEvery request costs about the same to handleCurrent open-connection count is a good proxy for how busy an instance actually is
Where it breaksLong-lived or expensive requests pile up on one instance while others go idleA brand-new instance with zero connections looks like the least busy one and can get flooded the instant it joins, before its real capacity is proven
SimplicityTrivial to implement and reason aboutNeeds the load balancer to actively track live connection counts per instance

Watch out

It's tempting to think adding a load balancer is the whole fix: traffic's spread out now, done. The health check has a blind spot people underrate. It runs on an interval, every few seconds, not continuously, so an instance can fail the instant after it last passed, and every request routed there in that window fails too, before the load balancer even knows to stop sending traffic. Spreading load evenly and detecting failure fast are two different problems, and a load balancer that's good at one isn't automatically good at the other.

05 Where You'll Actually See This

The URL shortener case study's app server was already established as stateless in scalability, specifically so more instances could be added without a rewrite. A load balancer is what makes that actually work: it's the thing sitting in front of those instances deciding which one gets each request, the piece scalability's own argument assumes exists but never draws.

CDN is this same routing question at a different scale: a load balancer picks the best instance within one data center, a CDN picks the best edge location on the planet for a given user. Same underlying decision, which nearby, capable node should actually answer this, just applied across machines in one rack versus across the globe.

Rate limiting sits in the same request path but isn't answering the same question. A load balancer distributes however much traffic shows up across however much capacity exists. It has no opinion on whether that traffic should exist in the first place. Rate limiting is the thing capping total demand per client, and neither one substitutes for the other, perfectly distributed load doesn't help if one client is generating enough of it to need capacity that isn't there.

API gateways sit near the same point in the path too, and it's worth being precise about the difference rather than treating the two as interchangeable. A load balancer distributes requests across identical instances of one service. A gateway decides which different service a request belongs to in the first place, on top of auth, rate limiting, and logging along the way. A real system usually has both, layered, not one standing in for the other.

Both of those take it as given that there's more than one service to route to. Microservices architecture is the article that actually argues for that split, and what it costs to get there.

The Watch Out above named a real gap: spreading load and detecting failure fast are different problems. Health checks & readiness probes closes half of it, not the polling-interval blind spot itself, that's still real, but what "failing" is actually supposed to mean. A health check treated as one undifferentiated pass-or-fail signal is exactly how a warming-up instance ends up looking identical to a genuinely broken one.

How to reason about it

When asked how a load balancer works, don't stop at "it spreads traffic across instances." Name the algorithm and say why it fits the traffic: round robin for uniform, cheap requests, least-connections for requests that vary in cost, consistent hashing when the same client needs to keep landing on the same instance. Then say what happens to a request in flight when a health check fails, because that's the part people forget to design for until it happens in production.

06 Quick Check

A team switches from round robin to least-connections hoping to fix uneven load, then notices a newly deployed tenth instance gets slammed with traffic the moment it joins the pool, more than its fair share. What's the most likely explanation?