# Horizontal scaling confirmed: real-time state not held solely in process memory

- **Pattern:** `ab-000753` (`community-realtime.realtime-ux.horizontal-scaling-ready`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000753
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000753)

## Why it matters

Storing presence, channel subscriptions, or message history exclusively in process memory means that restarting a single server instance silently resets that state for all users connected to it. In production, auto-scaling and rolling deploys trigger instance restarts routinely — users are dropped from channels, presence shows them offline, and in-flight message queues are lost. ISO 25010 reliability requires that state survival is not contingent on any individual process's uptime.

## Severity rationale

Low because in-process-only state loss occurs on restart or scale events — a failure mode that is invisible in development but routine in production deployments.

## Remediation

Externalise real-time state to a shared backend. The Socket.IO Redis adapter handles presence and room membership automatically; use a database or Redis for message history.

```ts
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';

const pub = createClient({ url: process.env.REDIS_URL });
const sub = pub.duplicate();
await Promise.all([pub.connect(), sub.connect()]);

io.adapter(createAdapter(pub, sub));
// Rooms, presence, and subscription state now survive instance restarts
```

For message history, write to the database on send and query on reconnect — never rely on an in-process buffer as the source of truth. The skip condition applies only when the deployment contract explicitly states single-instance forever.

## Detection

- **ID:** `horizontal-scaling-ready`
- **Severity:** `low`
- **What to look for:** Enumerate all real-time state storage locations: in-process Maps, Redis, Memcached, database tables. Count the state types stored externally: presence, message history, subscriptions, channel membership.
- **Pass criteria:** Real-time state (presence, message history, subscriptions) is stored in at least 1 shared backend (Redis, Memcached, database), not only in process memory. At least 2 state types must be externally persisted.
- **Fail criteria:** Real-time state exists only in process memory. Horizontal scaling would lose state when instances restart.
- **Skip (N/A) when:** The deployment is explicitly single-instance and not expected to scale.
- **Detail on fail:** `"Presence state stored in process memory only. Adding a second server instance would lose user presence data."`
- **Remediation:** Use a shared state backend for horizontal scaling:

  ```ts
  import { createAdapter } from '@socket.io-redis';
  import { createClient } from 'redis';

  const pubClient = createClient();
  const subClient = pubClient.duplicate();

  io.adapter(createAdapter(pubClient, subClient));

  // Now presence, rooms, and other state are shared across instances
  ```

## External references

- iso-25010 reliability.availability

Taxons: operational-readiness

HTML version: https://auditbuffet.com/patterns/ab-000753
