Dev Learning

Load balancing

One address in front, many machines behind, and a way to stop sending traffic to a broken one.

1

What problem does load balancing solve?

One server can only do so much, and one day it will restart or die. A load balancer lets you put several servers behind a single address, spread requests between them, and automatically stop using any server that stops answering.

It is also what makes deploys boring. Take one server out, update it, put it back, repeat.

2

What load balancing costs you

What it costs you
It is one more hop (a fraction of a millisecond) and one more thing that can fail, so it needs a backup of its own. It also pushes you to keep no user state on your servers. If a server holds a session in memory, every request from that user has to come back to that same server, and you have lost half the benefit.

Every concept in here states a cost. A technique with no price is a technique you have not understood yet — and naming the cost out loud is most of what separates a senior answer from a junior one.

3

When to reach for load balancing, and when not to

Use it when

  • You have more than one server. That is the whole condition.
  • You want deploys and restarts with no downtime.
  • You want broken machines removed automatically by health checks.

Do not, when

  • You have one machine and downtime is acceptable. Then it is complexity for nothing.
  • The thing behind it keeps important state per connection and cannot be changed. Fix that first, or accept sticky routing and what it costs.
4

How load balancing actually works

UsersBalancerServer 1Server 2Server 3
each request lands on whichever server is free
Requests arrive at one address and land on whichever server is free. Remove a server from the pool and traffic simply stops going there.

Layer 4 balancing looks only at IP and port. It does not read the request, so it is fast and works for any protocol. Layer 7 reads the HTTP request, so it can route by path or header, handle TLS, and retry a failed request on another server. Layer 7 is the default for web traffic. Layer 4 is for raw speed and for protocols that are not HTTP.

The algorithms, in the order you should reach for them. Round robin: fine when all requests are similar. Least connections: better when some requests take much longer than others, which is common in real systems. Consistent hashing on a key: use it when you want the same user or key to keep landing on the same server, usually so its cache stays useful.

Health checks do much of the useful work. A passive check notices failing responses. An active check calls an endpoint every couple of seconds. That endpoint should test something real, like whether it can reach the database. But not so real that one slow dependency makes every server mark itself broken at the same time and takes down the whole fleet. That failure is common and embarrassing.

Sticky sessions pin a user to one server. It solves in-memory session state, and it costs you: uneven load, a messy story when you add machines, and users losing their state when a server restarts. It is better to put session state in a shared store and keep every server interchangeable.

Above the load balancer sits DNS, which spreads traffic across regions or across several balancers. Clients cache DNS for minutes, so DNS on its own is a poor way to fail over. Say that if someone suggests it.

RequestsRound robin1,2,3,1,2,3…Server 1Server 2Server 3
strict rotation — each server takes the next request in turn, regardless of how busy it is
Round robin in motion: each request goes to the next server in turn, whether or not that server is already busy.
Common trap
  • Drawing one load balancer and calling the design highly available. That box is now the single point of failure.
  • Forgetting connection draining, and dropping requests on every deploy.
  • Reaching for sticky sessions instead of taking state off the servers.
Say this
"Layer 7 balancer, least-connections, active health checks on a cheap endpoint that does not touch the database. Servers hold no session state, so any server can take any request. Cost: an extra hop, and I need at least two balancers, or the balancer is the single point of failure I just drew."
Go deeper · 18 minView more — load balancing in depth9 sections, every sub-topic covered separately, with a worked example at the end.Read it →
5

The load balancing follow-up an interviewer will ask

"How do you deploy a new version with zero downtime?"

What a strong answer sounds like

A rolling deploy through the load balancer. Take one instance out of the pool. Wait for the requests it is already handling to finish. That waiting step is called connection draining, and forgetting it is how you drop live traffic during a "zero downtime" deploy. Then update it, wait for its health check to pass, put it back, and move to the next one. Keep enough instances in the pool the whole time to carry the traffic. Two things make this work. First, the new version has to run happily beside the old one, which means database changes get split across releases: add the column, deploy, backfill, then remove the old column. Second, you need a fast way out, and rolling forward to a known-good build is safer than trying to undo a migration. Cost: every schema change now takes three deploys instead of one.

6

Your turn

60-second self-check

Your health check endpoint queries the database. The database gets slow. What happens to your fleet, and what would you do differently?

Stuck? Take one hint at a time0 of 3 used
0 / 25 characters
Worked example

Putting it together: the front door of a link shortener

Redirects have to answer in well under 200 ms, and the read path is about 4,000 requests a second with peaks near 12,000. Creating a link is rare, around 40 a second, and can afford to be slower. How should traffic reach the servers?

  1. 1
    Pick the layer

    Layer 7, because the read path and the write path want different treatment: /{code} should go to the redirect pool and /api/create to the creation pool. A Layer 4 balancer cannot tell those apart, because it never reads the path.

  2. 2
    Pick the algorithm

    Redirects are uniform: every one is a cache lookup and a 302, a millisecond or two. Round robin handles that case well. But creation involves a database write with a possible retry on collision, so those requests vary. Least connections across both pools is the safer single choice, and it costs almost nothing.

  3. 3
    Health checks

    A /healthz endpoint that returns 200 if the process is up. It deliberately does not check Redis or the database. If the cache is down, redirect servers can still serve from the database, and I do not want the balancer removing every server because one dependency is slow.

  4. 4
    Keep servers stateless

    Redirect servers hold only a small in-process cache of hot codes. Nothing user-specific lives in memory, so no sticky sessions are needed and any server can serve any request. Losing one costs a small cache warm-up, nothing more.

  5. 5
    Make the balancer redundant

    Two balancers, or a managed one. Drawing a single box in front of the fleet would make the component I added for availability the thing most likely to cause an outage.

  6. 6
    Handle deploys

    Remove an instance from the pool, drain in-flight requests, update, wait for the health check to pass, put it back. Without draining, every deploy drops the requests that were mid-flight, which is invisible in staging and obvious in production.

Result: Redirects land on any healthy server in a couple of milliseconds, a dead server leaves the pool within a few seconds, and a slow database degrades link creation without touching the redirect path at all. The costs named out loud: an extra hop of roughly half a millisecond, and two balancers to run instead of one.
7

Learn it from the source

This page gets you interview-ready. These take you to where the idea is actually specified or measured — read them when you want the real depth.

Where you will use this