myHotTake

Tag: real-time server

  • How to Efficiently Scale WebSocket Servers for Real-Time Use

    If you enjoy this story, feel free to give it a like or share it with friends who might need a little tech inspiration.


    I’m the conductor for a symphony of ants, each carrying tiny notes of music in real-time across a vast field. My goal is to ensure that every note reaches the right musician at the right time without delay. This field is the WebSocket server, and the ants are the messages that need to be delivered instantly to ensure our performance is flawless.

    As the orchestra grows, more ants are needed, and the paths they travel become busier. I realize that if I don’t manage this growth carefully, some notes could get lost, or worse, the performance could become a cacophony. So, I decide to introduce more pathways and conductors, each responsible for a specific section of the field. These pathways are like scaling our WebSocket server horizontally by adding more server instances. Each conductor ensures that the ants in their section don’t collide and that each message finds its way swiftly.

    To keep everything in harmony, I bring in a central coordinator, akin to a load balancer, directing the ants to the least crowded paths. This way, the conductors, or server instances, aren’t overwhelmed, and the ants can deliver their notes efficiently.

    As the performance continues, I also notice some musicians are more popular, receiving more notes than others. To handle this gracefully, I introduce a clever system where ants can prioritize these paths, ensuring the most critical notes reach their destinations first. This is like implementing message prioritization and ensuring that key communications aren’t delayed.

    In the end, by carefully orchestrating the ant pathways and ensuring each conductor is well-coordinated, I achieve a seamless symphony of real-time communication, much like scaling a WebSocket server to handle increasing loads without missing a beat.


    In our ant symphony, the JavaScript code acts as the conductor, directing messages efficiently. Here’s how we can set up a basic WebSocket server using Node.js with the ws library, which will serve as our single conductor initially:

    const WebSocket = require('ws');
    const server = new WebSocket.Server({ port: 8080 });
    
    server.on('connection', (socket) => {
      console.log('A new musician has joined the orchestra.');
    
      socket.on('message', (message) => {
        console.log(`Received a note: ${message}`);
        // Echo the message back to all connected clients
        server.clients.forEach((client) => {
          if (client !== socket && client.readyState === WebSocket.OPEN) {
            client.send(message);
          }
        });
      });
    
      socket.on('close', () => {
        console.log('A musician has left the orchestra.');
      });
    });

    This code represents the single pathway in our field, where each ant (message) is sent to all musicians (clients).

    Scaling the Orchestra

    To scale this up, we can introduce multiple servers (conductors). This is typically done using a load balancer like NGINX, which spreads incoming connections across several WebSocket server instances. Here’s a simple configuration with NGINX:

    http {
        upstream websocket_servers {
            server localhost:8080;
            server localhost:8081;
            server localhost:8082;
        }
    
        server {
            listen 80;
    
            location / {
                proxy_pass http://websocket_servers;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "Upgrade";
                proxy_set_header Host $host;
            }
        }
    }

    In this configuration, NGINX acts as the central coordinator, directing ants to the least crowded path, ensuring load is evenly distributed.

    Message Prioritization

    To handle critical messages, we can implement a simple priority system on our server:

    const messageQueue = [];
    
    function processQueue() {
      if (messageQueue.length > 0) {
        const message = messageQueue.shift();
        // Broadcast the prioritized message
        server.clients.forEach((client) => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(message.content);
          }
        });
      }
      setTimeout(processQueue, 100); // Process the next message in 100ms
    }
    
    server.on('message', (message) => {
      const parsedMessage = JSON.parse(message);
      messageQueue.push(parsedMessage);
      messageQueue.sort((a, b) => b.priority - a.priority);
    });
    
    processQueue();

    Here, messages are queued and sorted by priority, ensuring that critical notes reach their destinations in a timely manner.

    Key Takeaways

    1. Horizontal Scaling: Use multiple WebSocket server instances with a load balancer to distribute connections, just like adding more pathways in the field.
    2. Load Balancing: An external load balancer like NGINX can effectively manage traffic distribution, similar to our central coordinator.
    3. Message Prioritization: Implement a prioritization system to ensure important messages are processed first, akin to prioritizing paths for high-demand musicians.