Jesús Abraham González Flores – Lead Programmer
“How we scaled Runiverse, a browser-based MMORPG, to host ten thousand players in a single persistent world, without dividing them across servers.”
The Challenge
Most MMORPGs create the illusion of scale. Behind the login screen, the player base is discreetly fragmented into shards, realms, or instances: separate copies of the same world. It is the pragmatic path: each copy remains small enough to be simulated, avoiding the challenge of making ten thousand players coexist in the same physics, the same economy, and the same patch of land.
Runiverse set out to avoid that shortcut. The design goal was a single, shared, and persistent region where players see each other, affect the same world objects, own the same land, and trade in the same economy, without a server selection screen fragmenting the community. Additionally, it had to run in a web browser (based on Phaser and Colyseus/Node.js), where native networking and generous bandwidth are not available, only a WebSocket and whatever the player’s connection allows.
Achieving this for a small group is simple. Doing it while aiming for 10,000 concurrent users (CCU) in a single region is a different class of problem: every additional player is not just another connection, but another moving entity that potentially every nearby player must know about in real time, several times per second.

Key Criteria
Before writing a single line of scaling code, we defined the non-negotiable constraints:
– A shared world, not rigid shards. Partitioning was acceptable internally, but it had to be invisible to players. Someone placing a building or moving through an area had to be visible to their neighbors, without walls between isolated copies.
– Authoritative real-time simulation. The server is the source of truth. We run a ~60 Hz server tick (16.6 ms) so movement and interactions remain consistent and cheating is contained.
– Browser-level bandwidth. The client runs in a tab. Network payloads had to be small and predictable; full state broadcasts were never an option.
– Durable persistence. Positions, inventories, and objects reside in MongoDB and must survive restarts. Web3/NFT-backed assets raise the bar for correctness: one cannot “lose” anyone’s on-chain item.
– Horizontal scalability in the cloud. The system had to scale across standard AWS instances, keeping costs under control and without exotic hardware.

The Solution
The core idea is spatial partitioning that remains cohesive. The world is divided into chunks. Players in the same area are grouped in the same Colyseus room instance, with a soft limit (~25 clients) and a hard limit (65) per room, and approximately 150 clients per server process. This keeps each simulation loop small and fast.
The key to maintaining the illusion of a single, cohesive world—rather than a grid of disconnected, isolated zones—is Redis pub/sub. Instead of siloing each room, we publish state updates (such as building placements, object movements, and inventory changes) on chunk-scoped channels. These updates are then propagated to every server process subscribed to that specific chunk. Since rooms only subscribe to the chunks they actively host and unsubscribe upon disposal, the system remains efficient. Consequently, a change in one room becomes immediately visible to neighboring areas across different server processes, effectively eliminating the seams between chunks. This represents the fundamental architectural distinction between sharding players apart versus partitioning a shared world.
Built on this foundation, several mechanisms sustain the load:
– Delta-compressed state sync. Colyseus’s schema serialization sends only what changed since the last patch, in a compact binary form not the whole world every tick. On a browser client, this is the single most important bandwidth lever.
– Custom load balancer. New rooms are assigned to the least loaded process using a weighted score of CCU, active rooms, and CPU usage, optimizing the real processing cost.
– A typed RPC layer with rate limiting. Every client-to-server action is a validated, typed message with per-client throttling (minimum interval between calls, capped successive calls). At 10k CCU, this is as much a stability feature as a security one it stops a spike of messages from taking a process down.
– Multi-layer caching to shield the database. Frequently read, rarely changed data (building types, item descriptors, affixes, etc…) lives in in-memory caches backed by Redis, invalidated in real time through pub/sub. MongoDB (Atlas) runs with connection pooling (100 per process) so the database sees a fraction of the raw read traffic.
– Observability built in. We instrument the servers with OpenTelemetry, which ships traces and metrics to a central OpenTelemetry Collector. From there, metrics are exported to Prometheus and visualized in Grafana dashboards, while traces and errors flow to Sentry (with Jaeger available for local debugging). Combined with the Colyseus monitor dashboard and custom per-process CCU/room/CPU stats, this gave us live visibility across the whole distributed system essential for finding where it actually bends under load. Building on an open standard with a collector in the middle also keeps us vendor-neutral: adding or swapping an observability backend is a config change in the collector, not a code change.

To validate all of this ahead of real traffic, we drove the servers with synthetic load tests (`@colyseus/loadtest`) fleets of bots simulating movement, interactions, and inventory access, including high-pressure scenarios like a land-sale event.
Results
In production during live, high-traffic events Runiverse sustained peaks of around 7,000 CCU in a single shared region, with synthetic load testing used to probe headroom beyond that. Crucially, players experienced it as one world: no shard selection, no fragmented economy, neighbors visible across chunk boundaries.
The most valuable outcome, though, was learning where the architecture bends first. As we approached those peaks, the pressure point was not the game servers or MongoDB it was Redis. The pub/sub layer that makes the shared world possible also becomes the busiest component under load, and it’s the hardest tier to scale horizontally without careful sharding of channels and traffic. Identifying that early turned “we think it scales” into “we know exactly what to reinforce next” which is the honest engineering position any team should want before a launch.

Lessons Learned
– A shared world is a networking problem, not a server count problem. Partition internally, but invest in the layer that joins those partitions; that is where the true complexity resides.
– Delta compression is mandatory. Send differences, not full states.
– Instrument before you scale. You can’t tune a bottleneck you can’t see, and the bottleneck is rarely where you first expect.
– Design the pub/sub layer for horizontal scale from day one. It will be the most critical path in a single-world design.
Runiverse proves that a browser-based MMORPG can host thousands of players in a shared region, tracing a clear path to scale from thousands to ten thousand.

