Reading — step 1 of 5
Read
Slow Start: Ramping New Backends
A backend that just booted is functionally slower than its steady-state self: application caches are empty, database connection pools are unopened, JIT compilers haven't warmed, CPU caches are cold. If the balancer hands it a full share of traffic in its first second, those first requests run slow — and now your own protective machinery betrays you. Timeouts fire, passive health checks count failures, circuit breakers trip, and the brand-new backend gets ejected... then re-added, cold again, and ejected again. This oscillation is flapping, and it can keep a perfectly good server out of rotation indefinitely.
Slow start fixes it by ramping a new backend's effective weight linearly from 0 to its configured target over a window:
effective_weight(t) = target_weight * min(1, (t - joined_at) / ramp_secs)
A backend with target weight 10 and a 10-second ramp:
t=0 weight 0 (no traffic yet)
t=5 weight 5 (half share)
t=10 weight 10 (fully ramped)
Every serious balancer ships this: NGINX Plus (slow_start=30s on the upstream server), HAProxy (slowstart 30s), Envoy (slow_start_config, with a configurable aggression curve), and AWS ALB (slow start on target groups, configurable 30–900 seconds, off by default). And note the second trigger: slow start applies not just to new backends but to backends recovering from DOWN — a server that just passed its health checks has the same cold-cache problem as a fresh one, and blasting it on recovery is how you re-kill it.
Plugging into smooth WRR
The elegance of the effective-weight formulation is that the selection algorithm doesn't change. You still run smooth weighted round-robin (lesson 2) — you just recompute each backend's effective weight at pick time and use that instead of the static target:
- For each backend with effective weight > 0: add its effective weight to its
current. - Pick the highest
current(ties → lexicographically smaller name). - Subtract the total of the participating effective weights from the winner.
Two integration details matter. A backend whose effective weight is 0 is skipped entirely — it neither accumulates current nor competes; a just-added backend must receive no traffic at t=0, not "a little". And the current counters persist across picks as always; only the weights fed into each round change as time advances.
Your exercise
Implement eff_weight() and pick() in the starter's SlowStartPool. Commands: POOL <name>:<w> ... (all at full target, no ramp), NOW <t> (integer sim time), ADD <name>:<w> <ramp_secs> (joins at the current time, ramping), WEIGHTS (print <name>:<effective_weight> sorted by name), PICK (smooth WRR over current effective weights, or NONE if every weight is 0).
Grader traps:
- Truncate the effective weight, never round.
ADD b:10 20thenNOW 5: 10 × 5/20 = 2.5 →b:2. Rounding to 3 fails the hidden test. (Integer mathtarget * elapsed // rampgets it right.) - Zero means invisible.
POOL a:5,ADD b:5 5, then fivePICKs at t=0 must printafive times — b is at weight 0 and completely absent from the rotation. - Fully-ramped is capped.
ADD b:5 10thenNOW 100→b:5, never more;min(1, elapsed/ramp)caps the factor. - Smooth-WRR ties are lexicographic here.
POOL a:6 b:2: picks goa a b a— on the second pick both currents hit 4 andawins the tie. If your tie falls to b you'll seea b ...and fail.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…