Learn WebSocket - Performance Optimization
Episode 17 of 34

Learn WebSocket - Performance Optimization

This episode covers making WebSocket applications faster: connection and payload optimization, Node.js clustering with workers, event loop monitoring, client debouncing, and network and TLS optimization.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The application works, but is it fast enough? In this episode we measure and fix: pushing latency down, reducing bandwidth, using every CPU core, and ensuring the Node.js event loop never stalls.

Episode 17 covers performance optimization from four sides: connections, messages, the server, and the client. You will learn techniques that can be applied immediately, from compression to clustering.

Connection Optimization

Keep-Alive and TCP Tuning

A healthy WebSocket connection needs tuning so it is neither wasteful nor dies in vain.

Check TCP parameters
sysctl net.ipv4.tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_intvl
sysctl net.ipv4.tcp_keepalive_probes

The tcp_keepalive_time parameter decides when the kernel starts sending keep-alive packets. For long-lived connections like WebSocket, a sensible value (for example 60 seconds) keeps NATs and load balancers from evicting idle connections.

Socket Buffers

A TCP buffer that is too small holds back throughput; one too large holds memory.

JSSetting the socket buffer
const wss = new WebSocketServer({
  port: 8080,
  perMessageDeflate: true,
  maxPayload: 64 * 1024,
});

maxPayload: 64 * 1024 limits the message size so a single giant message cannot exhaust memory. This limit also prevents attacks that send very large payloads.

Message Optimization

Compression and Minimal Payloads

Message size is bandwidth and latency. Three ways to reduce it:

JSMinimizing the payload
const pesan = {
  t: "chat",          // singkatan tipe
  r: "node",          // singkatan room
  m: "Halo!",         // isi pesan
};
 
ws.send(JSON.stringify(pesan));
  • Minify field names: type becomes t, message becomes m. Saves 30-50 percent.
  • Compression: enable permessage-deflate for repeating payloads.
  • Binary: for numbers and fixed structures, use binary instead of text.

Batching Messages

Sending a hundred small messages costs more than one large message.

JSBatching updates to the client
const antrean = [];
 
setInterval(() => {
  if (antrean.length === 0) return;
  ws.send(JSON.stringify({
    type: "batch",
    items: antrean.splice(0, antrean.length),
  }));
}, 100);

antrean.splice(0, antrean.length) takes all the updates accumulated in 100 ms. A mouse position updated 60 times per second only needs to be sent 10 times per second in one batch.

Server Optimization

Node.js Clustering

Node.js runs on a single CPU core. Clustering uses every core.

JSNode.js cluster for WebSocket
const cluster = require("cluster");
const os = require("os");
 
if (cluster.isPrimary) {
  const jumlah = os.cpus().length;
  for (let i = 0; i < jumlah; i++) {
    cluster.fork();
  }
  cluster.on("exit", (worker) => {
    console.log("worker mati, fork ulang:", worker.process.pid);
    cluster.fork();
  });
} else {
  const { WebSocketServer } = require("ws");
  const wss = new WebSocketServer({ port: 8080 });
  wss.on("connection", (ws) => ws.send("terhubung ke worker " + process.pid));
}

cluster.isPrimary distinguishes the primary process from the workers. Each worker serves its own connections on the same port. The primary process re-forks dead workers to keep availability high.

Monitoring the Event Loop

A blocked event loop slows down every connection. Measure its delay regularly.

JSMeasuring event loop delay
const mulai = Date.now();
 
setInterval(() => {
  const delay = Date.now() - mulai;
  if (delay > 100) {
    console.warn("event loop tersumbat:", delay, "ms");
  }
  mulai = Date.now();
}, 100);

Date.now() - mulai measures the difference between the scheduled interval and the actual run. A delay above 100 ms signals a synchronous task that is too heavy — usually a giant JSON.parse or a blocking operation — and it should be moved to a worker thread.

Client Optimization

Throttling and Virtualization

A frugal client makes the server lighter and the experience smoother.

JSThrottling updates from input
let lastSend = 0;
 
canvas.onmousemove = (e) => {
  const kini = Date.now();
  if (kini - lastSend < 50) return;
  lastSend = kini;
  ws.send(JSON.stringify({ type: "pointer", x: e.x, y: e.y }));
};

kini - lastSend < 50 caps sending at 20 times per second. For long message lists, virtual scrolling renders only the visible rows, so the browser does not freeze when thousands of messages arrive.

Network Optimization

TLS and Distribution

An optimized network cuts latency from the infrastructure side.

  • CDN: keep static assets on a CDN so the server only handles WebSocket.
  • TLS 1.3: fewer handshake round-trips than TLS 1.2.
  • HTTP/2 for the handshake: speeds up the initial request before the upgrade.
  • Geographic location: put servers close to the majority of users.

Keep in mind: the load balancer adds one hop. Measure end-to-end latency before and after adding infrastructure layers.

Closing

Episode 17 gave you performance glasses: every message byte, every synchronous task, and every network layer can become a bottleneck. Optimization starts with measurement, not guesses.

Key takeaways:

  • TCP parameters and maxPayload tune connection behavior at a low level.
  • Minified fields, compression, and binary shrink message size.
  • Batching lowers the send cost for high-frequency updates.
  • Clustering uses every CPU core in Node.js.
  • Monitor event loop delay to detect blocking tasks.
  • Input throttling and virtual scrolling keep the client responsive.

In the next episode we cover monitoring & observability: connection and message metrics, logging, Prometheus and Grafana integration, distributed tracing with OpenTelemetry, and alerting.

Learn WebSocket - Performance Optimization | Learn WebSocket