Room capacity

maxClients is 64 by default, and a room built without thought about capacity usually runs out of something else first. This page is about finding your own number. Limits and metering has the ceilings irtio enforces. This one is about which of them you will meet, and when.

What runs out first

What runs outWhat it looks likeWhere you see it
Bandwidth per clientPlaying gets choppy, then a client is dropped with E_SLOW_CONSUMERirtio simulate’s bytes in per bot, and the bandwidth invariant
Tick timeThe room ticks late, then sheds a tick’s backlogThe tick-health invariant, and irtio status’s per-room tick health
MemoryThe room worker is killed and restarted. Three restarts in a minute closes the roomThe rss series on the dashboard, and irtio logs
maxClientsThe next join is refused with E_ROOM_FULLYour own join errors

Bandwidth is usually first, because it is the one that grows with the square of the room. Every client receives an update about every other client by default, so doubling the players doubles what each client receives and quadruples what the room sends.

maxClients is the only one of the four you set directly, and it should be the number you measured, not the number you hoped for. In our own arena a room saturates between 30 and 40 concurrent players, which is below the default of 64. Yours will be a different number, because it depends on your schema, your tick rate and how much of the world each player can see.

Find your own number

Run the room locally and point real client sessions at it.

npx irtio dev                                             # in one terminal
npx irtio simulate --bots 30 --seconds 60 --profile       # in another

Read four things off the report:

  • tick-health. ok means the server never dropped a tick’s backlog during the run. This is the hard stop: past it the room is doing less work than the game asked for.
  • Bytes in per bot. What one player’s connection costs. Compare it to the budget you intend to hold, not to the 128,000 B/s default, which is a ceiling rather than a target.
  • Convergence. The median milliseconds from one bot writing a value to another bot’s view showing it. A number that climbs as you add bots is the room falling behind.
  • correction-storm. A rising correction rate under load usually means the room is late rather than wrong.

Then step the bot count: 10, 20, 40, 80. Your capacity is the largest count where tick health stays ok, per-client bytes stay inside your budget, and convergence stays flat. Set maxClients a little below it.

Four things will bend the answer if you let them.

  • Hundreds of bots on one machine measure the machine. Treat a run in the hundreds as a profile, not a pass or fail.
  • Against a deployed project, the per-address cap is 120 new connections per minute. A bigger run has to go at irtio dev or come from several addresses.
  • A cold deployed project fails its first run on E_STARTING. Warm it with a throwaway run first.
  • The random script wanders. If capacity depends on where players go, such as anything using visibility: 'spatial-grid', write a script instead. See invariants and load runs.

Reference points

These are measurements of our own rooms, on our own hardware, against the schemas those rooms have. Use them to sanity-check an order of magnitude, never as your number.

RoomMeasured
200-bot arena with spatial filtering, against production, 3 minutes1.7 kB/s inbound per player on average, 14 kB/s at peak, against a 128 kB/s budget. Zero disconnects, zero handler errors
The same 200-player arena, per clientAbout 2.7 kB/s for a filtered player, about 68 kB/s for a full-view spectator
A 60 Hz physics room, 4 players, 30 secondsWorst tick 9.6 ms, 2.05 MB of egress
A 30 Hz physics room, 50 bodies, 8 clientsAbout 16 microseconds per tick in step, sync and reconciliation, against a 33 ms budget
Spatial index over 5,000 entitiesAbout 1.6 ms per tick to rebuild the buckets and run every client’s query, against a 50 ms tick

Two readings worth taking from that table. Physics is rarely what makes a room expensive: the tick budget is milliseconds and the simulation is microseconds. And interest management is what changes the shape of the bandwidth curve. In the arena above, four times the players cost each client about twice the bandwidth.

Bandwidth breakdown

Before you cut anything, find out what you are paying for.

npx irtio dev --profile
npx irtio simulate --profile

The bandwidth profiler breaks traffic down per field rather than giving you a total, and the answer is often not the field you suspected. In one 60 Hz room, 55% of everything the server sent was framing: the op tag that says which entity changed, and the per-frame delta header. That room was paying for addressing, not for data, and no total would have said so.

What to do at the ceiling

Three moves, in the order they usually pay off.

1. Send each client less. This is the only move that raises players per room.

  • visibility: 'spatial-grid' on the large collections, so a client receives the records near it rather than all of them. It starts paying off at roughly a few hundred entities. See visibility.
  • visibility: 'role' where a whole category of client does not need a whole collection.
  • Narrower field types. u8 and f32 are budgets, not hints.
  • A lower tickRate for a room whose data does not need the rate it has, or fewer entities that change on every tick.

2. Give the room more room. A room type declares memoryMb (32 to 1024) and maxAwake (1 to 256), and those declarations decide how big a server your project gets. Raising either is a deploy, and the bigger server arrives at your project’s next start rather than immediately, with rooms restoring from their snapshots. This buys headroom for memory and for concurrency. It does not buy bandwidth per client.

3. Split into more rooms. One room never spans more than one server, so past a point the answer is more rooms rather than a bigger one. Lower maxClients so rooms fill and split, and route players with quick match or a lobby. Room count matters much less than player count: many small idle rooms cost close to nothing, because an idle room hibernates after idleMs.

Sharding a project is a fourth thing, and not a capacity fix for one room. It spreads a project’s rooms across up to four servers. A room still lives on exactly one of them for its whole life.

Watch it after launch

npx irtio status              # every project-wide metric, then a per-room table
npx irtio status --room <id>  # one room's recent history

The dashboard’s Metrics page carries the same series: sockets, roomsByState.*, ingressBytes, egressBytes, rss. The two to watch against capacity are rss, which should rise and fall rather than only rise, and egressBytes, which is what a room costs the network.

Next steps