> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sx.bet/llms.txt
> Use this file to discover all available pages before exploring further.

# Reliability & Recovery

> Key patterns for maintaining consistent state across connects and reconnects

The real-time channels are straightforward to subscribe to, but there are a few patterns worth following to avoid dropped or duplicated data. This page covers the essentials — see [Real-time Data](/developers/real-time) for full code examples.

## Snapshot + subscribe

Don't rely on the live feed alone to build initial state. Subscribe first, then seed from REST inside the `subscribed` handler. This closes the gap between "when you fetched the snapshot" and "when your first publication arrives":

1. Create a subscription with `positioned: true, recoverable: true`
2. In the `subscribed` handler, fetch current state from REST
3. Apply live updates as publications arrive

On reconnects, the same `subscribed` handler fires — check `wasRecovering` and `recovered` to decide whether you need to re-seed (see [Recovery](#recovery)).

## Recovery flags

Pass both flags together on channels with history enabled:

```js theme={null}
client.newSubscription("order_book:market_abc123", {
  positioned: true,
  recoverable: true,
});
```

`positioned` bookmarks your place in the stream. `recoverable` uses that bookmark to replay missed messages on reconnect. Either flag alone does nothing useful — you need both. For channels without history (`best_odds`, `parlay_markets`), omit both and re-seed from REST on every reconnect.

See [Namespace history capabilities](/api-reference/centrifugo-overview#namespace-history-capabilities) for which channels support recovery.

## Recovery

After a reconnect, the `subscribed` event tells you whether your local state is still consistent:

| `wasRecovering` | `recovered` | What happened                           | Action                        |
| --------------- | ----------- | --------------------------------------- | ----------------------------- |
| `true`          | `true`      | History replay filled the gap           | Nothing — state is consistent |
| `true`          | `false`     | History window expired before reconnect | Re-seed from REST             |
| `false`         | —           | Fresh connect                           | Seed from REST                |

The recovery window is 5 minutes. After that, or if the server stream was reset, `recovered` will be `false` and you must re-seed.

## Deduplication

At-least-once delivery means a message can be replayed more than once during recovery. Every publication includes a `messageId` in `ctx.tags` — track seen IDs and skip duplicates:

```js theme={null}
const seen = new Set();
sub.on("publication", (ctx) => {
  const id = ctx.tags?.messageId;
  if (seen.has(id)) return;
  seen.add(id);
  applyUpdate(ctx.data);
});
```

For long-running processes, cap your dedup set (e.g. last 1,000 IDs) to avoid unbounded memory growth.

## 512 channel limit

Each connection supports a maximum of 512 simultaneous subscriptions. If you exceed this, the subscribe call returns error code `106`. Create additional client instances for additional channels — each gets its own connection and its own 512-slot budget.

## Slow consumer

The server buffers up to 1 MB per connection. If your `publication` handler is slow, the buffer fills faster than it drains and the server closes the connection. Hand off incoming messages to a queue immediately — do not do heavy work inline. Unexpected disconnects that aren't auth errors are often a saturated buffer.

***

<Card title="Real-time Data →" icon="bolt" href="/developers/real-time">
  Full code examples for all patterns above, plus common failures and error codes.
</Card>
