L
Node.js • Literally Web ID

Arsitektur Real-Time Web: Perbandingan Performa Soketi (Self-hosted Pusher Compatible) vs Node.js Socket.io untuk Notifikasi Pengguna

⏱️ 18 menit baca • 📅 12 Oct 2026 • 👁️ 1 views
Arsitektur Real-Time Web: Perbandingan Performa Soketi (Self-hosted Pusher Compatible) vs Node.js Socket.io untuk Notifikasi Pengguna

Arsitektur Real-Time Web: Perbandingan Performa Soketi (Self-hosted Pusher Compatible) vs Node.js Socket.io untuk Notifikasi Pengguna

1. Konteks Industri & Analisis Akar Masalah

Di era aplikasi modern, pengguna menuntut interaktivitas instan. Notifikasi detik-kesekian, pembaruan dasbor real-time, dan kolaborasi langsung bukan lagi fitur tambahan, melainkan standar esensial. Secara historis, web beroperasi pada model request-response HTTP yang bersifat stateless. Untuk mengakalinya, teknik lama seperti HTTP Short Polling dan Long Polling digunakan, yang terbukti membebani jaringan dan server karena overhead header HTTP berulang serta koneksi TCP yang konstan dibuka-tutup.

Protokol WebSocket hadir untuk menyelesaikan masalah ini dengan menyediakan saluran komunikasi full-duplex persistent melalui satu koneksi TCP tunggal. Namun, mengelola ribuan hingga ratusan ribu koneksi WebSocket secara konkuren di tingkat produksi mendatangkan tantangan baru yang kompleks: manajemen memori per koneksi, latensi pengiriman pesan (message latency), skalabilitas horizontal, dan ketahanan terhadap lonjakan beban (traffic spikes).

Sebelum penerapan arsitektur real-time yang terstandarisasi, organisasi sering kali menderita masalah memory leaks pada runtime Node.js standar, overhead CPU yang masif akibat pengelolaan event loop yang tidak efisien, dan kegagalan sinkronisasi status ketika server diskalakan secara horizontal tanpa broker pesan yang tepat. Sesudah penerapan arsitektur terstruktur menggunakan Soketi berbasis uWebSockets.js atau Socket.io dengan Redis Pub/Sub, sistem mampu menangani high concurrency dengan footprint memori minimal, latensi sub-millisecond, dan fault tolerance yang teruji di lingkungan enterprise.

2. Konsep Inti & Deep Dive Arsitektur

Untuk memahami perbedaan performa antara Soketi dan Socket.io, kita harus menyelami bagaimana runtime di balik layar mengelola I/O dan memori.

Socket.io dan Event Loop Node.js

Socket.io dibangun di atas arsitektur Node.js standar menggunakan event loop libuv. Setiap koneksi WebSocket mempertahankan objek soket di memori heap V8. Meskipun Node.js sangat handal untuk I/O asinkron, pengelolaan ribuan koneksi WebSocket murni di Node.js rentan terhadap garbage collection (GC) pauses jika alokasi objek tidak dioptimalkan. Socket.io menambahkan lapisan protokol abstrak (mencakup fallback ke HTTP long-polling, packet encoding/decoding, dan room management) yang menambah overhead CPU.

Soketi dan Core uWebSockets.js (C++)

Soketi ditulis dalam TypeScript di atas pustaka uWebSockets.js. Inti dari uWebSockets.js bukanlah JavaScript murni, melainkan pembungkus tingkat tinggi (wrapper) untuk pustaka C++ uWebSockets yang dirancang khusus untuk kecepatan ekstrem dan konsumsi memori minimal. Pendekatan ini memotong overhead V8 heap allocation dengan mengelola buffer memori langsung di sisi C++, menghasilkan performa throughput yang jauh lebih tinggi dan latensi yang lebih konsisten.

+-------------------------------------------------------------------+
|                    Client Layers (Web / Mobile)                   +
+-------------------------------------------------------------------+
                                  |
                      (WebSocket Protocol / TCP)
                                  v
+-------------------------------------------------------------------+
|                        Nginx Reverse Proxy                        +
|                 (SSL Termination & Load Balancing)                +
+-------------------------------------------------------------------+
             /                                       \
            / (Upstream)                              \ (Upstream)
           v                                           v
+-----------------------+                   +-----------------------+
|  Soketi Instance 01   |                   |  Soketi Instance 02   | 
|   (uWebSockets.js)    |                   |   (uWebSockets.js)    |
+-----------------------+                   +-----------------------+
           \                                           /
            \                                         /
             +---------------------------------------+
             |             Redis Cluster             |
             |     (Pub/Sub Adapter for Scaling)     |
             +---------------------------------------+

3. Perbandingan Komparatif / Analisis Pendekatan

Berikut adalah analisis komprehensif antara Soketi dan Node.js Socket.io:

Parameter Komparasi Soketi (uWebSockets.js Core) Node.js Socket.io Standard
Bahasa Inti / Runtime C++ (via Node.js Binding) / uWebSockets JavaScript / Node.js V8 Heap
Konsumsi Memori (RAM) Sangat Rendah (~5-10 KB per koneksi) Sedang (~30-50 KB per koneksi)
Throughput (Req/sec) Sangat Tinggi (Ratusan ribu msg/s) Menengah ke Tinggi
Latensi (P99) Sub-millisecond 2 - 10 milidetik
Kompatibilitas Klien 100% Kompatibel dengan Pusher SDK Membutuhkan Klien Khusus Socket.io
Fitur Fallback Tidak ada (Murni WebSocket) Mendukung HTTP Long-Polling
Kurva Pembelajaran Menengah (Konfigurasi Pusher-compatible) Rendah (Dokumentasi sangat luas)

4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum memulai implementasi, pastikan environment Anda memenuhi spesifikasi berikut:

  • OS: Ubuntu Server 22.04 LTS / Debian 12 (Disarankan Linux berbasis glibc 2.31+)
  • Runtime: Node.js versi 18.x LTS atau 20.x LTS
  • Containerization: Docker & Docker Compose v2+
  • Broker: Redis Server 7.x

Verifikasi instalasi Docker dan Node.js pada terminal Anda:

node -v
docker --version
docker compose version

Tune parameter kernel Linux (/etc/sysctl.conf) untuk menangani koneksi konkuren dalam jumlah besar (C10K/C100K problem):

fs.file-max = 2097152
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1

Terapkan perubahan dengan perintah:

sysctl -p

5. Panduan Implementasi Langkah demi Langkah

A. Implementasi Soketi dengan Docker Compose & Redis

Buat struktur direktori proyek:

mkdir -p /opt/soketi-cluster && cd /opt/soketi-cluster

Buat file konfigurasi Soketi config.json:

{
  "debug": false,
  "port": 6001,
  "appManager": {
    "apps": [
      {
        "id": "app-id-production",
        "key": "app-key-secret",
        "secret": "app-secret-hash",
        "maxConnections": -1,
        "enableClientMessages": true
      }
    ]
  },
  "scaling": {
    "redis": {
      "host": "redis",
      "port": 6379
    }
  }
}

Buat file docker-compose.yml:

version: '3.8'

services:
  redis:
    image: redis:7-alpine
    container_name: soketi-redis
    restart: always
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - soketi-net

  soketi:
    image: quay.io/soketi/soketi:16-16-alpine
    container_name: soketi-server
    restart: always
    ports:
      - "6001:6001"
    volumes:
      - ./config.json:/app/config.json
    environment:
      - SOKETI_CONFIG=/app/config.json
    depends_on:
      - redis
    networks:
      - soketi-net

volumes:
  redis_data:

networks:
  soketi-net:
    driver: bridge

Jalankan layanan:

docker compose up -d

B. Implementasi Custom Node.js Socket.io Server dengan Redis Pub/Sub

Inisialisasi proyek Node.js baru:

mkdir -p /opt/socketio-cluster && cd /opt/socketio-cluster
npm init -y
npm install express socket.io @socket.io/redis-adapter redis

Buat file implementasi server server.js:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const { createClient } = require('redis');
const { createAdapter } = require('@socket.io/redis-adapter');

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"]
  }
});

(async () => {
  const pubClient = createClient({ url: 'redis://localhost:6379' });
  const subClient = pubClient.duplicate();

  await Promise.all([pubClient.connect(), subClient.connect()]);

  io.adapter(createAdapter(pubClient, subClient));

  io.on('connection', (socket) => {
    console.log(`Client connected: ${socket.id}`);

    socket.on('join-room', (room) => {
      socket.join(room);
      console.log(`Socket ${socket.id} joined room: ${room}`);
    });

    socket.on('send-notification', (data) => {
      io.to(data.room).emit('notification', data.payload);
    });

    socket.on('disconnect', () => {
      console.log(`Client disconnected: ${socket.id}`);
    });
  });

  const PORT = process.env.PORT || 3000;
  server.listen(PORT, () => {
    console.log(`Socket.io server running on port ${PORT}`);
  });
})();

6. Bedah Kode & Penjelasan Detail Baris Kunci

  • socket.io/redis-adapter: Baris ini sangat krusial ketika Anda melakukan scaling horizontal. Tanpa adapter Redis, instans Server A tidak akan tahu jika klien di Server B harus menerima event pesan yang sama. Adapter mendengarkan channel Redis Pub/Sub dan meneruskannya ke seluruh instans lokal.
  • uWebSockets.js di dalam Soketi: Menggunakan eventing C++ non-blocking yang menghemat siklus CPU untuk parsing frame WebSocket secara native tanpa overhead V8 Garbage Collector.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Dalam sistem notifikasi e-commerce skala besar dengan 50.000 pengguna aktif bersamaan, penggunaan Soketi mengurangi penggunaan RAM dari rata-rata 2.5 GB (pada Socket.io murni) menjadi sekitar 400 MB. Latency pengiriman status pesanan turun drastis dari 12ms menjadi 0.8ms, memastikan pengalaman pengguna yang benar-benar instan.

8. Common Pitfalls, Anti-Patterns & Solusi

[!WARNING] Anti-Pattern Terbesar: Menjalankan Node.js Socket.io atau Soketi tanpa pembatasan file descriptor sistem operasi (nofile). Hal ini akan menyebabkan error EMFILE: too many open files ketika koneksi mencapai batas default sistem (biasanya 1024).

Solusi Konfigurasi Systemd Service (/etc/systemd/system/soketi.service):

[Service]
LimitNOFILE=65535
ExecStart=/usr/bin/node /opt/socketio-cluster/server.js
Restart=always
User=www-data

9. Benchmark, Metrik Kinerja & Tuning Performa

Gunakan alat load testing k6 untuk menguji performa server WebSocket Anda. Buat file load-test.js:

import ws from 'k6/ws';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 1000 },
    { duration: '3m', target: 5000 },
    { duration: '1m', target: 0 },
  ],
};

export default function () {
  const url = 'ws://localhost:6001/app/app-id-production?protocol=7&client=js&version=7.0.3';
  
  const res = ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      console.log('Connected');
    });
    socket.on('close', function () {
      console.log('Disconnected');
    });
  });

  check(res, { 'status is 101': (r) => r && r.status === 101 });
}

Jalankan pengujian:

k6 run load-test.js

10. Checklist Penerapan Produksi & Kesimpulan

  • Konfigurasi sysctl.conf untuk file descriptors dan network stack telah diterapkan.
  • Reverse proxy Nginx dikonfigurasi dengan proxy_set_header Upgrade $http_upgrade; dan timeout yang diperpanjang.
  • Redis Cluster menggunakan mekanisme password authentication dan replikasi master-slave.
  • Monitoring metrik CPU, RAM, dan active WebSocket connections diintegrasikan dengan Prometheus & Grafana.

Pemilihan antara Soketi dan Node.js Socket.io bergantung pada kebutuhan spesifik organisasi Anda. Jika prioritas utama adalah efisiensi memori mutlak, latensi rendah, dan kompatibilitas ekosistem Pusher, Soketi adalah pilihan terbaik. Namun, jika tim Anda sangat bergantung pada fitur kustom Socket.io seperti fallback transport dan abstraksi kamar tingkat lanjut, Node.js Socket.io dengan Redis Adapter tetap menjadi pilihan yang sangat solid.

🏷️ Tags: #Redis #Websocket #Node.js #Soketi #Socket.io