← Back to the library
System designDeep dive · 10 min

WebSockets disconnect, duplicate, or lose events

“The live UI looks correct most of the time, but reconnects produce gaps, duplicates, stale balances, or event storms.”

ERROR CODES YOU MAY SEE

A code is a clue. Use its meaning and surrounding evidence to narrow the cause.

ECONNRESETNode.js system errors · platform

The peer forcibly closed the connection.

Correlate client, proxy, and server logs. A reset alone does not reveal whether an operation completed before the connection ended.

Official reference for ECONNRESET (opens in new tab)Code definition reviewed
1006WebSocket close codes · RFC 6455 · standard

The connection ended abnormally without a received Close frame. This is a local indication; endpoints must not send 1006 in a Close frame.

Check network interruptions, proxy timeouts, and process restarts before choosing a reconnect and state-recovery strategy.

Official reference for 1006 (opens in new tab)Code definition reviewed

THE PRINCIPLE

Assume every connection ends and every event may be delivered zero, one, or multiple times.

FIRST MOVES

  1. Attach stable event IDs and monotonically increasing stream positions.
  2. Reconnect with exponential backoff and jitter; avoid a thundering herd.
  3. Resume from the last acknowledged cursor or replace state with an HTTP snapshot.
  4. Make consumers idempotent and reject stale entity versions.

TOOLS: THEN → NOW

Raw socket plus in-memory server stateWebSocket/SSE plus Redis Streams, NATS, or Kafka
Blind reconnect loopsBackoff, jitter, heartbeats, replay cursors

PATTERN SNAPSHOT

const delay = Math.min(30_000, 1_000 * 2 ** attempt);
const withJitter = delay * (0.5 + Math.random());

// Reconnect with lastSequence; upsert by event.id.
setTimeout(() => connect(lastSequence), withJitter);

CLOSE THE AI. EXPLAIN THIS.

Why must a financial balance not use the WebSocket stream as its only source of truth?

Guide reviewed