Episode ini membahas distribusi koneksi WebSocket ke banyak server: tantangan sticky session, perbedaan load balancer Layer 4 dan Layer 7, konfigurasi NGINX dan HAProxy, health check, serta graceful shutdown.

Satu server Node.js hanya bisa melayani puluhan ribu koneksi. Saat aplikasi tumbuh, kalian menambah server — tapi koneksi WebSocket berbeda dari request HTTP biasa. Koneksi WebSocket hidup lama, jadi begitu dilempar ke sebuah server, ia harus bertahan di server itu selamanya.
Episode 15 membahas load balancing untuk WebSocket: mengapa sticky session diperlukan, beda load balancer Layer 4 dan Layer 7, konfigurasi NGINX dan HAProxy, serta bagaimana menjaga server tetap sehat saat deployment.
Request HTTP selesai dalam milidetik, jadi boleh dilempar ke server mana saja. Koneksi WebSocket bertahan berjam-jam. Jika load balancer mengirim request upgrade ke server A tapi frame berikutnya ke server B, koneksi akan putus.
curl -i -H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" \
http://localhost:8080Perintah di atas mengirim handshake upgrade. Load balancer harus mengenali request ini dan mengarahkan seluruh koneksi ke satu backend mulai dari handshake sampai ditutup.
Kunci utamanya adalah sticky session atau affinity: semua frame dari satu koneksi menuju server yang sama.
Tanpa ini, aplikasi stateful seperti chat akan rusak: pesan terpecah di banyak server.
Load balancer bekerja di dua lapisan berbeda.
Layer 4 (TCP) : melihat alamat IP dan port, memforward byte mentah
Layer 7 (HTTP) : melihat isi request, bisa baca header UpgradeLayer 4 sangat cepat dan tidak peduli protokol — ia hanya meneruskan byte. Sayangnya ia tidak bisa membaca header Upgrade, sehingga tidak bisa memastikan sticky session per koneksi.
Layer 7 membaca header HTTP dan memahami handshake WebSocket. Ia bisa mengatur affinity berbasis cookie atau header, tapi biayanya sedikit lebih mahal karena harus mengurai request.
NGINX adalah pilihan paling umum untuk WebSocket di production karena konfigurasinya sederhana.
location /ws {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}Dua header paling penting: proxy_set_header Upgrade $http_upgrade dan proxy_set_header Connection "upgrade". Tanpa keduanya, koneksi WebSocket langsung gagal karena NGINX tidak meneruskan permintaan upgrade. Timeout 3600 detik mencegah NGINX memutus koneksi idle yang sehat.
Untuk beberapa backend, definisikan upstream dengan opsi sticky.
upstream ws_backend {
ip_hash;
server 10.0.1.1:8080;
server 10.0.1.2:8080;
}
server {
listen 80;
location /ws {
proxy_pass http://ws_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}ip_hash mengarahkan klien dengan IP yang sama ke backend yang sama secara konsisten. Untuk distribusi lebih merata, pakai opsi sticky cookie yang berbasis cookie.
HAProxy dikenal sangat efisien untuk koneksi persisten seperti WebSocket.
frontend ws_front
bind *:80
mode http
use_backend ws_back if { hdr(Upgrade) -i websocket }
backend ws_back
mode http
server node1 10.0.1.1:8080 weight 1
server node2 10.0.1.2:8080 weight 1Kondisi { hdr(Upgrade) -i websocket } mengarahkan request yang mengirim header Upgrade ke backend khusus WebSocket. HAProxy menangani koneksi WebSocket secara native tanpa konfigurasi tambahan, selama nilai timeout client dan timeout server disetel panjang (misalnya 3600 detik) agar koneksi yang hidup lebih dari satu jam tidak terputus.
Load balancer perlu tahu server mana yang sehat. Untuk WebSocket, health check sebaiknya di aplikasi, bukan sekadar port terbuka.
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({
status: "ok",
koneksi: wss.clients.size,
}));
return;
}
res.writeHead(404);
res.end();
});Endpoint /healthz melaporkan status server dan jumlah koneksi aktif. Load balancer menandai backend sehat hanya jika endpoint ini merespons 200.
Saat deploy, server lama harus melepas koneksi dengan lembut, bukan langsung dibunuh.
process.on("SIGTERM", () => {
wss.clients.forEach((ws) => {
ws.close(1001, "server akan di-deploy");
});
server.close(() => {
console.log("semua koneksi ditutup, server berhenti");
process.exit(0);
});
setTimeout(() => process.exit(1), 10000).unref();
});Kode 1001 berarti going away. Load balancer yang sehat mendeteksi koneksi keluar dan mengarahkan klien ke backend yang tersisa, sementara klien dengan reconnection logic dari episode 12 pindah tanpa disadari pengguna.
Episode 15 menjelaskan kenapa load balancing WebSocket lebih rumit dari HTTP biasa: koneksi panjang menuntut sticky session, pemahaman header Upgrade, dan perlakuan khusus di load balancer.
Inti yang harus dibawa pulang:
Di episode 16 berikutnya kita membahas horizontal scaling dengan Redis adapter: pub-sub lintas server, sinkronisasi room, dan arsitektur chat yang melayani ribuan server sekaligus.