L
Event-Driven Architecture 🚨 Post-Mortem & Incident Runbook • Literally Web ID

Membangun Real-time Notification Pipeline dengan Event-Driven Architecture

⏱️ 18 menit baca • 📅 20 Oct 2026 • 👁️ 2 views
Membangun Real-time Notification Pipeline dengan Event-Driven Architecture
🔊 Dengarkan Artikel Ini

1. Alarm Tengah Malam: Saat Layanan Produksi Mulai Tumbang

Pukul 02.14 WIB. Ponsel saya bergetar liar dengan nada alarm PagerDuty paling menyebalkan yang pernah ada—nada yang didesain khusus untuk merusak siklus REM tidur siapa pun. Di layar, metrik Grafana menyala merah menyala seperti lampu diskotik darurat. Latency endpoint WebSocket kami melompat dari 45 milidetik menjadi 12.000 milidetik dalam kurun waktu kurang dari tiga menit. Ribuan koneksi klien mulai terputus secara massal, diikuti oleh banjir keluhan dari tim Customer Success di kanal Slack internal.

Sistem kami, sebuah platform notifikasi real-time yang didesain untuk mendistribusikan event jutaan pengguna, mendadak mengalami bottleneck parah. Broker RabbitMQ kami melaporkan memory alarm menyala, menandakan bahwa antrean pesan (queues) meluber hingga menelan habis seluruh alokasi RAM yang tersedia. Di sisi lain, backend Node.js yang menangani koneksi klien WebSocket mulai mengalami heap out of memory karena menumpuk payload yang gagal terkirim ke browser. Malam itu, kami tidak sedang membangun fitur baru; kami sedang berperang melawan entropi infrastruktur skala besar.

Perjalanan investigasi kami malam itu dimulai dari ruang kendali digital, menelusuri jejak digital yang ditinggalkan oleh tumpukan log yang berdarah-darah.

2. Jejak Digital: Menelusuri Anomali Log & Lonjakan Metrik

Begitu mendarat di terminal, hal pertama yang saya lakukan adalah membuka beberapa tab SSH ke klaster server produksi menggunakantmux. Saya langsung memantau status RabbitMQ menggunakan CLI bawaannya untuk melihat apa yang sebenarnya terjadi di balik layar:

# Memeriksa status node RabbitMQ dan penggunaan memori secara real-time
sudo rabbitmqctl status
sudo rabbitmqctl list_queues name messages memory consumers

Angkanya mengerikan. Antrean utama dengan nama notification.fanout memiliki lebih dari 4,2 juta pesan yang belum diproses (unacked), sementara jumlah consumer aktif turun drastis dari 64 instance menjadi hanya 3 instance saja. Sisa instance backend Node.js kami ternyata mengalami crash beruntun akibat kehabisan memori (OOM Killer kernel Linux menyapu bersih proses yang memakan RAM lebih dari 90%).

Kami beralih memeriksa log Nginx yang bertindak sebagai reverse proxy dan SSL termination di depan server WebSocket:

# Konfigurasi upstream Nginx untuk WebSocket dengan timeout yang ketat
upstream websocket_backend {
    server 10.0.1.101:3000 max_fails=3 fail_timeout=10s;
    server 10.0.1.102:3000 max_fails=3 fail_timeout=10s;
}

server {
    listen 443 ssl http2;
    server_name ws.production.io;

    location / {
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;
    }
}

Dari log Nginx, terlihat jelas pola serangan atau lonjakan trafik yang memicu bencana ini: sebuah flash sale dari salah satu merchant besar menyebabkan lonjakan event pendaftaran pesanan sebanyak 50 kali lipat dari rata-rata normal. Masalahnya, payload event tersebut tidak memiliki skema versioning yang jelas, dan struktur datanya membengkak hingga 15 KB per pesan karena menyertakan metadata yang tidak perlu. Klien WebSocket di sisi pengguna akhir, yang sebagian besar berada di jaringan seluler lambat, tidak mampu mengonsumsi data secepat itu. Akibatnya, terjadilah fenomena backpressure fatal yang merembet ke seluruh ekosistem backend.

3. Membongkar Biang Kerok: Akar Masalah yang Sebenarnya

Setelah menggali lebih dalam melalui analisis core dump dan log aplikasi Node.js, kami menemukan tiga dosa besar arsitektural yang kami perbuat sendiri pada fase desain sistem:

  1. Desain Event Payload yang Monolitik: Kami mengirimkan seluruh objek state database ke dalam broker pesan tanpa melakukan projection atau versioning. Ketika struktur tabel berubah, konsumen lama langsung mengalami parsing error, dan ukuran payload membunuh efisiensi jaringan.
  2. Kurangnya Mekanisme Backpressure: Layanan WebSocket kami berasumsi bahwa semua klien memiliki kecepatan internet gigabit dan kemampuan proses yang tak terbatas. Ketika klien lambat menerima data, server terus membuang pesan ke dalam buffer TCP lokal hingga memori aplikasi habis.
  3. Konfigurasi RabbitMQ yang Naif: Kami menggunakan default exchange tanpa menerapkan dead-letter exchange (DLX) yang layak, serta tidak membatasi ukuran antrean (queue length limits), sehingga broker menerima pesan tanpa batas hingga kehabisan RAM.

Untuk memperbaiki ini secara permanen, kita perlu merombak total cara kita mendesain payload, mengelola antrean broker, dan menangani klien yang lambat.

4. Tindakan Darurat: Menghidupkan Kembali Layanan dalam Hitungan Menit

Sambil menyusun rencana jangka panjang, sistem harus segera diselamatkan. Berikut adalah rangkaian perintah darurat one-liner terminal yang kami eksekusi dalam waktu kurang dari 60 detik untuk meredakan tekanan pada klaster:

# 1. Melakukan purge pada antrean yang macet total untuk menyelamatkan broker dari OOM
sudo rabbitmqctl purge_queue notification.fanout

# 2. Melakukan restart paksa pada seluruh klaster Node.js WebSocket melalui PM2
pm2 reload all --update-env

# 3. Menjatuhkan koneksi TCP yang menggantung menggunakan ss dan iptables jika diperlukan
sudo ss -K 'sport = :443' state established

Langkah drastis ini langsung menurunkan penggunaan memori RabbitMQ dari 98% ke level aman 22%. Namun, ini baru pertolongan pertama. Agar tidak tumbang lagi, kami harus membangun fondasi baru yang tahan banting.

5. Kebijakan Retrospektif: Menambal Celah agar Tak Pernah Terulang

Belajar dari tragedi malam itu, kami merombak arsitektur dengan menerapkan standar ketat pada tiga pilar utama: skema payload, konfigurasi broker, dan manajemen backpressure.

Desain Event Payload dan Skema Versioning

Setiap event yang masuk ke dalam sistem kini wajib mengikuti kontrak skema yang jelas dengan menyertakan header versi. Berikut adalah contoh implementasi payload terstruktur dalam format JSON yang bersih dan efisien:

{
  "specversion": "1.0",
  "type": "com.production.notification.order.created",
  "source": "/orders-service",
  "id": "A234-5678-90AB-CDEF",
  "time": "2023-10-25T02:30:00Z",
  "datacontenttype": "application/json",
  "dataversion": "v2",
  "data": {
    "order_id": 987654,
    "user_id": 4321,
    "status": "PAID",
    "total_amount": 150000
  }
}

Dengan format ini, konsumen dapat melakukan validasi menggunakan pustaka seperti Ajv di Node.js atau JSON Schema validator lainnya sebelum memproses pesan lebih lanjut.

Konfigurasi Exchange & Queue di RabbitMQ

Kami membuang konfigurasi default dan beralih menggunakan Topic Exchange yang dikawinkan dengan Dead-Letter Exchange (DLX) serta pembatasan kapasitas antrean (max-length):

// Contoh deklarasi exchange dan queue yang aman di Go (amqp091-go)
package main

import (
	"log"
	"github.com/streadway/amqp"
)

func SetupRabbitMQ(ch *amqp.Channel) error {
	// Deklarasi Dead Letter Exchange untuk menampung pesan gagal
	err := ch.ExchangeDeclare(
		"dlx.exchange", "direct", true, false, false, false, nil,
	)
	if err != nil { return err }

	// Deklarasi Queue Utama dengan argumen pembatas
	args := amqp.Table{
		"x-max-length":     100000,
		"x-overflow":       "reject-publish",
		"x-dead-letter-exchange": "dlx.exchange",
		"x-dead-letter-routing-key": "dead.notifications",
	}

	_, err = ch.QueueDeclare(
		"secure.notification.queue", true, false, false, false, args,
	)
	return err
}

Menangani Backpressure pada WebSocket Client yang Lambat

Di sisi backend Node.js, kami menerapkan manajemen buffer berbasis ukuran (highWaterMark) dan mekanisme heartbeat yang agresif untuk mendeteksi klien yang mandeg (zombie clients):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws) => {
    ws.isAlive = true;
    ws.on('pong', () => { ws.isAlive = true; });

    ws.sendEvent = (payload) => {
        // Cek apakah buffer socket hampir penuh (mencegah memory leak)
        if (ws.bufferedAmount > 65536) { // 64KB threshold
            console.warn('Backpressure terdeteksi, menutup koneksi klien lambat...');
            ws.terminate();
            return;
        }
        ws.send(JSON.stringify(payload));
    };
});

// Interval pengecekan kesehatan koneksi
const interval = setInterval(() => {
    wss.clients.forEach((ws) => {
        if (ws.isAlive === false) return ws.terminate();
        ws.isAlive = false;
        ws.ping();
    });
}, 30000);

6. Pasang Alarm: Otomasi Deteksi Dini untuk Tim On-Call

Agar insiden serupa tidak lagi menyelinap diam-diam di tengah malam, kami memasang aturan peringatan (alerting rules) baru di Prometheus dan Alertmanager yang terintegrasi langsung dengan webhook PagerDuty:

groups:
  - name: notification_pipeline_alerts
    rules:
      - alert: RabbitMQQueueHighMessages
        expr: rabbitmq_queue_messages{queue="secure.notification.queue"} > 50000
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Antrean RabbitMQ menumpuk tinggi!"
          description: "Jumlah pesan dalam antrean melewati batas aman 50.000 selama lebih dari 2 menit."

      - alert: WebSocketHighBufferedAmount
        expr: nodejs_websocket_buffered_amount_bytes > 1048576
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Potensi Backpressure pada WebSocket Server"
          description: "Buffer pengiriman data WebSocket melebihi 1MB per instance."

Dengan sistem pemantauan proaktif ini, kami kini bisa tidur dengan lebih nyenyak, mengetahui bahwa infrastruktur event-driven kami tidak hanya cepat, tetapi juga tangguh menghadapi badai trafik apa pun.

🏷️ Tags: #Websocket #Go #Architecture #Node.js #Rabbitmq

Apakah panduan tutorial ini membantu Anda?

Masukan Anda sangat berharga untuk terus memperbarui dan menyempurnakan kualitas materi teknis kami.

🎉

Terima kasih banyak atas apresiasi Anda!

Tim kami terus menyempurnakan tutorial agar selalu akurat dengan versi teknologi terbaru.

Bantu Kami Menyempurnakan Materi Ini

⚡ Literally Tech Digest

Update Tutorial & Developer Tools Terbaru Langsung ke Email Anda

Dapatkan kurasi artikel arsitektur software, tips DevOps, panduan database, serta rilis fitur developer utility gratis setiap minggu. Bebas spam, bisa berhenti kapan saja.

⏳ Mencari artikel...
Tutorial & Artikel Terkait
Developer Tools
Tidak ditemukan tutorial atau tool dengan kata kunci "".
Ketik minimal 2 karakter untuk mencari artikel atau nama tools.
Tekan ESC untuk menutup Cari di Semua Artikel →