Hands-on System Design with Java Spring Boot

Hands-on System Design with Java Spring Boot

Building Discord : From Socket to Scale

Day 51: Node Rebalancing - Minimizing Connection Chaos in Distributed Gateways

Jul 10, 2026
∙ Paid

Introduction: The Problem with Traditional Load Balancing

Imagine you’re running a real-time messaging platform like Discord. You have 500,000 users connected to your servers through WebSocket connections, distributed across 5 gateway nodes. Each connection represents an active session with chat history, voice channel state, and user preferences loaded into memory.

One day, traffic spikes during a major gaming event. You need to add a 6th server to handle the load. You click “Deploy” and within seconds, everything breaks:

  • 833,000 connections start reconnecting (that’s 83% of your total users)

  • Your authentication service, built to handle 10,000 requests per second, gets hit with 100,000

  • Redis connection pools exhaust trying to restore session state

  • The new server crashes from the reconnection tsunami

  • Users across the platform see “Connection Lost” errors

What just happened? You fell into the “Spring Boot Trap.”


The Spring Boot Trap: When Frameworks Hide Critical Failures

A typical approach to building a WebSocket gateway might look like this:

@Configuration
public class LoadBalancerConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

This works great for 10,000 connections. The load balancer uses simple round-robin distribution, and life is good. But here’s what Spring’s LoadBalancer does under the hood:

// Simplified version of what happens
int assignedNode = hash(connectionId) % nodeCount;

This is modulo arithmetic. When you have 3 nodes, a connection with hash value 100 goes to node 1 (100 % 3 = 1). When you add a 4th node, that same connection now belongs to node 0 (100 % 4 = 0). The math changes for almost every connection.

Let’s see the damage:

  • 3 nodes → 4 nodes: 75% of connections reassigned

  • 5 nodes → 6 nodes: 83% of connections reassigned

  • 10 nodes → 11 nodes: 91% of connections reassigned

The framework doesn’t understand that WebSocket connections are stateful. It treats them like ephemeral HTTP requests that can be freely reassigned. This assumption costs you dearly at scale.


The Failure Mode: Cascading Collapse

When you force 83% of active users to reconnect simultaneously, here’s the chain reaction:

Stage 1: The Reconnect Storm

Clients detect their socket closed and immediately retry. At 500,000 connections per second, your authentication service starts queuing requests. Response times go from 50ms to 5 seconds.

Stage 2: Memory Allocation Death Spiral

Every reconnection allocates objects:

  • ByteBuffer instances for parsing WebSocket frames

  • ConcurrentHashMap entries for session tracking

  • String objects for user IDs and channel names

Your Java heap’s Eden space (where new objects live) fills in seconds. The garbage collector runs, pausing all application threads. This is called a “stop-the-world” pause, and it can last 500 milliseconds or more.

Stage 3: TCP Queue Overflow

Your new server can only queue 128 incoming connections by default (the SO_BACKLOG setting). When 100,000 clients try to connect simultaneously, the operating system drops connections. Users see timeout errors.

Stage 4: State Desynchronization

Connections that do successfully reconnect discover their session state is corrupted. The old node hasn’t flushed its in-memory write buffer to Redis yet, so the new node loads stale data. Users lose their chat scroll position, voice channel state becomes confused, and notification settings revert.

This isn’t a theoretical problem. When Discord’s team added nodes to their gateway cluster in 2017 without proper consistent hashing, they measured 12 minutes of degraded service affecting 2 million users.


The Solution: Consistent Hashing with Virtual Nodes

The fix comes from a technique invented at Akamai in 1997 called consistent hashing. It’s the secret behind every large-scale distributed system: Amazon’s Dynamo database, Apache Cassandra, memcached, and yes, Discord’s gateway infrastructure.

Here’s the core idea: instead of using modulo math that breaks when the node count changes, we map both nodes and connections onto a circular number line from 0 to 4,294,967,295 (that’s 2^32 - 1).

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 javap · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture