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.
What the engine bounds for you
Section titled “What the engine bounds for you”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.
| Limit | Default | Bounds |
|---|---|---|
maxPatchesPerFrame | 64 | Patch ops in one write frame. |
maxFrameBytes | 64 KB | Encoded bytes of one inbound realm frame. |
maxRecordKeyLength | 128 | Dynamic record-key length (also protects the intern dictionary). |
maxExpandedLeaves | 256 | Leaves one subtree write may expand to. |
writeRatePerSecond | 100 | Sustained write/intent rate per connection (token bucket). |
writeRateBurst | 200 | Bucket depth — the burst a connection may spend at once. |
maxBreachesBeforeDetach | 10 | Limit 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/rulesrules: (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.
Keep it cheap: transient and ephemeral
Section titled “Keep it cheap: transient and ephemeral”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 withassertOnAttachso each client re-owns its entries on a wake.
Where this sits in production
Section titled “Where this sits in production”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.