WebSocket Matchmaking & Signaling
To support 10K+ concurrent users with zero database overhead, Haven uses a dedicated Node.js WebSocket Server.
Why Not Supabase Realtime?
Initially, Haven used Supabase Realtime (Phoenix Channels) for matching. This had several bottlenecks:
- Max Connections: Limited by Supabase tier (200 in free tier).
- Matching Latency: Required PostgreSQL
waiting_queuepolling or RPC calls. - Race coached: Advisory locks were needed, which can become a bottleneck under high load.
New Architecture (2026)
- Microservice: A specialized Node.js server (separate from the Next.js app).
- In-Memory Logic: Uses a Map-based
waitingQueueto store active WebSocket connections. - Atomic Matching:
Map<WebSocket, User>: Stores waiting users.match_found: Instantly pairs two users when both are available.- Zero Database Load: The match happens entirely in memory. Only the final
chat_sessionis written to the database.
Key Features
-
In-Memory Queueing:
buddies_first: Prioritizes matching users who are already connected (mutual friends).global: Matches with anybody in the pool.
-
WebRTC Signaling Proxy:
- The WS Server acts as a signal relay between two matched peers.
- Handles
offer,answer, andice-candidatemessages.
-
Automatic Reconnection:
- Clients use exponential backoff to handle network flickers.
- Heartbeat keeps the connection alive on platforms like Render or Fly.io.
WS Message Types
join_queue: Notifies the server that the user is ready.match_found: Server notifies two users they have been paired.signal: Relay WebRTC metadata between peers.partner_left: Instant notification if a peer disconnects.report_user: Trigger a fast-block in memory and log to the database.
[!TIP] Performance: Matches are typically performed in <5ms after the second user joins the queue.
Source Code
The matchmaking server is located in the matchmaking-server/ directory.
server.ts: Handles the main WebSocket logic.newrelic.js: Monitoring for real-time traffic.