DEV Community

Daniel Ioni
Daniel Ioni

Posted on

Scaling the MyZubster Metaverse Realtime Layer with Socket.IO and Redis

Scaling the MyZubster Metaverse Realtime Layer with Socket.IO and Redis

We are continuing the development of the MyZubster Metaverse, moving from a locally synchronized experience toward a distributed realtime world capable of supporting multiple application instances.

The current work focuses on one fundamental requirement:

Players connected to different server instances must still see the same world events and shared presence.

This post documents what we have implemented, what we have verified, and what comes next.

The realtime foundation

The metaverse now has a dedicated Socket.IO runtime using:


text
/realtime
Authenticated users receive a short-lived connection token from:
POST /api/realtime/token
The token authorizes access to specific realtime channels, including:
world:neon-plaza
The current shared-world event stream supports:
- Player joins
- Player movement
- Chat messages
- Emotes
- Player departures
- Reconnection and state reconciliation
The browser client uses WebSocket transport and automatically falls back to the existing REST synchronization layer if the realtime connection is unavailable.
When realtime is active, REST becomes a slower reconciliation mechanism rather than the primary transport.
Cross-instance fan-out with Redis
A local Socket.IO server can distribute events between users connected to the same process.
That is not enough for a horizontally scaled deployment.
If two players are connected to different Vercel instances, events must be propagated through shared infrastructure.
We added the official Socket.IO Redis adapter:
@socket.io/redis-adapter
The backend now creates dedicated Redis publishing and subscription clients. Socket.IO events are distributed across instances through Redis Pub/Sub.
If Redis becomes unavailable, the application records the failure and falls back to local delivery instead of crashing the entire realtime runtime.
The operational health endpoint now reports the active transport:
{
  "success": true,
  "status": "ok",
  "transport": "socket.io",
  "socketPath": "/realtime",
  "presence": "local-fallback",
  "fanout": "redis",
  "privacy": "aggregate-only"
}
The important milestone here is:
"fanout": "redis"
This confirms that cross-instance Socket.IO fan-out is using Redis.
Redis infrastructure
For the Preview environment, we provisioned an Upstash Redis Free database in Frankfurt.
The configuration is currently isolated from production:
Environment: Preview
Region: Frankfurt
Plan: Free
Production access: Disabled
Vercel generated a native REDIS_URL, together with the optional REST credentials. Secret values remain masked and are never exposed by the health endpoint.
We verified the database directly:
PING
PONG
We also tested Redis publishing on a temporary health-check channel:
PUBLISH myzubster:healthcheck ping
0
A result of 0 means the command was accepted and there were no active subscribers on that temporary channel.
This confirms that the credentials support the publishing operations required by the Socket.IO adapter.
Why presence initially reported local fallback
After enabling Redis fan-out, the health endpoint returned:
"presence": "local-fallback",
"fanout": "redis"
This was not a Redis connection failure.
The presence client was intentionally initialized only when the first presence.join event occurred. The health endpoint was checking its state before any presence operation had started the client.
As a result, the status was technically accurate but operationally confusing.
We fixed this behavior in commit:
e29d613 β€” fix(realtime): verify Redis presence health
The health route now performs a real operation against the shared presence store before reporting its mode.
This means the endpoint verifies Redis readiness instead of merely checking whether a lazy client has already been initialized.
The expected result is now:
{
  "success": true,
  "status": "ok",
  "transport": "socket.io",
  "socketPath": "/realtime",
  "presence": "redis",
  "fanout": "redis",
  "privacy": "aggregate-only"
}
Privacy-aware observability
Realtime observability remains aggregate-only.
The system tracks operational signals such as:
- Connection attempts
- Successful connections
- Rejected connections
- Redis failures
- Message processing latency
- Duplicate messages
- Reconnection attempts
- Active connection count
- Backpressure events
Public health responses do not expose:
- User identities
- Player positions
- Messages
- Authentication tokens
- Redis credentials
- MongoDB connection details
Detailed metrics remain restricted to authenticated administrators.
Current architecture
The current data flow looks like this:
Authenticated browser
        |
        v
Short-lived realtime token
        |
        v
Socket.IO /realtime
        |
        +----> MongoDB persistent world state
        |
        +----> Redis shared presence
        |
        +----> Redis Socket.IO Pub/Sub adapter
        |
        v
Other connected application instances
If the WebSocket connection is interrupted, the browser reconnects with a fresh token and reconciles its state through the existing REST APIs.
What we are testing next
The next phase is an authenticated two-client test.
We will open the metaverse in two separate browser sessions and verify that both clients can:
1. Connect through WebSocket.
2. Join the Neon Plaza channel.
3. See each other’s arrival.
4. Receive movement events.
5. Exchange chat and emote events.
6. Handle disconnects and reconnects.
7. Restore subscriptions after reconnecting.
8. Observe consistent state across separate runtime instances.
We will also inspect aggregate metrics and confirm that Redis failures trigger the expected local fallback behavior.
Production remains untouched
All Redis work described here is currently running in the Preview environment.
We have not:
- Enabled Redis in production
- Promoted the Preview deployment
- Merged the pull request
- Disabled Vercel deployment protection
- Exposed any secret value
The production rollout will happen only after the health verification, two-client test, pull-request review, and final authorization.
The direction
This work transforms the MyZubster Metaverse from a single-process experience into a distributed realtime platform.
The goal is not only to render a virtual world. The goal is to build a shared environment where identity, collaboration, presence, communities, marketplace activity, and interactive experiences can coexist on reliable infrastructure.
The Neon Plaza is the first shared world.
The architecture being built around it is designed to support many more.
Follow the project:
- Website: https://www.myzubster.com/metaverse
- GitHub: https://github.com/MyZubster-Ecosystem/myzubster
- Creator: https://github.com/DanielIoni-creator
#MyZubster #Metaverse #SocketIO #Redis #NodeJS #WebSockets #Realtime #OpenSource
Enter fullscreen mode Exit fullscreen mode

Top comments (0)