Membangun Real-Time Notification System Menggunakan React, WebSockets, dan Redis Pub/Sub
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap pengembangan aplikasi web modern, ekspektasi pengguna terhadap kecepatan dan responsivitas sistem telah mencapai tingkat yang belum pernah terjadi sebelumnya. Dasbor finansial, aplikasi kolaborasi real-time, sistem pelacakan logistik, hingga platform e-commerce interaktif menuntut pembaruan data yang terjadi seketika (instantaneous). Pendekatan tradisional seperti HTTP Short Polling atau Long Polling terbukti menjadi beban yang tidak efisien bagi arsitektur infrastruktur web modern. Setiap kali klien menanyakan pembaruan data melalui polling, server harus memproses header HTTP baru, membuka koneksi TCP berulang kali, dan menghabiskan siklus CPU serta memori yang masif. Hal ini menyebabkan lonjakan latensi, pemborosan bandwidth jaringan, dan kerentanan terhadap kelelahan sumber daya server (resource exhaustion).
Sebagai Principal Web & Infrastructure Systems Architect, saya sering melihat insiden di mana lonjakan traffic mendadak menyebabkan kegagalan sistem (cascade failure) karena backend kewalahan melayani jutaan request polling yang redundan. Selain itu, ketika aplikasi berkembang dan di-deploy ke dalam banyak instans server (horizontal scaling), masalah baru muncul: bagaimana menyinkronkan status atau pesan antar-klien yang terhubung ke server WebSocket yang berbeda? Jika Klien A terhubung ke Server Node.js #1 dan Klien B terhubung ke Server Node.js #2, pesan yang dikirimkan oleh Klien A tidak akan secara otomatis sampai ke Klien B tanpa adanya lapisan perantara komunikasi antar-server.
Sebelum penerapan arsitektur WebSocket yang ditenagai oleh Redis Pub/Sub, sistem pada umumnya mengandalkan mekanisme polling berbasis interval 3 hingga 5 detik. Kondisi Sebelum vs Sesudah penerapan menunjukkan perbedaan drastis:
- Sebelum (HTTP Polling): Beban CPU server tinggi secara konstan akibat pembukaan dan penutupan koneksi TCP yang masif, penggunaan bandwidth membengkak hingga 85% untuk transmisi HTTP header yang berulang, latensi rata-rata mencapai 2000ms - 5000ms, dan ketidakmampuan melakukan real-time broadcasting lintas instans server.
- Sesudah (WebSocket + Redis Pub/Sub): Koneksi TCP dijaga tetap terbuka secara persisten (full-duplex), penggunaan bandwidth efisien karena hanya mengirimkan frame data biner/teks minimalis (beberapa byte), latensi drop drastis di bawah 50ms, dan skalabilitas horizontal tercapai dengan sempurna berkat kemampuan message brokering Redis.
2. Konsep Inti & Deep Dive Arsitektur
Untuk membangun sistem notifikasi real-time yang handal, kita perlu memahami interaksi mendalam antara klien React, server backend (Node.js/Go/PHP), dan Redis sebagai in-memory data store serta pub/sub message broker.
WebSocket beroperasi di atas protokol TCP dengan melakukan proses handshake awal menggunakan HTTP. Setelah handshake berhasil, koneksi di-upgrade (connection upgrade) menjadi protokol WebSocket (ws:// atau wss://). Berbeda dengan HTTP yang bersifat stateless dan request-response, WebSocket menyediakan saluran komunikasi dua arah (full-duplex) di mana klien maupun server dapat saling mengirim data kapan saja tanpa harus menunggu permintaan dari pihak lain.
Namun, ketika server backend direplikasi menjadi beberapa instans untuk menangani jutaan koneksi bersamaan (high concurrency), arsitektur WebSocket tunggal akan pecah. Di sinilah Redis Pub/Sub berperan sebagai jantung distribusi pesan. Redis menjalankan pola Publisher-Subscriber di mana server backend bertindak sebagai publisher yang memancarkan pesan ke sebuah channel Redis, dan seluruh instans server backend yang berlangganan (subscriber) akan menerima pesan tersebut secara instan dan meneruskannya ke klien WebSocket yang terhubung secara lokal.
Berikut adalah representasi diagram alur arsitektur sistem secara menyeluruh:
+-----------------------+ +-----------------------+
| React Client A | | React Client B |
+-----------------------+ +-----------------------+
^ ^
| (WebSocket WSS) | (WebSocket WSS)
v v
+-----------------------+ +-----------------------+
| Backend Server Instans 1 | | Backend Server Instans 2 |
| (Node.js / WS Server)| | (Node.js / WS Server)|
+-----------------------+ +-----------------------+
^ ^
| |
+------------+ +----------------+
| |
v v
+-----------------------+
| Redis Server |----
| (Pub/Sub Broker) | |
+-----------------------+ |
^ |
| (Trigger) |
+-----------------------+ |
| Database / Worker |<--+
+-----------------------+
Alur eksekusi saat sebuah notifikasi baru dibuat:
- Layanan latar belakang atau API Endpoint memicu pembuatan notifikasi baru dan menyimpannya ke Database.
- Server backend mempublikasikan (publish) payload notifikasi ke channel Redis (contoh:
notifications:channel). - Semua instans server backend yang terhubung ke Redis mendengarkan (subscribe) channel tersebut.
- Setiap instans server backend menerima pesan secara simultan dan memeriksa apakah klien WebSocket lokal mereka memiliki hak akses ke notifikasi tersebut.
- Pesan dikirimkan melalui koneksi WebSocket yang aktif ke browser pengguna di React Client.
3. Perbandingan Komparatif / Analisis Pendekatan
Sebelum memutuskan menggunakan arsitektur WebSocket dengan Redis Pub/Sub, penting untuk membandingkannya dengan pendekatan alternatif lain yang sering dipertimbangkan oleh arsitek sistem.
| Parameter Komparasi | HTTP Short/Long Polling | Server-Sent Events (SSE) | WebSocket + Redis Pub/Sub |
|---|---|---|---|
| Arah Komunikasi | Unidirectional (Klien -> Server) | Unidirectional (Server -> Klien) | Bidirectional (Full-Duplex) |
| Konsumsi Jaringan | Sangat Tinggi (Banyak Overhead Header) | Rendah (Streaming HTTP tunggal) | Sangat Rendah (Frame biner/teks minimal) |
| Latensi | Tinggi (Bergantung interval polling) | Rendah (< 100ms) | Sangat Rendah (< 30ms) |
| Skalabilitas Horisontal | Mudah, tapi membebani database | Mudah dengan Nginx proxy | Membutuhkan Message Broker (Redis/Kafka) |
| Dukungan Browser | Universal (100%) | Universal (Kecuali IE lawas) | Universal di semua browser modern |
| Kompleksitas Implementasi | Rendah | Menengah | Tinggi |
| Penggunaan Memori Server | Rendah | Menengah (Per koneksi HTTP) | Menengah-Tinggi (Persistent socket) |
[!WARNING] Jangan pernah menggunakan arsitektur WebSocket tanpa mekanisme heartbeat (ping-pong) dan reconnection strategy di sisi klien. Jaringan seluler atau proxy perusahaan (corporate firewall) sering kali memutuskan koneksi TCP yang diam (idle) dalam kurun waktu tertentu, yang akan menyebabkan klien mengira koneksi masih tersambung padahal sudah terputus (zombie connection).
4. Prasyarat Sistem & Persiapan Lingkungan
Untuk menjalankan arsitektur ini secara lokal maupun produksi, pastikan lingkungan sistem Anda memenuhi spesifikasi berikut:
- Node.js: Versi 18.x LTS atau lebih baru.
- Redis Server: Versi 6.x / 7.x dengan konfigurasi akses lokal atau tercluster.
- React: Versi 18+ dengan dukungan Hooks.
- Package Manager: npm atau pnpm.
Langkah verifikasi awal di terminal sistem Linux/Ubuntu:
# Verifikasi versi Node.js
ode -v
# Verifikasi layanan Redis berjalan
redis-cli ping
# Output yang benar: PONG
Buat struktur direktori proyek baru:
mkdir -p real-time-notification-system/{backend,frontend}
cd real-time-notification-system
5. Panduan Implementasi Langkah demi Langkah
A. Setup Backend Server (Node.js, ws, dan ioredis)
Masuk ke direktori backend, inisialisasi proyek, dan instal dependensi yang diperlukan:
cd backend
npm init -y
npm install express ws ioredis dotenv cors
npm install -D nodemon
Buat file server.js dengan kode lengkap di bawah ini:
require('dotenv').config();
const express = require('http');
const { WebSocketServer } = require('ws');
const Redis = require('ioredis');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const server = express.createServer(app);
const wss = new WebSocketServer({ server });
// Konfigurasi Redis Publisher dan Subscriber
const redisPub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
const redisSub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
const CHANNEL = 'notifications:global';
// Simpan koneksi klien aktif beserta metadata pengguna
const clients = new Map();
// Subscribe ke channel Redis Pub/Sub
redisSub.subscribe(CHANNEL, (err, count) => {
if (err) {
console.error('Gagal subscribe ke Redis channel:', err);
} else {
console.log(`Berhasil subscribe ke ${count} Redis channel. Menyimak pesan...`);
}
});
// Tangkap pesan yang dipublikasikan di Redis
redisSub.on('message', (channel, message) => {
if (channel === CHANNEL) {
const parsedMessage = JSON.parse(message);
// Broadcast ke seluruh klien WebSocket yang terhubung ke instans server ini
clients.forEach((ws, userId) => {
if (ws.readyState === ws.OPEN) {
// Filter opsional: kirim hanya jika ditujukan untuk user ini atau broadcast global
if (!parsedMessage.targetUserId || parsedMessage.targetUserId === userId) {
ws.send(JSON.stringify(parsedMessage.payload));
}
}
});
}
});
// Handle koneksi WebSocket dari klien React
wss.on('connection', (ws, req) => {
// Ekstraksi token / userId dari parameter URL (contoh: ws://localhost:8080?userId=123)
const urlParams = new URLSearchParams(req.url.replace('/?', ''));
const userId = urlParams.get('userId') || 'anonymous_' + Math.random();
clients.set(userId, ws);
console.log(`Klien terhubung: ${userId}. Total klien aktif: ${clients.size}`);
// Kirim pesan selamat datang
ws.send(JSON.stringify({ type: 'WELCOME', message: 'Koneksi WebSocket berhasil.' }));
// Mekanisme Heartbeat: Tanggapi pong dari klien
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
ws.on('close', () => {
clients.delete(userId);
console.log(`Klien terputus: ${userId}. Sisa klien aktif: ${clients.size}`);
});
ws.on('error', (error) => {
console.error(`Error WebSocket pada user ${userId}:`, error);
});
});
// Interval Ping-Pong untuk mendeteksi koneksi mati
const heartbeatInterval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on('close', () => {
clearInterval(heartbeatInterval);
});
// Endpoint HTTP untuk memicu pengiriman notifikasi
app.post('/api/notify', async (req, res) => {
const { targetUserId, title, body } = req.body;
const notificationPayload = {
targetUserId: targetUserId || null,
payload: {
id: Date.now(),
title,
body,
timestamp: new Date().toISOString(),
}
};
try {
// Publikasikan ke Redis Pub/Sub
await redisPub.publish(CHANNEL, JSON.stringify(notificationPayload));
res.status(200).json({ success: true, message: 'Notifikasi berhasil dipublikasikan.' });
} catch (error) {
console.error('Gagal publish notifikasi:', error);
res.status(500).json({ success: false, error: 'Internal Server Error' });
}
});
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Backend server berjalan pada port ${PORT}`);
});
B. Setup Frontend React (Custom Hook & UI Komponen)
Pindah ke direktori frontend, buat aplikasi React menggunakan Vite, dan instal dependensi:
cd ../frontend
npm create vite@latest . -- --template react
npm install
npm install lucide-react
Buat file custom hook untuk manajemen koneksi WebSocket di src/hooks/useWebSocket.js:
import { useState, useEffect, useRef, useCallback } from 'react';
export const useWebSocket = (url, userId) => {
const [isConnected, setIsConnected] = useState(false);
const [notifications, setNotifications] = useState([]);
const socketRef = useRef(null);
const reconnectTimeoutRef = useRef(null);
const reconnectAttemptsRef = useRef(0);
const maxReconnectAttempts = 5;
const connect = useCallback(() => {
if (!url) return;
const wsUrl = `${url}?userId=${userId}`;
console.log(`Menghubungkan ke WebSocket: ${wsUrl}`);
const socket = new WebSocket(wsUrl);
socketRef.current = socket;
socket.onopen = () => {
console.log('WebSocket terhubung.');
setIsConnected(true);
reconnectAttemptsRef.current = 0; // Reset counter saat berhasil terhubung
};
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'WELCOME') {
console.log(data.message);
return;
}
// Tambahkan notifikasi baru ke state
setNotifications((prev) => [data, ...prev]);
} catch (error) {
console.error('Gagal memparsing pesan WebSocket:', error);
}
};
socket.onclose = (event) => {
console.warn('WebSocket terputus:', event.reason);
setIsConnected(false);
// Logika Exponential Backoff untuk Reconnection
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
const timeout = Math.pow(2, reconnectAttemptsRef.current) * 1000;
console.log(`Mencoba menghubungkan kembali dalam ${timeout / 1000} detik...`);
reconnectTimeoutRef.current = setTimeout(() => {
reconnectAttemptsRef.current += 1;
connect();
}, timeout);
} else {
console.error('Maksimum percobaan koneksi ulang tercapai. Gagal terhubung.');
}
};
socket.onerror = (error) => {
console.error('WebSocket Error:', error);
socket.close();
};
}, [url, userId]);
useEffect(() => {
connect();
return () => {
if (socketRef.current) {
socketRef.current.close();
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
};
}, [connect]);
const clearNotifications = () => {
setNotifications([]);
};
return { isConnected, notifications, clearNotifications };
};
Implementasikan komponen utama di src/App.jsx:
import React, { useState } from 'react';
import { useWebSocket } from './hooks/useWebSocket';
import { Bell, Wifi, WifiOff, Trash2, Send } from 'lucide-react';
function App() {
const [userId] = useState(() => 'user_' + Math.floor(Math.random() * 1000));
const { isConnected, notifications, clearNotifications } = useWebSocket('ws://localhost:8080', userId);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [loading, setLoading] = useState(false);
const sendTestNotification = async (e) => {
e.preventDefault();
if (!title || !body) return;
setLoading(true);
try {
await fetch('http://localhost:8080/api/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetUserId: userId, title, body }),
});
setTitle('');
setBody('');
} catch (error) {
console.error('Gagal mengirim notifikasi:', error);
} finally {
setLoading(false);
}
};
return (
<div style={{ maxWidth: '600px', margin: '40px auto', fontFamily: 'Inter, sans-serif', padding: '20px' }}>
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #eaeaea', paddingBottom: '20px' }}>
<div>
<h1 style={{ fontSize: '24px', margin: 0, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Bell /> Real-Time Notifications
</h1>
<p style={{ color: '#666', fontSize: '14px', margin: '5px 0 0 0' }}>User ID: <strong>{userId}</strong></p>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 12px', borderRadius: '20px', background: isConnected ? '#e6f4ea' : '#fce8e6', color: isConnected ? '#137333' : '#c5221f', fontSize: '14px', fontWeight: 600 }}>
{isConnected ? <Wifi size={16} /> : <WifiOff size={16} />}
{isConnected ? 'Terhubung' : 'Terputus'}
</div>
</header>
<section style={{ margin: '20px 0', background: '#f9f9f9', padding: '20px', borderRadius: '8px', border: '1px solid #eee' }}>
<h3 style={{ margin: '0 0 15px 0', fontSize: '16px' }}>Kirim Test Notifikasi</h3>
<form onSubmit={sendTestNotification} style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<input
type="text"
placeholder="Judul Notifikasi"
value={title}
onChange={(e) => setTitle(e.target.value)}
style={{ padding: '10px', borderRadius: '4px', border: '1px solid #ccc' }}
required
/>
<textarea
placeholder="Isi Pesan Notifikasi"
value={body}
onChange={(e) => setBody(e.target.value)}
style={{ padding: '10px', borderRadius: '4px', border: '1px solid #ccc', resize: 'vertical' }}
rows="3"
required
/>
<button
type="submit"
disabled={loading}
style={{ background: '#0070f3', color: '#fff', border: 'none', padding: '10px 15px', borderRadius: '4px', cursor: 'pointer', fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}
>
<Send size={16} /> Kirim Notifikasi
</button>
</form>
</section>
<section>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h3 style={{ margin: 0, fontSize: '16px' }}>Daftar Notifikasi ({notifications.length})</h3>
{notifications.length > 0 && (
<button
onClick={clearNotifications}
style={{ background: 'transparent', border: 'none', color: '#c5221f', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '5px', fontSize: '14px' }}
>
<Trash2 size={16} /> Bersihkan
</button>
)}
</div>
{notifications.length === 0 ? (
<p style={{ textAlign: 'center', color: '#888', padding: '40px 0' }}>Belum ada notifikasi real-time masuk.</p>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{notifications.map((notif) => (
<div key={notif.id} style={{ background: '#fff', border: '1px solid #eaeaea', padding: '15px', borderRadius: '6px', boxShadow: '0 2px 4px rgba(0,0,0,0.02)' }}>
<h4 style={{ margin: '0 0 5px 0', fontSize: '16px', color: '#111' }}>{notif.title}</h4>
<p style={{ margin: '0 0 10px 0', color: '#444', fontSize: '14px' }}>{notif.body}</p>
<span style={{ fontSize: '12px', color: '#888' }}>{new Date(notif.timestamp).toLocaleTimeString()}</span>
</div>
))}
</div>
)}
</section>
</div>
);
}
export default App;
6. Bedah Kode & Penjelasan Detail Baris Kunci
Mari kita bedah beberapa komponen kode paling krusial dalam arsitektur ini:
-
Redis Pub/Sub Dual Client (
redisPub&redisSub): Dalam pustakaioredis, ketika sebuah koneksi klien diatur ke mode subscriber, koneksi tersebut tidak dapat digunakan untuk mengeksekusi perintah lain (sepertipublish,get, atauset). Oleh karena itu, kita mendeklarasikan dua instans terpisah:redisPubuntuk mengirim data danredisSubuntuk mendengarkan saluran. -
Kalkulasi Exponential Backoff pada Klien React:
const timeout = Math.pow(2, reconnectAttemptsRef.current) * 1000;Formula ini melipatgandakan waktu tunggu setiap kali koneksi ulang gagal ($2^0 = 1$ detik, $2^1 = 2$ detik, $2^2 = 4$ detik, dst.). Pendekatan ini krusial untuk mencegah serangan DoS tidak disengaja (Thundering Herd Problem) ke server backend apabila ribuan klien terputus secara bersamaan (misalnya saat jaringan kantor terganggu).
-
WebSocket Heartbeat Mechanism (Ping-Pong):
const heartbeatInterval = setInterval(() => { wss.clients.forEach((ws) => { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; ws.ping(); }); }, 30000);Server secara berkala mengirimkan paket kecil
pingsetiap 30 detik. Jika klien tidak merespons dengan eventpongdalam siklus tersebut, benderaws.isAlivetetap bernilaifalse, dan server akan secara paksa memutuskan koneksi (terminate()) untuk mengosongkan file descriptor dan memori server.
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam lingkungan produksi enterprise dengan traffic tinggi (misalnya 100.000 pengguna aktif bersamaan), arsitektur Node.js tunggal akan kehabisan file descriptor sistem operasi (biasanya dibatasi default 1024 atau 65535).
Langkah-langkah penguatan infrastruktur produksi meliputi:
- Load Balancer (Nginx / HAProxy): Mengonfigurasi Nginx sebagai reverse proxy untuk melakukan proxy pass ke beberapa instans backend Node.js dan mengaktifkan dukungan Upgrade Header:
upstream websocket_backend { server 127.0.0.1:8081; server 127.0.0.1:8082; } server { listen 80; server_name notifications.enterprise.com; location / { proxy_pass http://websocket_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; } } - Tuning Kernel Linux (
/etc/sysctl.conf): Meningkatkan batas file descriptors dan parameter jaringan TCP:fs.file-max = 2097152 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216
8. Common Pitfalls, Anti-Patterns & Solusi
-
Anti-Pattern: Menyimpan State WebSocket di Memori Lokal Tanpa Redis Cluster
- Dampak: Ketika server di-scaling menjadi 2 instans (Server A dan Server B), pengguna yang terhubung ke Server A tidak akan pernah menerima notifikasi yang dipicu melalui Server B.
- Solusi: Selalu gunakan Redis Pub/Sub atau Kafka sebagai message broker eksternal untuk sinkronisasi lintas server.
-
Pitfalls: Memory Leak pada Koleksi
clientsMap di Node.js- Dampak: Klien terputus secara tidak normal (misalnya menutup browser secara paksa tanpa memicu event
close), namun referensi objekwstetap tersimpan di dalamMap, menyebabkan konsumsi memori RAM membengkak secara perlahan (memory leak). - Solusi: Selalu pastikan mekanisme Heartbeat Ping-Pong diaktifkan dan jalankan fungsi
.terminate()untuk membersihkan referensi socket yang mati.
- Dampak: Klien terputus secara tidak normal (misalnya menutup browser secara paksa tanpa memicu event
9. Benchmark, Metrik Kinerja & Tuning Performa
Untuk menguji ketahanan sistem, kita dapat menggunakan tool benchmark beban WebSocket seperti autocannon atau artillery. Jalankan uji beban pada server backend lokal:
npx autocannon -c 500 -d 30 -w 10 ws://localhost:8080
Parameter pengujian:
-c 500: Mensimulasikan 500 koneksi konkuren bersamaan.-d 30: Durasi pengujian selama 30 detik.
Interpretasi Hasil Metrik:
- Pastikan latency rata-rata di bawah 25ms.
- Periksa apakah ada lonjakan penggunaan CPU (CPU spike) yang tidak wajar.
- Pantau penggunaan memori Redis menggunakan perintah
redis-cli info memoryuntuk memastikan tidak ada akumulasi pesan yang gagal terkirim (backpressure).
10. Checklist Penerapan Produksi & Kesimpulan
Sebelum merilis sistem notifikasi real-time ini ke lingkungan Production, pastikan checklist berikut terpenuhi secara ketat:
- Protokol keamanan telah ditingkatkan dari
ws://menjadiwss://menggunakan sertifikat SSL/TLS valid (Let's Encrypt). - Batas file descriptor sistem operasi Linux telah ditingkatkan pada level sistem dan level Nginx.
- Mekanisme Authentication & Authorization telah diintegrasikan pada tahap WebSocket Handshake (memvalidasi JWT token sebelum koneksi diterima).
- Konfigurasi Exponential Backoff telah diuji coba untuk skenario pemadaman jaringan singkat.
- Sistem monitoring (Prometheus + Grafana) dipasang untuk memantau metrik jumlah koneksi aktif, latensi pesan, dan utilisasi Redis Pub/Sub.
Dengan menerapkan arsitektur WebSocket yang dikombinasikan secara presisi bersama Redis Pub/Sub, Anda tidak hanya membangun sistem notifikasi yang cepat dan efisien, tetapi juga sebuah infrastruktur enterprise yang siap diskalakan secara horizontal untuk menghadapi jutaan pengguna aktif tanpa kompromi terhadap stabilitas dan performa.