CASE STUDY · DISTRIBUTED SYSTEMS · PERSONAL PROJECT

One game, three databases, and a reason for each.

A real-time multiplayer chess platform built on polyglot persistence. Every store was chosen against a specific access pattern rather than a default, and WebSockets keep two boards in sync without either player waiting on a database.

ROLE
Sole engineer — architecture, backend, client
SCOPE
Real-time play, match history, user accounts
STACK
Node.js, WebSockets, Redis, Cassandra, PostgreSQL
STATUS
Working build, open source
THE PROBLEM

Three access patterns, pretending to be one.

A chess game looks like a single application, but the data underneath behaves in three incompatible ways. Live game state is read and written on every move and worthless the moment the game ends. Match logs are written constantly and almost never updated. Accounts, ratings and friendships are relational and need real constraints.

Putting all three in one relational database is the default answer, and it fails predictably: the hot path for live moves contends with the append-heavy write path for logs, and both sit behind constraints that only the account data needs.

ARCHITECTURE

Each store doing one job.

CLIENTS ──ws──▶ GATEWAY
Connection fan-out, room membership, move broadcast
▼ GAME SERVICE
Move validation, clock, authoritative state transitions
REDIS
Session and live game state, expired at game end
CASSANDRA
Append-only match logs, partitioned by game
POSTGRESQL
Users, ratings, relationships — joins earn their cost
DECISIONS

What I chose, and why.

Authoritative server state
The client never decides whether a move is legal. It renders what the game service confirms, which kills a whole class of desync and cheating bugs before they exist.
Redis as the hot path
Live board state lives in memory with a TTL. Nothing durable sits between a player's move and their opponent seeing it.
Cassandra for the log
Every move is an append. Partitioning by game id keeps replay reads on a single partition and lets write volume scale without touching the game loop.
Reconnect over resume
A dropped socket rehydrates from Redis rather than replaying the log, so a lost connection costs a round trip instead of a lost game.
screenshot — match history / replay
screenshot — lobby / matchmaking
WHAT'S NEXT

Matchmaking at scale: rating buckets, queue fairness, and what happens when the pool is thin. Then spectator mode, which is mostly a fan-out question the gateway already half answers.

Read the code on GitHub ↗