Skip to content

~/project

Real-Time Distributed Chat System

A distributed real-time chat system designed to study horizontal scalability, WebSocket state synchronization, and custom connection load balancing.

  • React
  • Go
  • WebSocket
  • Redis Pub/Sub
  • Docker

Role: Personal project · Year: 2024 - 2025 · Status: shipped

tldr: A chat system built to study what actually breaks when WebSocket traffic has to live across multiple servers. Goroutines handle connections, Redis pub/sub fans out messages between nodes, a small custom load balancer sits in front. The interesting part is not the chat; it’s what stops working when you go from one node to two.

Uses a Go backend, React frontend, Redis Pub/Sub for cross-node fanout, and Docker containerization.

Executive summary

  • Project Role: Creator / Sole Developer (Personal Project)
  • Tech Stack: Go, React, WebSocket, Redis Pub/Sub, Docker
  • Challenge: Horizontal scaling of stateful WebSocket connections across multiple nodes without losing message order or connection tracking.

The problem

Traditional real-time applications keep connection state in memory. While simple on a single node, scaling this architecture horizontally introduces a core problem: state fragmentation. A user connected to Server A cannot send a message directly to a user on Server B because Server A has no knowledge of Server B’s active connection handles. To build a robust, scalable system, connection management must be decoupled from the message distribution layer.

Constraints

  • Commodity Hardware Limits: The system must run within strict memory bounds per node, precluding resource-heavy framework layers.
  • WebSocket State Constraints: Active TCP connections are stateful and persistent; they cannot be shared or moved across physical servers without disconnects.
  • Message Delivery Reliability: Messages must be delivered to target clients exactly once, avoiding duplicate writes or message loss due to node restarts.

Technical decisions

  • Go for Backend Services: Selected Go for its light concurrency model. Goroutines allow handling thousands of concurrent connections with low memory footprint, avoiding callback/event loops.
  • Redis Pub/Sub for Messaging Bus: Used Redis Pub/Sub for lightweight cross-instance coordination. Rather than synchronizing global client lists across all servers, Redis acts as a stateless broker. When a message is sent to Server A, it is broadcast to the channel, and Server B reads it to push down to its local clients.
  • Docker for Orchestration: Packaged all services into lightweight containers to reliably test scaling from one node to multiple instances locally.

Trade-offs and results

  • Decoupled Messaging vs. Instantaneous Consistency: Chose eventual consistency via pub/sub instead of distributed locking mechanisms. While a small latency offset exists during high throughput, connection management remains extremely fast and stable.
  • Results: Successfully verified horizontal scaling of stateful connections with zero memory leaks. Goroutine footprints remained below 10KB per idle socket, allowing high concurrent throughput on standard instances.