8 views
A 30-second network blip can bounce tens of thousands of sockets. Each reconnect triggers presence fan-out, missed-message catch-up, and auth refreshes — a thundering herd that looks like a DDoS against your own postToConnection path. Seniors coalesce pushes, jitter catch-up, and shed non-critical fan-out under pressure.
⚡ TL;DR: Buffer outbound events per connection with a short coalesce window; collapse duplicate topics. Jitter reconnect catch-up. Cap postToConnection concurrency and drop ephemeral presence under load. Pair with Consistent Hashing for WebSockets and Kafka to Lambda Backpressure.
API Gateway WebSocket Fan-Out: production guidance
typescript
❌ Firing // ws/coalesce.ts
type Outbound = { topic: string; payload: unknown; ts: number };
const buffers = new Map<string, Map<string, Outbound>>(); // connId → topic → last
export function enqueue(connId: string, msg: Outbound, windowMs = 50) {
let topics = buffers.get(connId);
if (!topics) {
topics = new Map();
buffers.set(connId, topics);
setTimeout(() => flush(connId), windowMs); // ✅ one timer per conn
}
topics.set(msg.topic, msg); // collapse duplicates by topic
}
async function flush(connId: string) {
const topics = buffers.get(connId);
buffers.delete(connId);
if (!topics) return;
const batch = [...topics.values()];
await apiGw.postToConnection({
ConnectionId: connId,
Data: JSON.stringify({ type: "batch", items: batch }),
});
}
postToConnection once per upstream event during a reconnect storm — you will hit 429s and amplify retries.
Jittered catch-up after reconnect
typescript
// ws/catchup.ts
export async function onConnect(connId: string, userId: string) {
const delay = 200 + Math.random() * 1800; // ✅ spread load
await sleep(delay);
const missed = await loadMissed(userId, since: lastAck(userId));
// Cap catch-up size — rest goes to "refresh from snapshot"
const slice = missed.slice(0, 100);
for (const m of slice) enqueue(connId, m, 50);
if (missed.length > 100) enqueue(connId, snapshotHint(userId), 0);
}
Fan-out backpressure
typescript
// ws/fanout.ts
import { Semaphore } from "./sem";
const postSem = new Semaphore(200); // global postToConnection concurrency
export async function fanout(connIds: string[], payload: Outbound) {
const critical = payload.topic === "billing" || payload.topic === "security";
if (!critical && postSem.pressure() > 0.8) {
metrics.hit("FanoutShed");
return; // ✅ shed presence/typing indicators
}
await Promise.all(
connIds.map((id) =>
postSem.run(() =>
apiGw.postToConnection({ ConnectionId: id, Data: JSON.stringify(payload) })
.catch((e) => {
if (e.statusCode === 410) return registry.drop(id); // gone
throw e;
})
)
)
);
}
| Load signal | Action |
|---|---|
| postToConnection 429 | Increase coalesce window, lower concurrency |
| Reconnect rate spike | Enable catch-up jitter + snapshot hints |
| Presence QPS high | Shed ephemeral topics first |
| DLQ on push worker | Stop retries of ephemeral; keep critical |
Connection registry hygiene
Stale connections after a blip produce 410 Gone storms. Sweep aggressively and treat 410 as success for delete. Same lease mindset as Lease-Based ECS Leadership — expire what you cannot heartbeat.Closing checklist
✅ Dos – ✅ Coalesce by topic per connection with a short window – ✅ Jitter reconnect catch-up – ✅ Cap postToConnection concurrency – ✅ Shed ephemeral fan-out under pressure – ✅ Treat 410 as registry delete, not a hard error ❌ Don’ts – ❌ Don’t push every presence tick during reconnect spikes – ❌ Don’t replay unbounded missed-message histories – ❌ Don’t retry ephemeral topics forever – ❌ Don’t synchronize all clients on the same reconnect timer – ❌ Don’t ignore 429s from API Gateway as “transient noise”Related reading
- Consistent Hashing for WebSockets: Sticky Gateways During Scale Events
- Kafka to Lambda Backpressure: Lag SLOs That Protect Downstream Sinks
- Graceful Shutdown for Node
- Lease-Based ECS Leadership
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.
