Knowledge Guide
HomeSystem DesignScalable Systems (Advanced Topics)

hard Scale-In Safety & Connection Affinity

Scaling in is the dangerous direction

Scaling out adds a fresh instance that starts empty and takes new work — nothing can break. Scaling in deletes an instance that is right now holding live TCP connections and half-finished requests, so unless you first stop new traffic and let the in-flight work finish, those requests are severed mid-flight and the client gets a 5xx. The whole discipline of safe scale-in is one rule: deregister, drain, then terminate — never terminate first.

The drain mechanism, layer by layer

“Draining” means: remove the instance from the load balancer’s rotation so it receives no new requests, but keep its existing connections alive until they complete (or a timeout fires), and only then kill the process. Every layer implements the same idea:

The infamous race: endpoint removal is asynchronous. For a brief window after SIGTERM, the LB/kube-proxy may not have learned the pod is gone and can still send it new requests — which now hit a socket that is closing, producing 502s. The standard fix is a preStop hook that sleeps a few seconds: keep serving normally while deregistration propagates, then begin shutdown.

Traced scale-in timeline (drain vs hard kill)

Autoscaler removes instance B (3 → 2). B is serving 12 in-flight requests. Compare the two paths step by step:

tGraceful drainHard kill (terminate first)
0.0sB deregistered from LB → no new requests routed to BB receives SIGKILL immediately
0.0spreStop sleep 5s lets deregistration propagate; B keeps serving its 1212 open connections severed → clients get TCP RST
0–18sSIGTERM; B finishes in-flight, in-flight count 12 → 012 requests fail → 502s; clients retry
~18sprocess exits 0; B terminated cleanly → 0 errorsretries pile onto A & C; if they were already near capacity, they shed load → 503s (retry storm)

The drain path never has a moment where a request is routed to a gone instance, so it avoids the 502/503 window entirely. The hard-kill path converts a routine scale-in into a burst of user-visible errors — worse, the retry traffic can tip the surviving instances over, so a capacity reduction becomes an outage.

Four-stage graceful scale-in (deregister, preStop/SIGTERM, drain window, terminate) with 12 requests all served as solid green discs, versus a hard kill dropping all 12 as hollow rust rings into 502s and a retry storm
Four-stage graceful scale-in (deregister, preStop/SIGTERM, drain window, terminate) with 12 requests all served as solid green discs, versus a hard kill dropping all 12 as hollow rust rings into 502s and a retry storm

Affinity decides what a scale-in costs

If every instance is stateless, draining is the whole story. But the moment a client is pinned to a specific backend — “affinity” — removing that backend can destroy state the client depended on. There are two families, and they behave very differently under scale-in.

LB-issued (cookie / sticky) affinity

The load balancer pins the client: on the first response the LB sets its own cookie (e.g. ALB’s AWSALB, or an app cookie like JSESSIONID) that encodes which target to use, and thereafter routes that client to the same backend. This is how “keep the user on the node that holds their in-memory session” is implemented. On scale-in, if the pinned target is the one removed, the LB simply re-pins the client to a survivor — but the in-memory session on the dead node is gone. The user is logged out, their half-built cart evaporates, their WebSocket drops. Sticky sessions turn a stateless-looking service into a stateful one whose state disappears with the instance.

Client / consistent-hash affinity

Here the client (or a hash ring) picks the backend from a key: node = ring.lookup(hash(user_id)). With consistent hashing, removing one node reassigns only that node’s share of the keyspace (~1/N of keys) to its clockwise neighbour; every other key stays on the same node. Contrast naive hash(key) % N: dropping from N to N−1 servers changes the modulus, so almost every key maps somewhere new — a mass cache miss / re-shuffle. That remap-only-1/N property is exactly why consistent hashing is the affinity of choice for caches and sharded stores that scale in and out routinely.

Left: cookie affinity pins a client to instance B; when B is removed the in-memory session is lost and the client must re-establish. Right: a consistent-hash ring where removing node C remaps only C's highlighted arc (about 1/N of keys) to neighbour D while A, B, D keep their keys
Left: cookie affinity pins a client to instance B; when B is removed the in-memory session is lost and the client must re-establish. Right: a consistent-hash ring where removing node C remaps only C's highlighted arc (about 1/N of keys) to neighbour D while A, B, D keep their keys

Pitfalls engineers actually hit

Judgment: how a senior engineer decides

Connection draining vs hard kill

Drain whenever requests are user-facing or non-idempotent — the cost is a bounded shutdown delay (seconds to minutes) and slightly slower scale-in, and you gain zero dropped requests. Hard kill is acceptable only when work is fully idempotent and cheaply retriable and shutdown speed matters more than a few reset connections (e.g. a stateless batch worker pulling from a queue that redelivers on no-ack). For anything serving HTTP to users, hard kill is the wrong default — it trades a few seconds of patience for user-visible 5xx and a possible retry storm.

Sticky (cookie) sessions vs stateless+external store vs consistent-hash affinity

Rule of thumb: reach for stateless + external store first (it makes scale-in a non-event); use consistent-hash affinity when locality is a real performance win; treat sticky cookies as a last resort for disposable state.

Takeaways


Synthesized from AWS ELB/ALB target-deregistration & connection-draining docs, the Kubernetes pod-termination lifecycle (preStop / SIGTERM / terminationGracePeriodSeconds), Karpenter / cluster-autoscaler graceful-shutdown guidance, and consistent-hashing treatments in Designing Data-Intensive Applications (Kleppmann) and the original Amazon Dynamo paper. Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on Scale-In Safety & Connection Affinity? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Scale-In Safety & Connection Affinity** (System Design) and want to truly understand it. Explain Scale-In Safety & Connection Affinity from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Scale-In Safety & Connection Affinity** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Scale-In Safety & Connection Affinity** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Scale-In Safety & Connection Affinity** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes