Skip to main content
Version: 4.x

Performance tuning

Here are some tips to improve the performance of your Socket.IO server:

You might also be interested in scaling to multiple nodes.

At the Socket.IO level

Since, in most cases, the Socket.IO connection will be established with WebSocket, the performance of your Socket.IO server will be strongly linked to the performance of the underlying WebSocket server (ws, by default).

Install ws native add-ons

ws comes with two optional binary add-ons which improve certain operations. Prebuilt binaries are available for the most popular platforms so you don't necessarily need to have a C++ compiler installed on your machine.

  • bufferutil: Allows to efficiently perform operations such as masking and unmasking the data payload of the WebSocket frames.
  • utf-8-validate: Allows to efficiently check if a message contains valid UTF-8 as required by the spec.

To install those packages:

$ npm install --save-optional bufferutil utf-8-validate

Please note that these packages are optional, the WebSocket server will fallback to the Javascript implementation if they are not available. More information can be found here.

Use another WebSocket server implementation

For example, you can use the eiows package, which is a fork of the (now deprecated) uws package:

$ npm install eiows

And then use the wsEngine option:

const { createServer } = require("http");
const { Server } = require("socket.io");

const httpServer = createServer();
const io = new Server(httpServer, {
wsEngine: require("eiows").Server
});

Use a custom parser

If you send binary data over the Socket.IO connection, using a custom parser like the one based on msgpack might be interesting, as by default each buffer will be sent in its own WebSocket frame.

Usage:

Server

const { createServer } = require("http");
const { Server } = require("socket.io");
const parser = require("socket.io-msgpack-parser");

const httpServer = createServer();
const io = new Server(httpServer, {
parser
});

Client

const { io } = require("socket.io-client");
const parser = require("socket.io-msgpack-parser");

const socket = io("https://example.com", {
parser
});

Discard the initial HTTP request

By default, a reference to the first HTTP request of each session is kept in memory. This reference is needed when working with express-session for example (see here), but can be discarded to save memory:

io.engine.on("connection", (rawSocket) => {
rawSocket.request = null;
});

Before:

Memory usage before

After:

Memory usage with request discarded

At the OS level

There are lots of good articles on how to tune your OS to accept a large number of connections. Please see this one or this one for example.

While load testing your Socket.IO server, you will likely reach the two following limits:

  • maximum number of open files

If you can't go over 1000 concurrent connections (new clients are not able to connect), you have most certainly reached the maximum number of open files:

$ ulimit -n
1024

To increase this number, create a new file /etc/security/limits.d/custom.conf with the following content (requires root privileges):

* soft nofile 1048576
* hard nofile 1048576

And then reload your session. Your new limit should now be updated:

$ ulimit -n
1048576
  • maximum number of available local ports

If you can't go over 28000 concurrent connections, you have most certainly reached the maximum number of available local ports:

$ cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999

To increase this number, create a new file /etc/sysctl.d/net.ipv4.ip_local_port_range.conf with the following content (again, requires root privileges):

net.ipv4.ip_local_port_range = 10000 65535

Note: we used 10000 as a lower bound so it does not include the ports that are used by the services on the machine (like 5432 for a PostgreSQL server), but you can totally use a lower value (down to 1024).

Once you reboot your machine, you will now be able to happily go to 55k concurrent connections (per incoming IP).

See also: