I have 3 nodejs apps running on a GCP compute engine instance(2cpu, 2GB ram, ubuntu 20.04) with Nginx reverse proxy. One of them is a socket.io chat server. The socket.io app uses @socket.io/cluster-adapter to utilize all available CPU cores. I followed this tutorial to update the Linux settings to get maximum number of connections. Here is the output of ulimit command,
core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 7856 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 500000 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 7856 virtual memory (kbytes, -v) unlimited cat /proc/sys/fs/file-max 2097152 /etc/nginx/nginx.conf
user www-data; worker_processes auto; worker_rlimit_nofile 65535; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 30000; # multi_accept on; } ... /etc/nginx/sites-available/default
... //socket.io part location /socket.io/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy false; proxy_pass http://localhost:3001/socket.io/; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ... My chat server code,
const os = require("os"); const cluster = require("cluster"); const http = require("http"); const { Server } = require("socket.io"); const { setupMaster, setupWorker } = require("@socket.io/sticky"); const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter"); const { response } = require("express"); const PORT = process.env.PORT || 3001; const numberOfCPUs = os.cpus().length || 2; if (cluster.isPrimary) { const httpServer = http.createServer(); // setup sticky sessions setupMaster(httpServer, { loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection" }); // setup connections between the workers setupPrimary(); cluster.setupPrimary({ serialization: "advanced", }); httpServer.listen(PORT); for (let i = 0; i < numberOfCPUs; i++) { cluster.fork(); } cluster.on("exit", (worker) => { console.log(`Worker ${worker.process.pid} died`); cluster.fork(); }); } //worker process else { const express = require("express"); const app = express(); const Chat = require("./models/chat"); const mongoose = require("mongoose"); const request = require("request"); //todo remove var admin = require("firebase-admin"); var serviceAccount = require("./serviceAccountKey.json"); const httpServer = http.createServer(app); const io = require("socket.io")(httpServer, { cors: { origin: "*", methods: ["GET", "POST"], }, transports: "websocket", }); mongoose.connect(process.env.DB_URL, { authSource: "admin", user: process.env.DB_USERNAME, pass: process.env.DB_PASSWORD, }); app.use(express.json()); app.get("/", (req, res) => { res .status(200) .json({ status: "success", message: "Hello, I'm your chat server.." }); }); // use the cluster adapter io.adapter(createAdapter()); // setup connection with the primary process setupWorker(io); io.on("connection", (socket) => { activityLog( "Num of connected users: " + io.engine.clientsCount + " (per CPU)" ); ... //chat implementations }); } Load test client code,
const { io } = require("socket.io-client"); const URL = //"https://myserver.com/"; const MAX_CLIENTS = 6000; const CLIENT_CREATION_INTERVAL_IN_MS = 100; const EMIT_INTERVAL_IN_MS = 300; //1000; let clientCount = 0; let lastReport = new Date().getTime(); let packetsSinceLastReport = 0; const createClient = () => { const transports = ["websocket"]; const socket = io(URL, { transports, }); setInterval(() => { socket.emit("chat_event", {}); }, EMIT_INTERVAL_IN_MS); socket.on("chat_event", (e) => { packetsSinceLastReport++; }); socket.on("disconnect", (reason) => { console.log(`disconnect due to ${reason}`); }); if (++clientCount < MAX_CLIENTS) { setTimeout(createClient, CLIENT_CREATION_INTERVAL_IN_MS); } }; createClient(); const printReport = () => { const now = new Date().getTime(); const durationSinceLastReport = (now - lastReport) / 1000; const packetsPerSeconds = ( packetsSinceLastReport / durationSinceLastReport ).toFixed(2); console.log( `client count: ${clientCount} ; average packets received per second: ${packetsPerSeconds}` ); packetsSinceLastReport = 0; lastReport = now; }; setInterval(printReport, 5000); As you can see from the code, I'm only using websocket for transports. So, it should be able to serve up to 8000 connections as per this StackOverflow answer. But when I run the load test, the server becomes unstable after 1600 connections. And CPU usage goes up to 90% and memory usage up to 70%. I couldn’t find anything in the Nginx error log. How can increase the number of connections to at least 8000? Should I upgrade the instance or change any Linux settings? Any help would be appreciated.
UPDATE I removed everything related to clustering and ran it again as a regular single-threaded nodejs app. This time, the result was a little better, 2800 stable connections (CPU usage 40%., memory usage 50%). Please note that I'm not performing any disk I/O during the test.