Skip to content

Limits & Production

What the realm engine bounds for you per connection, the one limit your app must bound itself (state cardinality), and how to keep a realm cheap and safe under load.

A realm host is a shared resource — one Durable Object, many clients, some of them buggy or hostile. So the engine bounds every connection for you, and hands you the one lever it can’t guess: the total size of your state tree. This page is both of those, in one place.

Every realm host enforces per-connection ingress limits. A breach rejects the offending write (surfaced as limits_exceeded) and counts against the connection; maxBreachesBeforeDetach breaches force a detach — a hostile or buggy client cannot hold the host hostage. The client-side coalescer reads maxPatchesPerFrame / maxFrameBytes / writeRatePerSecond off the hydrate and splits over-limit flushes automatically, so a well-behaved app rarely sees a breach at all.

LimitDefaultBounds
maxPatchesPerFrame64Patch ops in one write frame.
maxFrameBytes64 KBEncoded bytes of one inbound realm frame.
maxRecordKeyLength128Dynamic record-key length (also protects the intern dictionary).
maxExpandedLeaves256Leaves one subtree write may expand to.
writeRatePerSecond100Sustained write/intent rate per connection (token bucket).
writeRateBurst200Bucket depth — the burst a connection may spend at once.
maxBreachesBeforeDetach10Limit breaches on one connection before it is forcibly detached.

Override any of them in the serve engine options; the effective values ride the hydrate, so the client splits against the same numbers you set. A bare limits_exceeded that reaches your onMutationRejected (rather than being split away) means a genuinely oversized write — back off, don’t hammer.

What your app must bound: state cardinality

Section titled “What your app must bound: state cardinality”

The engine bounds each frame and each write, but not the total size of a realm’s state — that is application-shaped, so it stays your call. A realm whose state is a record that grows one entry per client (a presence roster, a chat log, a leaderboard) grows without limit unless you cap it. Cap it with a rule — the same place you already authorize writes:

// doc-check: skip — illustrative rule shape; see nice-realm/rules
rules: (r) => [
r.on("roster.*").alter((ctx) =>
Object.keys(ctx.state.roster).length < 500 || err_game.fromId("roster_full"),
),
]

A maxStateBytes engine knob may arrive later; until then the cardinality rule is the recipe, and it’s strictly more expressive — you cap by the dimension that actually matters (entries per record), not by an opaque byte ceiling. For anything attacker-influenced (a key space a client can grow), a cap like this is not optional.

Two hosting choices keep a realm small and inexpensive to run — both covered in full on Serving a Realm, flagged here because they’re the load-and-cost levers:

  • Transient regions (transient: ["presence", ...]) — a branch that never persists and reloads empty on a hibernation wake. Presence, cursors, “who’s typing” — anything you’d re-assert on attach rather than store. See Presence Realms.
  • Ephemeral hosting (persistence: "ephemeral") — the whole realm lives only in memory and cold-starts from genesis; nothing is written to disk. Pair it with assertOnAttach so each client re-owns its entries on a wake.

Bounding state is one line of your pre-flight checklist. The rest — the trust model, payload exposure, devtools in production — is stack-level and lives there and on Identity & Trust. Next in the realm arc: Presence Realms.