14 views
WebSocket gateways need sticky affinity: the process that holds the socket must receive the pushes. Naive modulo hashing reshuffles almost everyone when you add a node; consistent hashing moves only ~1/N. Seniors combine a hash ring with graceful handoff so scale-in drains sockets instead of hard-cutting them mid-conversation.
⚡ TL;DR: Put gateways on a hash ring keyed byconnectionIdoruserId. On scale events, migrate only the affected arc; drain withGOAWAY-style close codes and client reconnect jitter. Pair with API Gateway WebSocket Fan-Out and Graceful Shutdown for Node.
Consistent Hashing for WebSockets: production guidance
typescript
❌ // ws/hash-ring.ts
import { createHash } from "node:crypto";
export class HashRing {
private ring: { hash: number; node: string }[] = [];
constructor(nodes: string[], vnode = 64) {
for (const n of nodes) {
for (let i = 0; i < vnode; i++) {
const hash = this.h(`${n}#${i}`);
this.ring.push({ hash, node: n });
}
}
this.ring.sort((a, b) => a.hash - b.hash);
}
private h(s: string) {
return createHash("sha256").update(s).digest().readUInt32BE(0);
}
owner(key: string): string {
const x = this.h(key);
const i = this.ring.findIndex((e) => e.hash >= x);
return (this.ring[i] ?? this.ring[0]).node; // ✅ wrap
}
}
nodes[crc32(userId) % nodes.length] — adding one gateway remaps most users.
Sticky registry and push path
typescript
// ws/registry.ts
export async function bindConnection(connId: string, gatewayId: string) {
await redis.set(`ws:${connId}`, gatewayId, "EX", 3600);
}
export async function publishToUser(userId: string, payload: unknown) {
const conns = await redis.smembers(`user:${userId}:conns`);
await Promise.all(
conns.map(async (connId) => {
const gw = await redis.get(`ws:${connId}`);
if (!gw) return;
// ✅ Direct to owning gateway; no broadcast storm
await bus.send(gw, { connId, payload });
})
);
}
Graceful migration on scale-in
typescript
// ws/drain.ts
export async function drainGateway(gw: string, ring: HashRing) {
const local = listLocalConnections();
for (const c of local) {
if (ring.owner(c.userId) === gw) continue; // still ours
// Soft close — client reconnects with jitter into new owner
c.socket.close(4001, "migrate");
await sleep(5 + Math.random() * 50);
}
await deregisterFromRing(gw);
}
| Event | Action |
|---|---|
| Scale-out | Add node to ring; optionally proactive migrate arc |
| Scale-in | Drain first; remove from ring after 0 local sockets |
| Crash | Clients reconnect; registry TTL expires stale binds |
| Deploy | Same as scale-in per task |
Client reconnect jitter
typescript
Coordinate with API Gateway WebSocket Fan-Out so reconnect storms do not amplify pushes.
// client reconnect (browser / RN)
function backoff(attempt: number) {
const base = Math.min(1000 * 2 ** attempt, 15000);
return base * (0.5 + Math.random()); // ✅ avoid reconnect storms
}
Closing checklist
✅ Dos – ✅ Use consistent hashing with vnodes – ✅ Bind connection→gateway in a TTL registry – ✅ Drain before removing a node from the ring – ✅ Soft-close with reconnect jitter – ✅ Push only to owning gateway ❌ Don’ts – ❌ Don’t use modulo hashing for sticky WS – ❌ Don’t broadcast every message to all gateways – ❌ Don’t kill tasks without drain – ❌ Don’t forget registry TTL after crashes – ❌ Don’t reconnect all clients on the same millisecondRelated reading
- API Gateway WebSocket Fan-Out: Coalesce Pushes After Reconnect Storms
- Graceful Shutdown for Node: Drain Correctly on Kubernetes and ECS
- HTTP/2 in Node Behind ALB: Multiplexing Pitfalls
- Cell-Based Architecture on AWS
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
