Taryana
Kembali ke Semua Artikel Teknis/Arsitektur Real-Time Notification Skala Besar dengan NestJS, Redis Pub/Sub, dan WebSocket
NestJSRedisWebSocketTypeScriptArchitecture

Arsitektur Real-Time Notification Skala Besar dengan NestJS, Redis Pub/Sub, dan WebSocket

Belajar dari pengalaman nyata menangani insiden polling spike dan kegagalan WebSocket di flash sale: bagaimana arsitektur decoupled push dengan NestJS dan Redis Pub/Sub mengeliminasi 98% beban database.

1. Mengapa Fitur Notifikasi Real-Time Sering Jadi Titik Runtuh Terbesar?

Pernahkah kamu mengalami momen menegangkan di mana fitur notifikasi pesanan atau pesan obrolan yang awalnya tampak sepele tiba-tiba melumpuhkan seluruh sistem backend tepat saat kampanye besar diluncurkan?

Pengalaman pahit itulah yang pernah saya dan tim hadapi ketika pertama kali merilis fitur pelacakan pesanan langsung (live order tracker). Di awal implementasi, semuanya tampak berjalan normal di lingkungan pengujian kantor. Namun, begitu masuk ke lingkungan produksi dengan puluhan ribu pengguna aktif, pendekatan naif yang kami gunakan langsung memicu serangkaian bencana teknis:

  • Database Exhaustion: Klien web dan mobile disetel untuk melakukan status pesanan setiap 2 detik. Begitu ada 50.000 pengguna aktif, server dihantam 25.000 request per detik hanya untuk memeriksa status yang 99% belum berubah! Database CPU langsung menyentuh 98% dan kueri penting lainnya .
  • Bencana "Client ": Untuk mengurangi , kami sempat mencoba cara pintas: frontend menembak REST API, lalu frontend memancarkan socket emit secara manual. Di jaringan seluler yang tidak stabil, panggilan API berhasil tetapi socket emit gagal. Pengguna melihat uangnya terpotong tetapi tidak ada notifikasi yang muncul, memicu kepanikan massal di customer service.
  • Isolasi Memori di Kubernetes: Ketika WebSocket di-scale menjadi 10 instance, pengguna yang terhubung di A tidak pernah menerima notifikasi jika event transaksi diproses oleh B!

Dari insiden tersebut, kami menarik satu prinsip arsitektural penting: jangan pernah membebankan koordinasi real-time kepada perangkat klien atau mengandalkan memori lokal server tunggal.

Executive Summary & Core Insight

Blueprint Solusi: Arsitektur Decoupled Push Terdistribusi Untuk membasmi kosong dan menjamin pengiriman notifikasi instan di seluruh kluster, kami membangun alur pertahanan 3 pilar:

  1. 1Single-Hit Ingestion: Klien hanya perlu melakukan satu panggilan HTTP REST biasa (POST /api/v1/orders). Klien tidak pernah perlu memancarkan event socket secara manual.
  2. 2Redis Event Backbone: Begitu transaksi divalidasi dan disimpan di database, backend mempublikasikan payload event ke Redis channel dalam hitungan sub-milidetik (<1ms).
  3. 3 WebSocket Gateway: Seluruh WebSocket yang terdistribusi secara horizontal mendengarkan event dari Redis dan langsung mendorong (push) notifikasi ke koneksi TCP aktif pengguna tanpa menyentuh database utama.
Network Flow: Real-Time Notification & Redis Pub/SubNestJS + Redis + WebSocket
Pinch touchpad / scroll untuk zoom • Klik-drag untuk geser (kiri, kanan, atas, bawah)
Sender ClientSingle HTTP HitPOST /api/v1/ordersNestJS REST APIController & ServiceStateless Ingestion200 OK ReturnedPUBLISHREDIS BUSchannel: live_notifFan-out < 1ms2 Subscriber PodsWS Pod 1 (NestJS)SUBSCRIBE Activeserver.emit()WS Pod 2 (NestJS)SUBSCRIBE Activeserver.emit()Web ClientMobile App
Client Ingestion:1x REST API Hit
Message Bus:Redis Pub/Sub
WebSocket Cluster:Multi-Pod NestJS
Delivery Latency:< 5 ms Push

Klien hanya melakukan satu kali request HTTP ke REST API NestJS. Event di-publish ke Redis Pub/Sub, lalu di-broadcast serentak oleh seluruh pod WebSocket Gateway ke klien aktif.


2. Peta Topologi dan Matriks Komparasi Pendekatan Notifikasi

Sebelum membedah implementasi kode, mari kita lihat bagaimana perbandingan teknis antara pendekatan naif dan arsitektur decoupled push:

Dimensi Rekayasa (HTTP GET Interval)Client (REST + Emit Frontend)Decoupled Push (NestJS + Redis )
Beban Kueri DatabaseSangat Ekstrem (Jutaan kueri kosong/menit)Sedang (Hanya saat mutasi pesanan)Minimal (Hanya transaksi mutasi riil)
Latensi Penerimaan NotifikasiBuruk (Bergantung jeda interval 2-5 detik)Cepat tetapi rentan gagal di jalanInstan Sub-Milidetik (4-8ms via push TCP)
Konsistensi Data & Rentan data tertinggal (stale state)Sangat Tinggi (Socket putus, notif hilang)100% Konsisten (Database selesai baru push)
Skalabilitas Mudah tetapi membebani databaseGagal total antar- socket terpisahSempurna (Terhubung via Redis bus backbone)
Risiko Spoofing / KeamananRendahTinggi (Klien bisa memalsukan emit payload)Nol (Event diproduksi eksklusif oleh server)

3. Tiga Masalah Nyata di Balik Kerusakan Sistem Notifikasi Tradisional

Masalah 1: Jebakan Short Polling yang Menguras Habis Connection Pool

Ketika aplikasi menjalankan interval 2 detik untuk memeriksa notifikasi:

  • 50.000 pengguna aktif menghasilkan 25.000 request per detik () kueri kosong ke database.
  • Database PostgreSQL kehabisan alokasi koneksi (), latensi kueri melonjak dari 15ms menjadi 12.000ms, dan pengguna yang ingin bertransaksi malah mendapatkan error 500.

Solusi 1: Mengubah Pola Pull Menjadi Event-Driven Push

Dengan beralih ke WebSocket yang dihubungkan ke Redis, beban 25.000 kueri kosong langsung turun menjadi 0 . Koneksi TCP terbuka satu kali dan server hanya mengirim data jika ada pembaruan nyata.


Masalah 2: Celah Anti-Pattern Client Dual-Hit

Ketika pengembang mencoba cara cepat:

TypeScript CRDT Reference3 baris kode
> // ANTI-PATTERN: Menyerahkan koordinasi real-time ke perangkat klien! | await axios.post('/api/v1/orders', payload); ...Klik untuk melihat implementasi lengkap →

Jika sinyal ponsel pengguna terganggu tepat di baris kedua, pembayaran berhasil tetapi notifikasi hilang selamanya. Selain itu, peretas dapat memodifikasi skrip klien untuk memalsukan event notifikasi palsu ke ruang socket pengguna lain.

Solusi 2: Single-Hit Stateless REST Ingestion

Klien cukup menembak endpoint HTTP biasa. Logika backend memvalidasi hak akses, menyelesaikan transaksi basis data secara , dan server sendirilah yang memancarkan event ke sistem notifikasi secara terpercaya.


Masalah 3: Isolasi Memori Koneksi TCP di Lingkungan Multi-Pod

Ketika aplikasi berjalan di atas Kubernetes:

  • Pengguna A terhubung ke WebSocket di 1.
  • Pengguna B terhubung ke WebSocket di 2.
  • Pengguna A melakukan checkout yang diproses oleh 3. Jika 3 mencoba menyiarkan pesan hanya lewat RAM internalnya sendiri, Pengguna B di 2 tidak akan pernah menerima pesan tersebut karena memori koneksi TCP mereka terisolasi di yang berbeda.

Solusi 3: Redis Pub/Sub sebagai Distributed Message Bus

Redis bertindak sebagai jembatan penghubung antar-. 3 cukup mempublikasikan satu pesan ke Redis, dan seluruh WebSocket ( 1, 2, N) akan serentak menerima pesan tersebut dan meneruskannya ke pengguna yang relevan.


4. Insiden Nyata Flash Sale: Saat 120.000 Pengguna Membakar Database

Pada kuartal lalu, tim kami meluncurkan kampanye diskon besar-besaran. Di 5 menit pertama, 120.000 pengguna membanjiri aplikasi secara bersamaan.

Karena arsitektur lama masih mengandalkan kombinasi dan socket lokal:

  1. 1Jaringan seluler yang padat menyebabkan 14% request socket klien mengalami timeout. Ribuan pengguna mengira transaksi mereka gagal padahal saldo mereka telah terpotong.
  2. 2Kepanikan pengguna membuat mereka melakukan refresh halaman berulang kali, menyulut badai 45.000 kueri yang melumpuhkan PostgreSQL.
  3. 3Server API dan gateway socket saling berebut CPU hingga sistem mengalami crash total selama 25 menit.

Insiden tersebut menjadi titik balik kami untuk merombak total sistem menjadi arsitektur decoupled push berbasis NestJS dan Redis yang dijelaskan di bawah ini.


5. Implementasi Kode NestJS Skala Produksi (TypeScript)

Berikut adalah struktur kode produksi yang modular, type-safe, dan siap di-scale ke puluhan tanpa kendala:

1. Dedicated Redis Pub/Sub Service

[!TIP]

Aturan Wajib ioredis:

Begitu sebuah koneksi mengeksekusi perintah SUBSCRIBE, koneksi tersebut terkunci dalam mode listening dan menolak perintah tulis seperti PUBLISH atau SET. Oleh karena itu, kita wajib memisahkan publisherClient dan subscriberClient menjadi dua instance mandiri.

TypeScript CRDT Reference52 baris kode
> // src/modules/redis/redis-pubsub.service.ts | import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from "@nestjs/common"; ...Klik untuk melihat implementasi lengkap →

2. Order Controller & Service (Stateless REST Ingestion)

Klien hanya perlu memanggil endpoint ini satu kali tanpa perlu repot mengurus emit socket di frontend:

TypeScript CRDT Reference21 baris kode
> // src/modules/order/order.controller.ts | import { Controller, Post, Body, HttpCode, HttpStatus } from "@nestjs/common"; ...Klik untuk melihat implementasi lengkap →
TypeScript CRDT Reference42 baris kode
> // src/modules/order/order.service.ts | import { Injectable, Logger } from "@nestjs/common"; ...Klik untuk melihat implementasi lengkap →

3. NestJS WebSocket Gateway (Distributed Pod Broadcaster)

TypeScript CRDT Reference51 baris kode
> // src/modules/notification/notification.gateway.ts | import { ...Klik untuk melihat implementasi lengkap →

6. Hasil Riil di Produksi: Metrik Sebelum vs Sesudah

Setelah menerapkan arsitektur Decoupled Push di lingkungan produksi, berikut adalah komparasi metrik performa sebelum dan sesudah migrasi:

Metrik ArsitekturSebelum ( & )Sesudah (NestJS + Redis + WS)Dampak Produksi Nyata
Latensi Penerimaan Notifikasi2.000 ms (menunggu giliran poll)4,1 ms (sub-frame WebSocket push)487x Lebih Cepat (Pengguna merasa instan)
Beban Kueri Database per Menit1.500.000 kueri/menit24.000 kueri/menitPengurangan Beban 98,4% (Database stabil)
Throughput HTTP Ingestion1.200 (koneksi tercekik )18.500+ Kapasitas melesat 15x Lipat
Tingkat Notifikasi Hilang (Loss)4,8% (akibat sinyal putus & )0,00% (Transmisi backend terjamin)Nol inkonsistensi data
Konsumsi CPU API85% - 94% (Status siaga merah)16% - 22%Penghematan biaya server hingga 75%

7. 4 Pembelajaran Penting dari Medan Produksi

  1. 1Prinsip Single-Hit Klien: Jangan pernah membebankan koordinasi notifikasi ke perangkat pengguna. Cukup minta klien melakukan satu request mutasi HTTP standar, dan serahkan tugas penyebaran event kepada backend.
  2. 2Pisahkan API dari WebSocket: Pisahkan REST API dari WebSocket Gateway. REST API dapat di-restart atau di-deploy kapan saja tanpa memutus puluhan ribu koneksi socket yang sedang aktif menempel di gateway.
  3. 3Wajib Memisahkan Client ioredis Publisher dan Subscriber: Mode listening pada protokol Redis mengunci koneksi dari perintah tulis reguler. Selalu buat dua instance client terpisah.
  4. 4Strategi Transient vs Persistent Storage: Manfaatkan Redis untuk penyebaran instan yang hemat RAM, tetapi tetap simpan riwayat notifikasi ke database utama agar pengguna yang sedang offline tidak kehilangan catatan penting saat membuka aplikasi kembali.
Real-Time Notification Simulator
Memuat lab interaktif...

Ingin bereksplorasi lebih dalam?

Buka seluruh koleksi simulator sistem terdistribusi di Central Sandbox Playground.

Buka Central Sandbox →