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 Connection Poolℹ Exhaustion: Klien web dan mobile disetel untuk melakukan pollingℹ 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 macet totalℹ.
- Bencana Race Conditionℹ "Client Dual-Hitℹ": Untuk mengurangi pollingℹ, 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 Multi-Podℹ Kubernetes: Ketika podℹ WebSocket di-scale menjadi 10 instance, pengguna yang terhubung di Podℹ A tidak pernah menerima notifikasi jika event transaksi diproses oleh Podℹ 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.
Blueprint Solusi: Arsitektur Decoupled Push Terdistribusi Untuk membasmi pollingℹ kosong dan menjamin pengiriman notifikasi instan di seluruh podℹ kluster, kami membangun alur pertahanan 3 pilar:
- 1Single-Hit Statelessℹ Ingestion: Klien hanya perlu melakukan satu panggilan HTTP REST biasa (
POST /api/v1/orders). Klien tidak pernah perlu memancarkan event socket secara manual. - 2Redis Pub/Subℹ Event Backbone: Begitu transaksi divalidasi dan disimpan di database, backend mempublikasikan payload event ke Redis channel dalam hitungan sub-milidetik (<1ms).
- 3Multi-Podℹ WebSocket Gateway: Seluruh podℹ WebSocket yang terdistribusi secara horizontal mendengarkan event dari Redis dan langsung mendorong (push) notifikasi ke koneksi TCP aktif pengguna tanpa menyentuh database utama.
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:
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 (RPSℹ) kueri kosong ke database.
- Database PostgreSQL kehabisan alokasi koneksi (connection starvationℹ), 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 RPSℹ kueri pollingℹ kosong langsung turun menjadi 0 RPSℹ. 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:
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 atomikℹ, 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 Podℹ 1.
- Pengguna B terhubung ke WebSocket di Podℹ 2.
- Pengguna A melakukan checkout yang diproses oleh Podℹ 3. Jika Podℹ 3 mencoba menyiarkan pesan hanya lewat RAM internalnya sendiri, Pengguna B di Podℹ 2 tidak akan pernah menerima pesan tersebut karena memori koneksi TCP mereka terisolasi di podℹ yang berbeda.
Solusi 3: Redis Pub/Sub sebagai Distributed Message Bus
Redis bertindak sebagai jembatan penghubung antar-podℹ. Podℹ 3 cukup mempublikasikan satu pesan ke Redis, dan seluruh podℹ WebSocket (Podℹ 1, Podℹ 2, Podℹ 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 pollingℹ dan socket lokal:
- 1Jaringan seluler yang padat menyebabkan 14% request socket klien mengalami timeout. Ribuan pengguna mengira transaksi mereka gagal padahal saldo mereka telah terpotong.
- 2Kepanikan pengguna membuat mereka melakukan refresh halaman berulang kali, menyulut badai 45.000 RPSℹ kueri pollingℹ yang melumpuhkan PostgreSQL.
- 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 Pub/Subℹ 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 podℹ 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 sepertiPUBLISHatauSET. Oleh karena itu, kita wajib memisahkanpublisherClientdansubscriberClientmenjadi dua instance mandiri.
2. Order Controller & Service (Stateless REST Ingestion)
Klien hanya perlu memanggil endpoint ini satu kali tanpa perlu repot mengurus emit socket di frontend:
3. NestJS WebSocket Gateway (Distributed Pod Broadcaster)
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:
7. 4 Pembelajaran Penting dari Medan Produksi
- 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.
- 2Pisahkan Statelessℹ API dari Statefulℹ WebSocket: Pisahkan podℹ REST API dari podℹ WebSocket Gateway. REST API dapat di-restart atau di-deploy kapan saja tanpa memutus puluhan ribu koneksi socket yang sedang aktif menempel di gateway.
- 3Wajib Memisahkan Client ioredis Publisher dan Subscriber: Mode listening pada protokol Redis mengunci koneksi dari perintah tulis reguler. Selalu buat dua instance client terpisah.
- 4Strategi Transient vs Persistent Storage: Manfaatkan Redis Pub/Subℹ 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.