# Membangun Real-Time Event-Driven Architecture dengan WebSockets dan Redis Pub/Sub

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/membangun-real-time-event-driven-architecture-dengan-websockets-dan-redis-pubsub

---

Pukul dua pagi, ruang server kami mendadak berubah menjadi medan perang digital. Notifikasi Telegram beruntun memerah: memory usage melonjak hingga 92% dan ribuan koneksi WebSocket terputus serentak karena jeda sesaat pada reverse proxy Nginx yang kewalahan menahan lonjakan data flash sale. Saat itulah perdebatan sengit antara saya dan tim senior backend memuncak: apakah kita harus merombak total arsitektur real-time kita menggunakan pendekatan WebSockets murni yang ditopang Redis Pub/Sub lintas node, atau tetap bertahan pada skema long-polling klasik yang sudah terbukti membosankan namun stabil? Pertarungan ini bukan sekadar memilih library, melainkan menentukan hidup-matinya infrastruktur di bawah gempuran jutaan pengguna aktif.

Mari kita bedah langsung performa kedua kubu dalam tabel Head-to-Head Hero Comparison Matrix di bawah ini:

| Metrik Performa | WebSockets + Redis Pub/Sub | Long-Polling + Database Polling |
| :--- | :--- | :--- |
| **Kecepatan & Latency** | 10/10 (Sub-millisecond) | 4/10 (Ratusan milidetik hingga detik) |
| **Efisiensi RAM** | 8/10 (Stabil, event-driven) | 3/10 (Boros connection overhead) |
| **Skalabilitas Horizontal** | 9/10 (Mudah via Redis Cluster) | 4/10 (Beban berat di database master) |
| **Kemudahan Konfigurasi** | 7/10 (Butuh orkestrasi ekstra) | 9/10 (Sangat sederhana) |
| **Ketahanan Jaringan** | 8/10 (Auto-reconnect & Heartbeat) | 5/10 (Rentan timeout saat trafik padat) |

> [!IMPORTANT]
> Memilih arsitektur real-time tanpa memperhitungkan pola distribusi event lintas node server adalah jalan pintas menuju kegagalan sistem saat aplikasi Anda mulai mengalami pertumbuhan organik.

## 1. Pertarungan Filosofi: Dua Cara Pandang Berbeda dalam Memecahkan Masalah

Di satu sudut ring, kita memiliki arsitektur **WebSockets + Redis Pub/Sub** yang mengusung filosofi event-driven murni: *buka satu pipa koneksi TCP permanen, biarkan paket data mengalir dua arah secara instan tanpa basa-basi HTTP header*. Di sudut seberang, ada pendekatan tradisional **HTTP Long-Polling** yang bersandar pada siklus *request-response* berulang-ulang, di mana klien terus-menerus mengetuk pintu server, bertanya, "Ada data baru? Ada data baru?" 

Pendekatan pertama memperlakukan server layaknya pusat saraf yang secara proaktif meneriakkan informasi begitu sebuah event terjadi. Pendekatan kedua memperlakukan server seperti penjaga toko yang malas, hanya merespons saat dipanggil secara eksplisit. Ketika Anda berurusan dengan ribuan klien yang menanti pembaruan harga saham, skor pertandingan sepak bola, atau status pengiriman kurir secara real-time, filosofi pertama bukan lagi sebuah kemewahan, melainkan harga mutlak yang harus dibayar demi kelangsungan sistem.

Namun, teori yang indah ini seringkali berbenturan dengan realita penulisan kode di lapangan. Mari kita lihat bagaimana kedua kubu saling bertarung dalam hal ergonomi kode sehari-hari sebelum kita membawanya ke arena uji beban.

## 2. Ronde 1: Ergonomi Menulis Kode & Pengalaman Konfigurasi Sehari-hari

Menulis kode WebSocket di Node.js atau Go terasa sangat elegan pada awalnya, namun kerumitan sesungguhnya muncul saat Anda harus memastikan pesan dari Node A (yang diakses User X) sampai ke Node B (tempat User Y terhubung). Di sinilah Redis Pub/Sub masuk sebagai jembatan penyelamat.

Mari kita intip implementasi nyata arsitektur ini menggunakan Node.js dan `ioredis` untuk menangani distribusi event lintas node server:

```javascript
const Redis = require('ioredis');
const WebSocket = require('ws');

// Inisialisasi Redis Publisher dan Subscriber terpisah
const publisher = new Redis({ host: 'redis-master.internal', port: 6379 });
const subscriber = new Redis({ host: 'redis-master.internal', port: 6379 });

const wss = new WebSocket.Server({ port: 8080 });
const connectedClients = new Map();

// Langganan ke channel Redis tertentu
subscriber.subscribe('chat_global_room', (err, count) => {
  if (err) {
    console.error('Gagal subscribe ke Redis channel:', err);
  } else {
    console.log(`Berhasil subscribe ke ${count} channel.`);
  }
});

// Tangkap pesan dari Redis Pub/Sub dan sebarkan ke klien WebSocket lokal
subscriber.on('message', (channel, message) => {
  console.log(`Menerima event dari channel ${channel}: ${message}`);
  forVMSClients(message);
});

function forVMSClients(message) {
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

wss.on('connection', (ws, req) => {
  const clientId = generateUniqueId();
  connectedClients.set(clientId, ws);

  ws.on('message', (incomingData) => {
    // Publikasikan event ke Redis, bukan langsung broadcast lokal
    publisher.publish('chat_global_room', incomingData);
  });

  ws.on('close', () => {
    connectedClients.delete(clientId);
  });
});

function generateUniqueId() {
  return Math.random().toString(36.substring(2, 9));
}
```

> [!TIP]
> Selalu pisahkan instansiasi klien Redis untuk `publisher` dan `subscriber`. Berbagi koneksi tunggal untuk kedua operasi ini dalam satu thread event-loop dapat menyebabkan blocking command dan merusak latensi real-time.

Konfigurasi di atas terlihat ringkas, namun bayangkan jika Anda harus menulis ulang logika reconnect, heartbeat ping/pong untuk mendeteksi koneksi zombie, serta manajemen memori saat klien terputus secara tiba-tiba. Di sinilah tim sering terjebak dalam jebakan kompleksitas operasional yang mengarah langsung ke babak pengujian beban ekstrem.

## 3. Ronde 2: Uji Ketahanan di Bawah Gempuran Beban Ekstrem

Kami mensimulasikan 50.000 klien konkuren menggunakan skrip `k6` untuk menguji ketahanan infrastruktur. Pada fase awal pengujian, Nginx bertindak sebagai reverse proxy yang meneruskan koneksi ke cluster Node.js backend. Konfigurasi Nginx untuk WebSocket connection upgrading memegang peranan krusial agar tidak terjadi bottleneck di layer network.

Berikut adalah konfigurasi Nginx production-ready yang kami gunakan untuk menopang lalu lintas WebSocket berskala masif:

```nginx
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 10240;
    use epoll;
    multi_accept on;
}

http {
    upstream websocket_backend {
        server 10.0.1.10:8080;
        server 10.0.1.11:8080;
        keepalive 64;
    }

    server {
        listen 80;
        server_name ws.production.internal;

        location / {
            proxy_pass http://websocket_backend;
            proxy_http_version 1.1;
            
            # Header krusial untuk upgrade protokol ke WebSocket
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            # Timeout diperpanjang untuk menjaga koneksi tetap hidup
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;
            proxy_buffering off;
        }
    }
}
```

Saat uji beban `k6` digeber hingga 50.000 virtual users mengirimkan pesan secara simultan, grafik CPU pada server Node.js menunjukkan lonjakan moderat di angka 45%, sementara Redis Pub/Sub melahap throughput hingga 120.000 message/detik tanpa hambatan berarti. Bandingkan dengan eksperimen long-polling sebelumnya di mana database MySQL mengalami *connection pool exhaustion* akibat jutaan query INSERT/SELECT per menit. Perbedaan performanya bagaikan membandingkan kereta peluru Shinkansen dengan becak tua di jalan menanjak.

Namun, performa tinggi ini tidak datang secara gratis. Kita perlu melihat dampak nyatanya terhadap konsumsi sumber daya server dan efisiensi biaya infrastruktur.

## 4. Ronde 3: Konsumsi Sumber Daya Server & Efisiensi Biaya Nyata

Ketika berbicara tentang infrastruktur skala besar, setiap megabit RAM dan setiap siklus CPU diterjemahkan langsung ke tagihan cloud bulanan Anda. Arsitektur WebSocket memiliki karakteristik unik: ia mengonsumsi memori secara konstan per koneksi aktif karena kernel sistem operasi harus mempertahankan soket TCP tetap terbuka.

Pada pengujian kami, setiap 10.000 koneksi WebSocket aktif memakan sekitar 350MB hingga 500MB RAM di sisi Node.js, tergantung pada ukuran payload objek state klien yang disimpan di dalam memori heap. Sementara itu, Redis Pub/Sub bekerja sangat efisien karena sifatnya yang *fire-and-forget*—Redis tidak menyimpan riwayat pesan (pesan langsung dibuang jika tidak ada subscriber aktif yang mendengarkan pada detik itu juga).

> [!WARNING]
> Redis Pub/Sub bukanlah sebuah message broker persisten seperti RabbitMQ atau Kafka. Jika sebuah node server down saat pesan dipublikasikan, pesan tersebut akan hilang selamanya tanpa jejak di dalam Redis.

Jika aplikasi Anda menuntut jaminan pengiriman data yang tidak boleh hilang (guaranteed delivery), Anda wajib mengombinasikan Redis Pub/Sub dengan *Persistent Stream* seperti Redis Streams atau beralih total ke arsitektur Message Broker berbasis disk. Memahami batasan ini menyelamatkan kami dari kesalahan fatal dalam mendesain sistem keuangan real-time.

## 5. Kapan Wajib Menggunakan WebSockets + Redis Pub/Sub

Anda wajib memilih kombinasi WebSockets dan Redis Pub/Sub ketika proyek Anda memenuhi kriteria operasional berikut:
- **Aplikasi Membutuhkan Latency Instan:** Layanan seperti *live trading dashboard*, game multiplayer turn-based, aplikasi kolaborasi dokumen secara bersamaan (seperti Google Docs), atau sistem notifikasi instan.
- **Infrastruktur Berjalan Secara Horizontal (Multi-Node):** Backend Anda di-deploy di atas Kubernetes atau beberapa instance EC2 di balik Load Balancer, di mana klien yang terhubung ke Node A harus bisa menerima pesan dari aksi yang dieksekusi di Node B.
- **Trafik Klien Konkuren Tinggi:** Jumlah pengguna aktif yang online secara bersamaan sangat besar, di mana pendekatan HTTP polling akan melumpuhkan database relational Anda dalam hitungan detik.

## 6. Kapan Wajib Menggunakan HTTP Polling / Server-Sent Events (SSE)

Sebaliknya, tinggalkan WebSockets dan gunakan alternatif yang lebih sederhana apabila:
- **Komunikasi Bersifat Searah (Unidirectional):** Jika klien hanya perlu menerima pembaruan dari server (seperti feed berita, status progres rendering video, atau dashboard monitoring sederhana), **Server-Sent Events (SSE)** jauh lebih mudah diimplementasikan dan bekerja secara native di atas protokol HTTP tanpa perlu konfigurasi proxy yang rumit.
- **Arsitektur Serverless (FaaS):** Jika backend Anda berjalan di AWS Lambda atau Google Cloud Functions, WebSocket adalah pilihan yang buruk karena arsitektur serverless bersifat ephemeral (berumur pendek) dan tidak dirancang untuk mempertahankan koneksi TCP persisten.
- **Skala Aplikasi Masih Kecil:** Jika aplikasi Anda baru melayani ratusan pengguna, jangan menyiksa diri dengan kompleksitas kluster Redis dan manajemen soket. Gunakan *polling* sederhana atau *short-polling* dengan interval 5 detik.

## 7. Keputusan Akhir: Rekomendasi Jujur untuk Tim Engineering

Sebagai arsitek yang pernah merasakan luka bakar akibat salah memilih teknologi di tengah produksi, kesimpulan saya sangat jelas: **WebSockets yang ditopang Redis Pub/Sub adalah standar emas untuk sistem komunikasi dua arah berskala besar, asalkan tim Anda siap menanggung kompleksitas operasionalnya.**

Jangan gunakan arsitektur ini hanya karena ia terdengar keren atau sedang tren di Hacker News. Gunakan karena domain bisnis Anda benar-benar menuntut kecepatan sub-millisecond dan skalabilitas horizontal lintas server. Jika kebutuhan Anda sederhana, pilihlah solusi yang paling membosankan, karena dalam dunia infrastruktur, kebosanan adalah sinonim dari stabilitas.