Limits & Production
What the realm engine limits per connection, the one limit your app must set itself (state cardinality), and how to keep a realm cheap and safe under load.
A realm host is a shared resource: one Durable Object serving many clients, some of them buggy or hostile. So the engine limits what each connection can send. What it can’t guess is how large your state tree may grow, so that limit is yours to set. This page covers both.
What the engine bounds for you
Section titled “What the engine bounds for you”Every realm host enforces per-connection ingress limits. A write that breaks one is rejected with
limits_exceeded and counts as a breach against the connection. After maxBreachesBeforeDetach
breaches the connection is forcibly detached, so a hostile or buggy client cannot hold the host
hostage. A well-behaved app rarely sees a breach: the client-side coalescer reads
maxPatchesPerFrame / maxFrameBytes / writeRatePerSecond off the hydrate and splits over-limit
flushes automatically.
| 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 values in effect are
sent with the hydrate, so the client splits against the same numbers you set. A bare
limits_exceeded that still reaches your onMutationRejected means the write really was oversized.
Back off rather than retrying.
What your app must bound: state cardinality
Section titled “What your app must bound: state cardinality”The engine limits each frame and each write, but not the total size of a realm’s state. That depends on your application, so it stays your call. A record that gains 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.path("roster.$rosterId.**").view(r.everyone).alter(({ state, params }) => // An entry that already exists can always be edited or removed. Only new entries count. state.roster[params.rosterId] != null || Object.keys(state.roster).length < 500 || err_game.fromId("roster_full"), ),]state is the state before the write, so a missing state.roster[params.rosterId] means the write
creates the entry. Without that first check the rule would also reject edits to existing entries
once the roster is full.
The declarative form of this cap is t.record(t.id("rosterId"), …, { maxKeys: 500 }). It is
hashed into the schema, enforced against the candidate next state on every write, intent and server
mutation, and checked once over the whole tree at boot. Prefer it for plain entry-count ceilings.
Keep the rule form for caps that need context a schema can’t see, such as per-avatar quotas or
conditional limits. Value sizes work the same way:
t.string({ maxLength }) and t.array(item, { maxItems }) bound leaves declaratively.
There is no total-state ceiling yet. A maxStateBytes engine option is planned but not built.
Declared per-node bounds cap each dimension, not the product of all of them. For anything an
attacker can influence (a key space a client can grow), some cap, declared or ruled, is required.
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 are covered in full on Serving a Realm; they appear here because they control load and cost:
- Transient regions (
transient: ["presence", ...]) never persist and reload empty on a hibernation wake. Use them for presence, cursors, “who’s typing”: anything you’d re-assert on attach rather than store. See Presence Realms. - Ephemeral hosting (
persistence: "ephemeral") keeps the whole realm in memory only. It cold-starts from genesis and 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 applies to the whole stack: the trust model, payload exposure and devtools in production live there and on Identity & Trust. Next in the realm arc: Presence Realms.