Skip to main content
Version: 4.x

Scaling horizontally

Now that our application is resilient to temporary network interruptions, let's see how we can horizontally scale it in order to be able to support thousands of concurrent clients.

note
  • Horizontal scaling (also known as "scaling out") means adding new servers to your infrastructure to cope with new demands
  • Vertical scaling (also known as "scaling up") means adding more resources (processing power, memory, storage, ...) to your existing infrastructure

First step: let's use all the available cores of the host. By default, Node.js runs your Javascript code in a single thread, which means that even with a 32-core CPU, only one core will be used. Fortunately, the Node.js cluster module provides a convenient way to create one worker thread per core.

We will also need a way to forward events between the Socket.IO servers. We call this component an "Adapter".

The 'hello' event is forwarded to the other serversThe 'hello' event is forwarded to the other servers

So let's install the cluster adapter:

npm install @socket.io/cluster-adapter

Now we plug it in:

index.js
const express = require('express');
const { createServer } = require('node:http');
const { join } = require('node:path');
const { Server } = require('socket.io');
const sqlite3 = require('sqlite3');
const { open } = require('sqlite');
const { availableParallelism } = require('node:os');
const cluster = require('node:cluster');
const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter');

if (cluster.isPrimary) {
const numCPUs = availableParallelism();
// create one worker per available core
for (let i = 0; i < numCPUs; i++) {
cluster.fork({
PORT: 3000 + i
});
}

// set up the adapter on the primary thread
return setupPrimary();
}

async function main() {
const app = express();
const server = createServer(app);
const io = new Server(server, {
connectionStateRecovery: {},
// set up the adapter on each worker thread
adapter: createAdapter()
});

// [...]

// each worker will listen on a distinct port
const port = process.env.PORT;

server.listen(port, () => {
console.log(`server running at http://localhost:${port}`);
});
}

main();

That's it! This will spawn one worker thread per CPU available on your machine. Let's see it in action:

As you can see in the address bar, each browser tab is connected to a different Socket.IO server, and the adapter is simply forwarding the chat message events between them.

tip

There are currently 5 official adapter implementations:

So you can choose the one that best suits your needs. However, please note that some implementations do not support the Connection state recovery feature, you can find the compatibility matrix here.

note

In most cases, you would also need to ensure that all the HTTP requests of a Socket.IO session reach the same server (also known as "sticky session"). This is not needed here though, as each Socket.IO server has its own port.

More information here.

And that finally completes our chat application! In this tutorial, we have seen how to:

  • send an event between the client and the server
  • broadcast an event to all or a subset of connected clients
  • handle temporary disconnections
  • scale up

You should now have a better overview of the features provided by Socket.IO. Now it's your time to build your own realtime application!

info

You can run this example directly in your browser on: