# Membangun Real-Time Event-Driven Architecture dengan NATS dan WebSockets

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

---

## 1. Alarm Tengah Malam: Saat Layanan Produksi Mulai Tumbang

Pukul 02.14 WIB. PagerDuty saya menjerit melengking, merobek keheningan malam kamar saya. Belum sempat nyawa terkumpul sepenuhnya, layar ponsel sudah dibanjiri notifikasi dari Slack: metrik *error rate* WebSocket melonjak drastis melewati batas 35%, dan pemakaian memori pada node cluster Go backend langsung menyentuh titik jenuh di angka 98%. Layanan pelacakan logistik real-time kami mendadak lumpuh total. Puluhan ribu sopir dan ribuan dashboard operasional terputus koneksinya dalam waktu bersamaan. Klien mulai marah, dan tim *on-call* langsung masuk ke mode siaga penuh. Kita sedang menghadapi skenario terburuk bagi seorang Systems Architect: sistem *real-time* yang kita bangun dengan susah payah mengalami *cascade failure* yang amat masif. Dan untuk menemukan siapa dalang di balik kekacauan ini, kita harus menyelam langsung ke dalam tumpukan log sistem yang mulai memanas.

## 2. Jejak Digital: Menelusuri Anomali Log & Lonjakan Metrik

Begitu membuka terminal dan masuk ke server bastion, saya langsung mengarahkan pandangan ke dasbor Grafana dan Prometheus. Pemandangannya sungguh mengerikan: kurva koneksi WebSocket yang tadinya stabil di angka 80.000 klien aktif, tiba-tiba terjun bebas, sementara metrik penggunaan *heap memory* di aplikasi Go melesat vertikal bagai roket tanpa rem. Saya segera membuka terminal dan memanggil log Nginx yang bertindak sebagai *reverse proxy* di lini depan:

```bash
# Memeriksa log error Nginx secara real-time untuk mendeteksi lonjakan koneksi terputus
sudo tail -f /var/log/nginx/error.log | grep -E "upstream timed out|connection reset by peer"
```

Log berhamburan memenuhi layar dengan ribuan pesan `epoll_wait() failed: 24 (Too many open files)` dan `upstream timed out (110: Connection timed out)`. Sial, masalahnya berlapis. Bukan hanya aplikasi backend kami yang kehabisan memori akibat kebocoran penanganan *routine* WebSocket, tetapi sistem operasi Linux sendiri sudah menyerah karena batas maksimum *file descriptor* (`ulimit -n`) terlampaui. Klien-klien browser yang putus koneksi terus-menerus melakukan *reconnect storm*, menghantam load balancer tanpa henti. Untuk memahami mengapa sistem kami begitu rapuh menghadapi hantaman ini, kita harus membongkar arsitektur pesan di balik layar yang ternyata salah memilih strategi sejak hari pertama perancangan.

## 3. Membongkar Biang Kerok: Akar Masalah yang Sebenarnya

Mari kita jujur sejenak. Dulu, sewaktu merancang arsitektur ini, tim sempat berdebat sengit antara menggunakan RabbitMQ atau NATS sebagai tulang punggung *message broker*. Karena sudah terbiasa dengan ekosistem AMQP yang kaya fitur, sebagian engineer mendesak untuk memakai RabbitMQ. Padahal, untuk kebutuhan *streaming* jutaan pesan IoT dan update posisi kurir secara instan, RabbitMQ terbukti terlalu berat karena overhead protokol perutean, manajemen *exchange* yang kompleks, serta pemborosan memori saat antrean pesan menumpuk.

> [!WARNING]
> Jangan pernah menggunakan message broker berbasis AMQP tradisional jika kasus penggunaan utama Anda adalah *high-throughput, low-latency pub/sub* untuk puluhan ribu klien WebSocket. Overhead protokol akan menghabiskan memori Anda dalam hitungan menit.

Di sinilah NATS mengungguli RabbitMQ. NATS dirancang dengan filosofi *at-most-once delivery* yang sangat ringan, berbasis teks murni yang dioptimalkan di memori, tanpa mekanisme *persistence* berat yang mencekik performa CPU. Namun, masalah inti bukan sekadar di NATS-nya. Biang kerok sebenarnya adalah ketiadaan mekanisme *backpressure* pada server WebSocket Go kami. Ketika klien seluler mengalami gangguan sinyal, server terus-menerus mencoba mengirimkan antrean pesan ke socket mereka yang lambat. Buffer internal membengkak, goroutine menumpuk (*goroutine leak*), dan terjadilah *Out-Of-Memory (OOM) Killer* yang membantai proses utama.

## 4. Tindakan Darurat: Menghidupkan Kembali Layanan dalam Hitungan Menit

Kita tidak punya waktu untuk berteori panjang lebar saat produksi sedang down. Tindakan penyelamatan darurat harus dilakukan detik itu juga agar layanan bisa bernapas kembali. Langkah pertama adalah menaikkan batas *file descriptor* secara instan pada sistem operasi Linux di semua node cluster, lalu memangkas koneksi *ghost* yang menggantung di Nginx.

```bash
# Menambah batas open files secara temporer pada sistem operasi untuk mencegah error 24
sudo sysctl -w fs.file-max=2097152
ulimit -n 65535

# Melakukan reload Nginx dengan konfigurasi pembatasan rate limit baru
sudo nginx -t && sudo systemctl reload nginx
```

Selanjutnya, kita harus menyuntikkan patch darurat ke dalam kode Go backend untuk mengaktifkan antrean pesan yang memiliki batas kapasitas (*bounded channel*) serta menerapkan *write timeout* yang agresif pada setiap koneksi WebSocket. Jika klien terlalu lambat membaca pesan, putuskan koneksinya secara paksa daripada mengorbankan seluruh server.

```go
// Potongan kode darurat untuk menangani backpressure pada WebSocket writer
package main

import (
	"net/http"
	"time"
	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func handleConnection(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	defer conn.Close()

	// Batasi ukuran channel buffer agar tidak terjadi memory leak akibat backpressure
	sendQueue := make(chan []byte, 256)
	go func() {
		for msg := range sendQueue {
			_ = conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
			if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
				return // Putus koneksi jika klien lambat (slow consumer)
			}
		}
	}()
}
```

Hanya dalam waktu 12 menit setelah eksekusi skrip darurat dan pengguliran ulang (*rolling restart*) kontainer backend, grafik error rate perlahan merata kembali ke angka 0.01%. Namun, memadamkan api saja tidak cukup; kita harus memastikan percikan api serupa tidak pernah membakar rumah kita lagi di masa depan.

## 5. Kebijakan Retrospektif: Menambal Celah agar Tak Pernah Terulang

Setelah evaluasi bersama tim, kami merombak total pendekatan arsitektur *real-time* kami. Kami resmi membuang sisa-sisa antrean lama dan mengintegrasikan NATS Server klaster penuh yang dikombinasikan dengan NATS JetStream untuk ketahanan data pesan penting. Kami juga menyempurnakan konfigurasi Nginx di bagian *upstream keepalive* dan *proxy timeout* agar mampu menahan gelombang *reconnect storm* dari puluhan ribu klien.

Berikut adalah contoh konfigurasi Nginx yang telah disempurnakan untuk menangani proksi WebSocket berskala masif:

```nginx
# Konfigurasi Nginx untuk Proxy WebSocket Berkinerja Tinggi
events {
    worker_connections 65535;
    use epoll;
    multi_accept on;
}

http {
    upstream websocket_backend {
        server 10.0.1.10:8080 max_fails=3 fail_timeout=10s;
        server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
        keepalive 1024;
    }

    server {
        listen 80;
        server_name api.namadomain.com;

        location /ws {
            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_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            # Timeout agresif untuk mencegah slow client memonopoli resource
            proxy_read_timeout 60s;
            proxy_send_timeout 60s;
            proxy_buffering off;
        }
    }
}
```

> [!TIP]
> Selalu aktifkan `proxy_buffering off;` pada blok lokasi WebSocket di Nginx. Buffering aktif akan merusak sifat asinkron dan instan dari protokol WebSocket itu sendiri.

Dengan fondasi infrastruktur yang kokoh ini, mari kita pastikan tim tidak pernah lagi kecolongan di tengah malam dengan memasang sistem deteksi dini yang presisi.

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

Pengalaman pahit malam itu mengajarkan kita bahwa memantau CPU dan RAM saja tidak cukup untuk sistem *real-time*. Kita membutuhkan metrik spesifik yang mendeteksi gejala awal sebelum bencana benar-benar terjadi. Kami sekarang mengonfigurasi aturan Prometheus Alertmanager baru yang memantau panjang antrean *buffer* dan jumlah koneksi aktif secara real-time.

```yaml
# Konfigurasi Prometheus Alert untuk Deteksi Dini Anomali WebSocket & NATS
groups:
- name: real_time_infrastructure_alerts
  rules:
  - alert: WebSocketBackpressureHigh
    expr: rate(websocket_send_dropped_total[1m]) > 50
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Peringatan Backpressure WebSocket Tinggi di {{ $labels.instance }}"
      description: "Klien lambat memicu pembuangan pesan melebihi ambang batas. Risiko memori meluber!"

  - alert: NATSConnectionSpike
    expr: nats_connections_total > 75000
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Lonjakan Koneksi NATS Mendekati Kapasitas Maksimal"
      description: "Jumlah klien aktif mencapai {{ $value }}. Siapkan ekspansi node klaster NATS."
```

Sistem peringatan dini ini kini terhubung langsung ke sistem siaga kami. Dengan persiapan matang, arsitektur NATS yang tangguh, serta pengelolaan *backpressure* yang disiplin, tidur malam saya dan tim *on-call* kini jauh lebih nyenyak, siap menyambut tantangan skala besar berikutnya tanpa drama.
