This is a simple, scalable matchmaking server designed for online gaming scenarios.
- Backend: Written in Go (Golang)
- Data Store: Redis
- Communication: WebSockets for real-time bidirectional communication
- The client sends a WebSocket connection request to the
<address>/of a randomly selected server replica. - The request must include the header
UserId, containing a valid UUID. - Authentication: Currently, there is no formal user authentication implemented. The server strictly treats the presence of a valid
UserIdUUID as proof of a legitimate user request. - Error Handling: If the
UserIdheader is missing or invalid, the server returns an HTTP status code 401 Unauthorized or 400 Bad Request. If validation succeeds, the connection upgrades to a WebSocket.
- After a successful upgrade, the user sends a request message via WebSockets.
- Note: This request message does not currently follow a specific format, but introducing structured, fine-grained details is a future improvement.
- Upon receipt, the server pushes this request into a Redis-backed queue.
The server runs two distinct periodic background jobs to handle the queue:
- Matchmaking Job: Selects candidates based on the following criteria:
- Minimum waiting time: The request must have resided in the queue for a specified minimum duration.
- Request expiry time: The request must not have exceeded the maximum allowed queue duration.
- Match Size: A match is formed by grouping a configurable number of valid candidate requests together.
- Expiry Job: Scans the queue and clears out expired requests (those exceeding the maximum duration threshold).
Both background jobs publish their respective results (matches and expirations) to two separate Redis Pub/Sub channels.
Because all server replicas subscribe to these channels, every replica listens for both matched and expired events. Once received, the events are streamed back dynamically to the relevant users via their active WebSocket connections using the following JSON formats:
{
"status": "success",
"users": [
"de263368-33d3-4d60-8cf6-abf88fb61f1e",
"b426268b-0768-42a7-b052-6b763830b2e4",
"4328dd7a-1cfc-45d1-b105-d26092ecaa23",
"c370e175-2e86-4210-8f4f-a63675bb52f7",
"13dfffc0-04be-4bda-9a6f-5e871ddcbdd7"
]
}
{
"status": "failure",
"users": []
}
This lifecycle loop repeats for a configured number of iterations.
---
The entire setup can be tested locally using a self-contained docker-compose environment. Both the server and client components are configurable via environment variables.
REQUEST_MANDATORY_WAIT_SECS=5s— The minimum duration a request must spend in the queue before it is eligible for matchmaking.REQUEST_TIMEOUT_SECS=60s— The maximum time a request can spend in the queue before it is marked as expired.MATCH_SIZE=10— The total number of users required to successfully form a single match.
ITERATIONS=500— The total number of match requests sent by the client simulation.SERVER_ADDRESSES=server1:9000,server2:9000— A comma-separated list of available backend server replicas inhost:portformat.
Note: The provided
docker-compose.ymlfile comes pre-configured with these default parameters.
To spin up the infrastructure:
docker compose up
To tear down the infrastructure:
docker compose down
A lightweight, real-time monitoring dashboard is exposed at the /dashboard endpoint.
The dashboard provides insights on:
- Currently connected clients
- Request and match rates per second
- Average matchmaking latency calculated over the trailing 30 seconds
To access the UI after launching the Docker Compose environment, navigate to: http://localhost:9000/dashboard in your browser.

