One fleet, one trace, four policies. A latency-aware balancer routes to whichever backend answers fastest — and the fastest answer in a fleet is an error. Every latency metric ranks the policies backwards. A study guide for load_balancer.py.
Python 3.15.0a8 (main, Apr 14 2026, 14:20:41) [Clang 22.1.3], stdlib only.
cd load-balancer
python3 demo.py # the aha (§6) — about 40 seconds
python3 test_load_balancer.py # pins every number this page claims
A load balancer takes a stream of requests and a set of backends and decides, per request, which backend gets it. The policies are famous and the comparison is a standard interview question: round-robin is dumb and fair, least-connections adapts to load, and latency-aware policies adapt to speed. The obvious lesson — different policies give different tail latency — is true, easy to demonstrate, and close to worthless, because it is predicted by the word "policy."
This toy exists to show you that the comparison runs the other way. It builds one fleet of four backends and routes one arrival trace through it with four policies, and the thing worth seeing is that every latency metric you could put on a dashboard ranks the four policies in the exact reverse of their goodput. The policy with the best p99 delivers 5 successful requests out of 6407.
The mechanism underneath is one sentence: a latency-aware balancer sends traffic to whichever backend answers fastest, and the fastest answer in a fleet is usually an error. A backend that has fallen over does not hang; it returns 503 in a microsecond, from a code path that touches no database. To a policy that measures round-trip time, that backend is not sick, it is excellent, and it gets promoted. The healthier a backend actually is, the slower it looks, and the less traffic it receives.
Google's SRE book names this exact failure and calls it sinkholing:
if a task is seriously unhealthy, it might start serving 100% errors. Depending on the nature of those errors, they may have very low latency; it's frequently significantly faster to just return an "I'm unhealthy!" error than to actually process a request.
So the toy runs one fleet, one trace, one set of policies, and flips exactly one boolean: is the fast backend genuinely fast, or is it broken? §6.1 shows that the balancer cannot tell — not "struggles to tell," but makes provably identical routing decisions in both worlds, down to the ninth decimal of its internal state.
By the end you should be able to:
You have one stream of requests and N machines that can serve them. Someone must choose. The choice is made millions of times a second, usually in a proxy or a client library, and it has to be cheap.
The naive answer, round-robin, hands out requests in rotation. It is O(1), needs no state beyond a counter, and is perfectly fair in request count. Its weakness is that request count is not the thing you care about. A backend on older hardware, a backend sharing a host with a noisy neighbour, a backend that just started and has a cold cache — all of them get exactly as much work as the fastest machine in the fleet, and the slow ones build queues while the fast ones idle.
So every serious balancer reads some runtime signal and steers by it:
least_conn and (via P2C) Envoy's LEAST_REQUEST.The competing goals that make more than one design defensible:
alpha in an EWMA is entirely about this trade.And here is the seam this toy pries open. Every one of those signals is a proxy for the question you actually want answered, which is "how much useful work can this backend do for me?" Latency is a proxy for it. In-flight count is a proxy for it. Both proxies are excellent while every backend is trying to serve you, and both inverted the instant one stops.
Note what is deliberately not in this toy: retries, health checks, and circuit breaking. Those are the remediations, and this page is about the signal. ../circuit-breaker/ already owns retry storms and the all-or-nothing load shedder; adding retries here would let you blame the goodput collapse on the retry policy, which would be wrong and would hide the mechanism.
None of this is deep, but the commentary below leans on it.
| Concept | Where it's used here | One source |
|---|---|---|
| Peak EWMA | PeakEwma.score / .observe — a moving average of RTT scaled by outstanding requests, copied from Finagle | Finagle: Clients § load balancing |
| Power of d choices | ScorePolicy.pick — sample d backends, take the best. The classic result is that d=2 is a huge win and d>2 barely helps | Mitzenmacher, Richa & Sitaraman: The Power of Two Random Choices |
| Goodput vs. throughput | §6.2 — total counts responses, good counts useful ones. The whole aha is the gap | Goodput |
| Sinkholing / the fail-fast black hole | §6.1 — the failure mode the toy reproduces, named in Google's SRE book | Google SRE Book: Load Balancing in the Datacenter |
| Outlier detection | §7.3 — the production fix: eject on consecutive 5xx or on success rate | Envoy: Outlier detection |
| Little's law | Backend.dispatch — why in-flight count is a usable proxy for load at all: L = λW | Little's law |
| Injected time and randomness | simulate(policy, fleet, ticks, lam, seed) — integer ticks and one seeded RNG, no clock anywhere | random.Random |
The two that carry the result are goodput vs. throughput and peak EWMA. If you only internalise one row, take the first: this toy's four policies all deliver about 6407 responses, and the number of them worth having ranges from 4806 to 5. Every metric in §6.2 that fails to see the incident is a metric computed over responses rather than over useful responses.
Before any code. What each policy can see, and what it therefore cannot:
Now the fleet, and the one bit that changes:
The slogan to carry: a latency-aware balancer is a machine for finding the fastest backend, and a broken backend is the fastest thing in your fleet. Speed is being used as a proxy for capacity, and that proxy is exactly inverted by failure — because failing is the cheapest thing a server can do.
The file is 218 lines. Read it in this order.
Backend — the one equivalence the whole toy rests onclass Backend:
"""One server: FIFO, `slots` requests in service at once, work-conserving.
`latency` is how long a call takes, in ticks. `healthy=False` means every
call *fails* after exactly that long -- a broken backend that is, to
anything measuring response times, indistinguishable from a fast one.
That equivalence is the whole toy, and `dispatch` is where it lives: the
duration does not depend on `healthy`, only the verdict does.
"""
Start here, because everything else is machinery and this is the claim. Then read dispatch, which is where the docstring is cashed out:
def dispatch(self, now, rng):
"""Accept one request at tick `now`. Returns (latency, ok).
The request waits for the earliest-free slot, so a saturated backend
makes new arrivals queue and its observed latency climbs.
"""
i = min(range(self.slots), key=lambda k: self.free_at[k])
start = max(now, self.free_at[i])
done = start + self.draw(rng)
self.free_at[i] = done
self.outstanding.append(done)
self.sent += 1
if self.healthy:
self.ok += 1
else:
self.err += 1
return done - now, self.healthy
Read the branch on self.healthy carefully, and note where it is not. done — the completion tick, and therefore the latency — is computed on line 71 from self.draw(rng), which never looks at healthy. The branch on healthy appears only afterwards, and only to increment a counter and pick the second element of the return tuple.
That is the modelling decision the whole page rests on, so it is worth defending. It says: a broken backend takes exactly as long to fail as a healthy backend of the same configured latency takes to succeed. In the demo the broken backend is configured at 1 tick against the healthy 10, which is the realistic direction — a 503 from a connection-refused or a tripped-breaker code path is faster than real work, not slower. The toy lets you set the failure latency to anything, and §6.5 sweeps it, which is where the boundary comes from.
test_failing_takes_exactly_as_long_as_succeeding (test_load_balancer.py:29-34) pins it: Backend("g", 7, True).dispatch(0, None) returns (7, True) and Backend("b", 7, False).dispatch(0, None) returns (7, False) — same 7.
RoundRobin — the control, and why it is immuneclass RoundRobin:
"""The dumb one. Reads no signal at all, so no signal can mislead it."""
name = "round-robin"
def __init__(self):
self.i = -1
def pick(self, fleet, now, rng):
self.i = (self.i + 1) % len(fleet)
return fleet[self.i]
Eleven lines, and it wins §6. pick takes fleet, now and rng and uses len(fleet) and nothing else — no backend attribute is read anywhere. It cannot route around a slow backend, and it cannot be lured by a fast one. This is the sense in which the toy's result is not "round-robin is good": it is that round-robin's immunity and its stupidity are the same property, and you cannot buy one without selling the other.
ScorePolicy.pick — one method, three policies, and the d knob def pick(self, fleet, now, rng):
cands = fleet if self.d is None else rng.sample(fleet, min(self.d, len(fleet)))
self.last = cands # so the demo can count won-vs-sampled
return min(cands, key=self.score)
Everything except round-robin is "compute a score, take the minimum," so the toy writes that once and lets subclasses supply score. The d parameter turns any of them into its power-of-d-choices variant over the same score, which is what makes §6.3 an apples-to-apples measurement rather than a comparison of two different algorithms.
Two details that are load-bearing in ways worth flagging now:
min is a first-wins tie-break. When several backends tie on score, min returns the earliest in the list. For LeastConn at light load, ties are the common case — most backends are idle — so its behaviour is decided by list order, and §7.2 measures how much: the same fleet gives least-conn a 33.20% or an 85.38% share depending only on where the broken backend sits. PeakEwma almost never ties, and is position-independent (99.92% either way). This is why least-conn is a supporting character on this page.
self.last is instrumentation, not mechanism. It records the candidate set so demo.py can count how often the broken backend won a sample it appeared in (§6.3). Deleting it would not change a single routing decision.
PeakEwma — where the toy's thesis livesclass PeakEwma(ScorePolicy):
"""Finagle's peak-EWMA: an exponentially weighted mean of observed
latency, scaled by the queue the next request would join. This is the
policy the toy is about, and `observe` is why -- it learns from the
duration of a call and never from its outcome.
"""
base = "peak-ewma"
def __init__(self, d=None, alpha=0.2):
ScorePolicy.__init__(self, d)
self.alpha = alpha
def score(self, b):
return b.ewma * (b.inflight() + 1)
def observe(self, b, latency, ok):
b.ewma = (1 - self.alpha) * b.ewma + self.alpha * latency
This is not invented. Finagle's own documentation describes it as a "moving average over an endpoint's round-trip time (RTT) that is highly sensitive to peaks," which is "then weighted by the number of outstanding requests" — line for line, score and observe.
observe takes ok and ignores it. That signature is the toy's whole argument in one line. The parameter is there because the simulator has the outcome and hands it over; the policy simply has no use for it. Every real latency-aware balancer has the same shape, because RTT is what a client can measure without agreeing on anything with the server.
test_the_balancer_never_reads_the_outcome (test_load_balancer.py:37-45) proves it directly rather than by inspection: feed two backends the identical latency with opposite verdicts, and their ewma and their score come out equal.
The (b.inflight() + 1) term is the only thing standing between this policy and unconditional winner-take-all, and it is worth knowing exactly how much it buys. With ewma at 1 for the broken backend and 10 for the healthy ones, the broken backend keeps winning until its in-flight count reaches 9 — at which point 1 × 10 = 10 finally ties a healthy backend's 10 × 1. At the demo's arrival rate the broken backend's in-flight count is essentially never above 1, so the term never fires. §7.5 measures what happens when you let backends serve more than one request at a time, and it is worse, not better.
PeakEwmaOverSuccess — the fix, as a diffclass PeakEwmaOverSuccess(PeakEwma):
"""The fix (§7.3): divide the score by the backend's observed success
rate, Laplace-smoothed so a backend with no history is not divided by
zero. One term, and it is the only term in the toy that reads `ok`.
"""
base = "ewma/success"
def score(self, b):
rate = (b.ok + 1) / (b.ok + b.err + 2)
return b.ewma * (b.inflight() + 1) / rate
The subclass overrides score and nothing else, so the diff against the broken policy is exactly one division. A backend failing everything has a smoothed success rate near 0, so its score goes to infinity and it stops being chosen. §7.3 measures it: goodput 4806 → 5 → 5871.
The Laplace smoothing (+1 over +2) is not decoration. Without it a brand-new backend has 0/0, and with a plain ok/(ok+err) a backend whose first request happened to fail would be divided by zero and permanently exiled on a sample of one.
simulate — the loop, and the order of the three lines that matter for now in range(ticks):
for b in fleet:
b.retire(now)
for _ in range(poisson(rng, lam)):
b = policy.pick(fleet, now, rng)
latency, ok = b.dispatch(now, rng)
policy.observe(b, latency, ok)
samples.append((latency, ok))
retire runs first, so in-flight counts reflect completions up to now before any routing decision is taken. Then arrivals: pick, dispatch, observe.
The thing to notice is that observe is called with the latency of a request that has just been scheduled, not one that has finished. The balancer learns the request's eventual latency immediately. This is a deliberate simplification and it is generous to the balancer — a real client only learns an RTT when the response arrives, so its EWMA lags. Giving the policy instant, perfect latency feedback removes "the signal was stale" as an explanation for anything on this page. The black hole in §6.1 happens to a balancer with better information than any real one has.
demo.py builds four backends — three answering in 10 ticks, one in 1 tick — and routes one Poisson arrival trace (λ=0.32, seed 1, 20000 ticks, 6407 requests) through them with four policies.
The offered load is worth a sentence. Three backends at 10 ticks each serve 0.30 requests/tick between them; the arrival rate is 0.32. So the healthy part of the fleet is just saturated and needs the fourth backend's help — this is not a fleet with acres of spare capacity where routing could not matter.
Read the fast3= column down both blocks. Every policy routes identically in both worlds — 24.99%, 33.20%, 50.40%, 99.92%, twice. The p99 columns are identical too. The only column that moves is goodput.
Then read the side-by-side block, which makes it exact rather than approximate: the same [4, 1, 0, 6402] split, and the same EWMA vector to nine decimal places, 1.303659381. Not "similar behaviour" — the balancer's entire observable universe is bit-identical in a world where it is doing the best possible job and a world where it has caused a total outage. test_broken_backend_is_invisible_to_the_balancer (test_load_balancer.py:50-66) asserts all of it, and test_all_four_policies_route_identically_in_both_worlds extends it to the other policies.
Now derive the two headline numbers.
Goodput 5. Not a statistic — the literal sum of the three healthy backends' request counts, 4 + 1 + 0 = 5. Every request that succeeded is a request that peak-EWMA sent somewhere other than fast3, and it did that five times in 20000 ticks. Note ok2 received zero requests in the entire run: a healthy, working machine that the balancer never used once.
Round-robin's 4806. Round-robin gives each backend a 1-in-4 share, so three quarters of the traffic lands on a healthy backend: 6407 × 3/4 = 4805.25, and the integer split gives 4806.
4806 / 5 = 961.2. Choosing the sophisticated policy over the dumb one cost three orders of magnitude of goodput.
And it did so while improving p99 from 41 ticks to 3 — a 13.67× win on the metric, delivered by the same decisions that caused the outage.
Three orderings of the same four runs. The first two are the exact reverse of the third: best p99 is worst goodput, worst p99 is best goodput. test_p99_ranking_is_the_reverse_of_the_goodput_ranking asserts the two endpoints of that reversal.
The reason the first ranking is upside-down is not subtle once you see it: a fast error is a fast response, and p99 is computed over responses. Sending 99.92% of your traffic to something that answers in 1 tick is a magnificent way to improve a latency percentile.
The second ranking is the one worth sitting with, because it is the obvious objection and it fails. "Fine — exclude the errors and measure only the requests that worked." Peak-EWMA still comes first, at 10 ticks against round-robin's 43. The five surviving requests went to idle healthy backends and were served immediately, with no queueing, precisely because all the other traffic had been diverted into the black hole. The policy's successes get faster as it destroys more of them.
So there is no filter on a latency histogram that reveals this incident. The only metric that moves is the count of useful responses, and that is a different measurement, not a different cut of the same one. test_throwing_the_errors_away_does_not_help pins the four values [43, 19, 23, 10].
The standard fix when a "least X" policy misbehaves is power-of-d-choices: sample d backends at random, take the best of those. Mitzenmacher, Richa and Sitaraman's survey gives the famous result — with d = 2 "the maximum load is log log n / log d + Θ(1)", so two choices is a huge improvement over one, "while each additional choice beyond two decreases the maximum load by just a constant factor."
Here is the same knob on the same fleet.
There are no diminishing returns and no sweet spot at 2. The broken backend's share is a straight line in d, and the derivation is one step.
Look at won|sampled: 1692/1692, 3255/3255, 4871/4871, 1577/1577. Of every sample that contained the broken backend, the broken backend was chosen — all of them. It is not likely to win a comparison, it is guaranteed to, because its score is 1 × (0 + 1) = 1 against a healthy backend's 10 × (q + 1) ≥ 10. So the only question is whether it appears in the sample at all, and for a uniform sample of d from N that probability is d/N:
d=3, N=4 predicts 75.00% and measures 75.98%. d=8, N=16 predicts 50.00% and measures 50.24%. d=12, N=16 predicts 75.00% and measures 75.59%. test_power_of_d_choices_is_the_blast_radius asserts the N=4 row to two decimals, and test_broken_backend_wins_every_sample_it_appears_in asserts the 4871/4871.
The two rows that end in 6313/6315 and 6441/6449 are the only ones where the broken backend ever lost a sample — 2 times in 6315, 8 in 6449. Those are the ticks where its in-flight count did briefly reach 9 and the (inflight+1) term fired, exactly as §5.4 predicts.
The classic result is not wrong; it is answering a different question. It concerns the maximum queue length when every server is trying to serve you, and there d=2 is close to optimal. This toy adds one server that is not trying, and the same knob becomes a dial on how much of your traffic finds it. Sampling more widely is only a virtue when the thing you are sampling for is real.
One division (§5.5) takes goodput from 5 to 5871 — and past round-robin's 4806, which is the satisfying part. ewma/success is the only policy here that beats the dumb one, because it keeps the latency steering that makes peak-EWMA good in the healthy world and stops the steering from being inverted by failure.
Note its p99 is 70, the worst on the page. Having stopped throwing 99.92% of requests into a 1-tick hole, it has to serve them on backends that take 10 ticks and now have queues. That is not a regression; it is the price of the work actually getting done, and it is a compact demonstration that on this fleet p99 and goodput are in direct tension — you can buy any p99 you like by discarding enough traffic.
The broken backend still gets 8.37% rather than 0%, because the smoothed success rate needs evidence and the score is a soft penalty, not an ejection. Real implementations add the missing piece: Envoy's outlier detection ejects a host outright on consecutive 5xx or on statistical success-rate outliers, and gRPC's weighted-round-robin computes weight = qps / (utilization + eps/qps * error_utilization_penalty) — an error term in the denominator, which is the same move as this toy's division.
Everything above depends on failing being faster than succeeding. Sweep that, and the effect does not merely fade — it crosses zero and changes sign.
Read the e = s column: 23.9%, 25.3%, 25.0%, 25.0%. The crossover is exactly where the error latency equals the service latency, for every service time from 5 to 40, and at that point the share is 1/N — peak-EWMA has become round-robin, because a failure that takes as long as a success is invisible to a signal made of durations. The goodput agrees: 4785 against round-robin's 4806, a 0.4% difference. test_boundary_error_latency_equal_to_service_latency pins it.
Past the crossover the sign flips. At e=50 against s=10 — a backend that fails by hanging, which is the other classic failure mode — the broken backend's share falls to 5.95% and peak-EWMA delivers 6026 against round-robin's 4806, a 25.4% win. The policy that caused the outage in §6.1 is, on the same fleet with the failure mode reversed, the best thing you own: it detects and routes around a hanging backend that round-robin keeps feeding forever. test_the_sign_flips_past_the_boundary pins 6026, 4806 and 5.95%.
So the honest statement, and the one that lets you place your own system:
Latency-aware load balancing is the best failure detector you own for failures that are slow, and the worst thing you own for failures that are fast. The dividing line is one question: does failing take longer than succeeding?
And the uncomfortable part is which side of that line modern infrastructure sits on. Fail fast is advice everyone follows. Connection refused is instant. A tripped circuit breaker (see ../circuit-breaker/) rejects without attempting the call, which is the point of it. A pod with no ready container RSTs immediately. Every one of those is a system that has been carefully engineered to fail on the left-hand side of the boundary — which is the side where the balancer aims traffic at the failure.
The demo output is byte-identical across runs and across PYTHONHASHSEED values 0, 1, 42 and 12345, because there is no clock anywhere and the single RNG is explicitly seeded — see §7.6.
The backlog entry that produced this toy asked for weighted round-robin as a third policy. It is not one, because on this fleet it is not a different policy. Smooth weighted round-robin with equal weights emits exactly the round-robin sequence:
Identical request-for-request. Give the broken backend a lower weight and goodput rises to 5766 — but only because a human happened to guess right in advance. That is the point worth keeping: a static weight is immune to this failure for the same reason it is useless against every other one. It is not reading anything.
I expected least-connections to be the villain — a backend that fails instantly has no queue, so it should look permanently idle. It does, but the size of the effect turns out to be an artifact of tie-breaking rather than a property of the policy, so it could not carry the page:
Same policy, same fleet, same trace: 33.20% or 85.38% depending only on where the broken backend sits in a list. At this load most backends are idle most of the time, so least-connections spends its life in ties, and min's first-wins rule (§5.3) decides the outcome. Peak-EWMA is unmoved by the same permutation, because it almost never ties — its score is a float built from a latency history, not a small integer.
This is worth knowing for its own sake: if you run least_conn in nginx with an idle-ish fleet, a meaningful part of your traffic distribution is decided by the order of lines in a config file.
Envoy's LEAST_REQUEST is P2C over active request count — "selects N random available hosts... and picks the host which has the fewest active requests." Running the identical sweep over in-flight instead of latency:
It saturates around 47% instead of climbing to 100%, and it does not follow d/N. The reason is the difference between a signal that self-corrects and one that does not: sending a request to the broken backend raises its in-flight count, so in-flight-based policies push back on themselves. Latency does not — serving one more instant error does not make the next error slower, so the beacon never dims.
That is a real ranking of the two signals, and it is the opposite of the usual one. Latency is the more sophisticated signal, reacts sooner to genuine degradation, and is exactly the one with no negative feedback when the "degradation" is failure.
The EWMA smoothing factor. The obvious tuning response to "the balancer over-reacted to a latency signal" is to smooth it harder. Every value fails:
Goodput ranges from 0 to 22 out of 6407 across two orders of magnitude of alpha. Slower smoothing is worse, not better, because the broken backend's true latency really is 1 tick — there is no noise here for smoothing to remove. The signal is not noisy, it is valid and irrelevant.
A bigger fleet. The intuition that more capacity dilutes a bad backend is correct for round-robin and false for peak-EWMA:
Round-robin's blast radius is 1/N and shrinks all the way to 2.00% at N=50. Peak-EWMA's is pinned at 99.92% and its goodput is 5 at every fleet size from 3 to 50. Scaling out is not a mitigation; the gap widens from 801× at N=2 to 1256× at N=50, because you added healthy capacity the balancer will not use. test_a_bigger_fleet_does_not_dilute_a_black_hole pins both columns.
Service-time jitter. Real backends do not answer in exactly 10 ticks, and you might hope variance blurs the signal. At a coefficient of variation of 2.0 — extremely heavy-tailed — the share is still 94.44% and goodput 361. test_service_time_variance_does_not_save_it pins it.
The most serious objection to the model is that these backends serve one request at a time, so the (inflight+1) term has an unrealistically easy job of saturating. Real servers handle hundreds of concurrent requests. Setting slots higher:
| slots per backend | peak-EWMA share | peak-EWMA goodput |
|---|---|---|
| 1 | 99.92% | 5 |
| 2 | 100.00% | 0 |
| 4 | 100.00% | 0 |
| 20 | 100.00% | 0 |
Goodput is exactly zero — not one request in 6407 reached a healthy backend. With more than one slot the broken backend's in-flight count never climbs enough for (inflight+1) to overcome a 10× latency advantage, so the one brake in the score never touches the pedal. The single-slot model in the demo is the conservative choice, and it is the only configuration in which the black hole leaks at all. test_concurrency_makes_it_strictly_worse pins the zeros.
Health checks and retries. Both are absent on purpose. Retries would make the goodput collapse look like a retry storm, which is a different mechanism that ../circuit-breaker/ already teaches; active health checks are a remediation, and putting one in would answer the question the page is asking. §8 says what they buy.
Ejecting the backend rather than down-weighting it. PeakEwmaOverSuccess applies a soft penalty, which is why the broken backend still gets 8.37% rather than 0%. Real outlier detection ejects. Soft penalty was the right choice for the toy because it is a one-line diff against the broken policy (§5.5) — the reader can see that the entire difference is one division — whereas ejection needs a state machine, timers and an un-ejection rule, which is another toy.
Giving the balancer delayed latency feedback. observe is called at dispatch, so the policy learns each RTT instantly (§5.6). Modelling the lag would be more realistic and would only strengthen the result; the toy gives the balancer perfect information so that nobody can attribute the outcome to stale data.
No clock, no unseeded RNG. simulate takes seed and threads one random.Random through arrivals, service draws and the P2C sample; time is an integer tick counter. The payoff here is specific: the aha in §6.1 is an exact identity between two runs, and an identity is not observable under jitter. It is what lets test_broken_backend_is_invisible_to_the_balancer assert [4, 1, 0, 6402] and 1.3036593805578662 rather than "approximately the same." Output is byte-identical across PYTHONHASHSEED 0, 1, 42, 12345.
The alpha and cv knobs live in the shipped code, not in a fork, because §7.4 needs to measure that they are inert rather than assert it.
/healthz and takes failing backends out of rotation, and a health check would catch this toy's broken backend immediately. The catch is that the black hole is a load-balancing failure, not a detection failure: it occurs in the window before a check fires, it occurs for backends that pass a shallow check while failing real requests (the classic "/healthz returns 200 because it doesn't touch the database"), and it occurs for partial failures where only some requests error. What §6.5 tells you is that during any such window, the balancer is not neutral — it is actively aiming traffic at the fault, and 99.92% of it.inflight() is a len() on a deque. A real client's view of in-flight is exact for its own requests and totally blind to everyone else's, which is the actual reason power-of-d-choices exists (Marc Brooker's post in §10 is about balancing on stale load data, which the toy does not model at all).Answer before expanding. Each answer is derivable from the source.
In §6.1 the balancer routes 6402 of 6407 requests to fast3 in both worlds. What single line of load_balancer.py guarantees the two worlds are indistinguishable to it, and what would you have to change to make them distinguishable?
PeakEwma.observe (load_balancer.py:150-151):
def observe(self, b, latency, ok):
b.ewma = (1 - self.alpha) * b.ewma + self.alpha * latency
It takes ok as a parameter and never reads it. Since Backend.dispatch computes the duration without consulting healthy (§5.1), the entire input to the policy — latency and inflight() — is identical in both worlds, so the output must be too.
To distinguish them you have to put ok in the score. That is exactly PeakEwmaOverSuccess.score (load_balancer.py:162-164), which divides by the smoothed success rate and is described in the source as "the only term in the toy that reads ok." It takes goodput from 5 to 5871.
Your fleet of 4 has one broken backend and you are running peak-EWMA with d=2. An incident review recommends moving to d=4 "so the balancer considers all backends and makes a better-informed choice." What happens, and what is the general rule?
Goodput falls from 3203 to 2, and the broken backend's share rises from 50.40% to 99.97% (§6.3).
The rule is share = d/N. The broken backend wins every sample it appears in — measured as 3255/3255 at d=2 and 6313/6315 at d=4 — because its score is 1 × (0+1) = 1 against a healthy backend's 10 × (q+1) ≥ 10. So its share is just the probability of being sampled, d/N, and at d=N that is 1.
More information is only better when the information is about something real. Here every additional sample is another chance to see the beacon, and the "better-informed choice" is a worse one.
Your on-call dashboard shows p50, p95, p99 and p99.9 request latency, sliced by endpoint, with errors excluded so they don't skew the numbers. The incident in §6.1 begins. What do you see?
An improvement. p99 of successful responses goes from round-robin's 43 ticks to 10 (§6.2) — the graph goes down and stays down.
Excluding errors is the intuitive hygiene move and it makes things worse, for a reason worth remembering: the requests that still succeed are being served by backends that are now almost completely idle, since 99.92% of the traffic has been diverted elsewhere. Their latency is excellent because the outage is severe. The successes get faster as more of them are destroyed.
Nothing on a latency dashboard can show you this. You need a count of useful responses — goodput, or error rate by backend. test_throwing_the_errors_ away_does_not_help pins the four p99 values at [43, 19, 23, 10].
Same fleet, same peak-EWMA policy, but the broken backend fails by hanging — 50 ticks to a timeout instead of 1 tick to a 503. Predict the outcome relative to round-robin.
Peak-EWMA wins, by 25.4%: 6026 successful requests against round-robin's 4806, with the broken backend's share down to 5.95% (§6.5).
This is the same policy that caused a 961× goodput collapse in §6.1, and nothing about it changed. A slow failure makes the backend's EWMA climb, so the score rises, so the balancer routes around it — which is precisely what latency-aware balancing was built to do. Round-robin, having no signal, keeps sending it a quarter of everything forever.
The crossover is at error latency == service latency, measured at 25.0% share for service times of 10, 20 and 40 (§6.5). The single question that decides which side you are on: does failing take longer than succeeding?
You run this policy in a client library on 200 hosts, each with its own EWMA table, in front of a fleet of 50 backends. One backend starts returning instant 503s. Estimate the blast radius, and say what you would add.
Close to total, and not 1/50th of anything.
Fleet size does not dilute it: §7.4 measures peak-EWMA's goodput as 5 at every N from 3 to 50, while round-robin's climbs to 6279 at N=50. The 200 clients make it worse, not better — each one independently measures the same 1-tick latency and independently concludes the same backend is the best in the fleet, so they converge on it together rather than spreading out. And if those backends serve concurrent requests (they do), §7.5 measures goodput at exactly 0.
What to add, in order of value: a success signal in the score — §6.4 measures one division taking goodput to 5871, past round-robin's 4806; then ejection rather than down-weighting, since the soft penalty still leaves 8.37% flowing into the hole; then health checks, remembering §8's caveat that they only cover the window they cover. Raising d, adding capacity, smoothing alpha, or switching to least-connections are all measured on this page and none of them work.
For a fleet you actually operate, what one question tells you whether latency-aware balancing will help or hurt?
"When a backend fails, does failing take it longer than succeeding?"
If yes — timeouts, hangs, thread-pool exhaustion, a saturated disk — the signal points the right way and the policy is the best failure detector you own: 6026 against round-robin's 4806, a 25.4% win (§6.5).
If no — connection refused, a tripped circuit breaker, a fast 503 from a process with a dead dependency — the signal is exactly inverted and the policy aims your traffic at the fault: goodput 5 against 4806, a 961× loss.
The trap is that "fail fast" is universal advice, and everything modern is built to fail on the wrong side of that line. Which means the answer for most fleets is no, on most of their failure modes — and the fix is not to fail slowly, it is to put the outcome in the score.
Every link below was fetched and confirmed live when this was written.
PeakEwma (§5.4), described there as "a moving average over an endpoint's round-trip time (RTT) that is highly sensitive to peaks," which is "then weighted by the number of outstanding requests." Note their own stated caveat — that it "assumes loaded endpoints take time recovering" — which is the assumption §6.5 breaks from the other direction.LEAST_REQUEST is P2C by default: "selects N random available hosts... and picks the host which has the fewest active requests." That is §7.3's policy, and the reason it saturates at 47% instead of 100%.qps / (utilization + eps/qps * error_utilization_penalty). An errors-per- second term in the denominator: the same shape as PeakEwmaOverSuccess (§5.5), reached independently and with a knob for how hard to penalise.d=2 "yields a large reduction in the maximum load over having one choice, while each additional choice beyond two decreases the maximum load by just a constant factor." Worth reading precisely to see what it assumes — that every server is trying to serve you — and therefore why share = d/N is not a contradiction of it.