# Membangun Event-Driven Architecture Skala Tinggi dengan Apache Kafka & Go

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/membangun-event-driven-architecture-skala-tinggi-dengan-apache-kafka-go

---

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

Pukul 02:14 WIB. Ponsel saya bergetar liar di atas nakas, memecah keheningan malam dengan alarm PagerDuty bernada tinggi yang sukses membuat adrenalin melonjak seketika. Di layar terpampang metrik merah menyala: latensi *endpoint* utama checkout melonjak dari rata-rata 45 milidetik menjadi lebih dari 8.500 milidetik, diikuti oleh grafik *error rate* HTTP 504 Gateway Timeout yang meroket tajam. Sebagai Principal Systems Architect yang baru saja memejamkan mata setelah migrasi infrastruktur *event-driven* berbasis Apache Kafka dan Go, saya tahu persis mimpi buruk apa yang sedang terjadi di *cluster* produksi.

Pelanggan kami di seluruh penjuru negeri sedang mencoba menyelesaikan transaksi belanja mereka, namun sistem backend web yang berada di balik Nginx *reverse proxy* dan dibantu oleh *worker* berbasis Go tiba-tiba mengalami *choke*. Antrean pesan di dalam Kafka *topic* utama tiba-tiba membengkak hingga puluhan juta *unconsumed messages*, sementara layanan *cache* Redis kami di port 6379 mulai kehabisan memori akibat tingginya *retry queue*. Tanpa membuang waktu, saya membuka laptop, menyalakan VPN, dan langsung menyelam ke dalam pusat kendali infrastruktur untuk melacak jejak digital dari bencana digital ini.

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

Langkah pertama seorang penyelidik infrastruktur bukanlah langsung merestart layanan, melainkan membaca bukti-bukti autentik yang ditinggalkan oleh sistem. Saya membuka terminal dan langsung melompat ke server penampung log menggunakan SSH, lalu memantau aliran akses real-time dari Nginx untuk melihat bagaimana pola *traffic* yang masuk.

```bash
# Memantau log akses Nginx untuk melihat distribusi HTTP status code secara real-time
tail -f /var/log/nginx/access.log | grep -E "(502|504|500)"
```

Baris demi baris log menampilkan deretan kode kesalahan 504 yang masif. Saya kemudian beralih memeriksa metrik kesehatan kontainer Docker dan kluster Kubernetes menggunakan Prometheus dan Grafana. Di sana terlihat jelas bahwa *Consumer Group* Go kami yang bertugas menarik data dari Kafka mengalami fenomena mengerikan bernama *Rebalance Storm*. Setiap beberapa detik, *consumer* terputus dari *cluster* Kafka, memicu proses pemilihan ulang *partition assignment*, yang membuat pemrosesan pesan terhenti total.

```bash
# Memeriksa status Consumer Group Kafka secara langsung via CLI container
docker exec -it kafka-broker-0 kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --describe --group order-processing-service-group
```

Output terminal menunjukkan angka *LAG* yang terus meroket naik ribuan angka setiap detiknya. Konsumen Go kami tampak kehabisan napas. Memori RAM pada pod *worker* Go melonjak drastis hingga menyentuh batas *hard limit* OOM (Out Of Memory) Killer, memaksa sistem operasi Linux untuk membunuh proses secara paksa. Pertanyaannya sekarang: mengapa aplikasi Go yang terkenal hemat sumber daya bisa mengalami kebocoran memori parah saat menghantam jutaan *event* per detik? Jawabannya tersembunyi jauh di dalam logika *partitioning* Kafka dan cara pustaka *consumer* Go kita mengelola memori.

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

Setelah membongkar kode sumber aplikasi Go dan melakukan analisis *heap profile* menggunakan alat bantu `pprof`, akar masalahnya mulai tersingkap satu per satu layaknya potongan teka-teki yang jatuh pada tempatnya. 

Pertama, strategi *partitioning* awal di Kafka kami menggunakan *key hashing* yang buruk. Sebagian besar kunci event (order_id) menghasilkan distribusi yang tidak merata (*hot partitioning*), di mana Partisi 3 menampung 70% dari total *traffic* global, sementara 15 partisi lainnya santai tanpa beban. Akibatnya, satu *consumer worker* di dalam *group* kewalahan menampung beban sementara yang lain menganggur.

Kedua, di sisi aplikasi Go, tim kami menggunakan pustaka `segmentio/kafka-go` atau `confluent-kafka-go` dengan konfigurasi *prefetch buffer* yang terlalu besar tanpa batasan *channel capacity*. Saat lonjakan transaksi terjadi, *worker* menarik puluhan ribu pesan mentah ke dalam memori RAM secara serentak sebelum pesan-pesan tersebut sempat divalidasi dan dimasukkan ke dalam basis data MySQL. Struktur data struct di Go mengalami alokasi memori masif (*allocations on heap*), memicu Garbage Collector (GC) bekerja ekstra keras hingga menyebabkan *Stop-The-World pause* yang panjang. Kafka mengira *consumer* tersebut mati karena tidak mengirimkan *heartbeat* tepat waktu, sehingga memicu *Rebalance Storm* yang kita saksikan tadi.

Ketiga, tidak adanya mekanisme *Dead Letter Queue* (DLQ) yang proper membuat pesan korup atau pesan yang gagal diproses terus-menerus masuk ke dalam siklus *infinite retry*, menyumbat pipa utama aplikasi secara permanen.

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

Situasi sudah kritis dan pengguna tidak bisa menunggu teori arsitektur yang panjang. Kita butuh tindakan darurat dalam hitungan detik untuk meredakan tekanan pada infrastruktur. Berikut adalah skrip dan langkah mitigasi cepat yang saya eksekusi langsung di terminal produksi untuk menyelamatkan sistem:

```bash
# 1. Melakukan scale-out instance worker Go secara darurat untuk memecah beban beban instan
kubectl scale deployment order-worker-service --replicas=25 -n production

# 2. Melakukan reset offset pada consumer group yang nyangkut (jika diperlukan untuk skip toxic message sementara)
kubectl exec -it deployment/order-worker-service -n production -- \
  ./app-binary --mode=emergency-reset-offset --topic=orders-v1

# 3. Menerapkan rate limiting kilat di Nginx upstream untuk menahan beban ke backend
sudo tee /etc/nginx/conf.d/rate_limit.conf << 'EOF'
limit_req_zone $binary_remote_addr zone=checkout_limit:10m rate=50r/s;
EOF
sudo nginx -t && sudo systemctl reload nginx
```

> [!WARNING]
> Jangan pernah melakukan *reset offset* secara sembarangan di lingkungan produksi tanpa memastikan data yang terlewat telah dicatat atau dicadangkan ke dalam *storage* sementara (seperti S3 atau MinIO) untuk menghindari kehilangan data transaksi finansial pelanggan.

Langkah darurat ini berhasil meredakan lonjakan latensi dalam waktu kurang dari 4 menit. Beban terdistribusi kembali, dan layanan checkout web kami berangsur pulih. Namun, pekerjaan rumah sesungguhnya baru saja dimulai: memastikan insiden ini tidak pernah terulang kembali dengan merombak total arsitektur defensif.

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

Evaluasi pasca-insiden menghasilkan tiga pilar perubahan arsitektur utama. 

Pertama, kami merevisi strategi *partitioning* di Kafka. Alih-alih mengandalkan *hash key* default yang rentan *hotspotING*, kami menerapkan *custom hashing function* di aplikasi produsen Go yang mendistribusikan *event* berdasarkan kombinasi *tenant_id* dan *sharding salt*, memastikan setiap partisi menerima beban yang setara.

Kedua, optimalisasi *Consumer Group* di Go. Kami mengubah pendekatan pembacaan pesan dari model tarik bebas menjadi model *worker pool* berkapasitas tetap menggunakan *buffered channel*, serta menyetel konfigurasi *MaxPollIntervalMs* dan *SessionTimeoutMs* dengan presisi tinggi agar Kafka tidak salah mendiagnosis status *consumer*.

Berikut adalah cuplikan kode pola *Consumer Worker Pool* yang aman di Go:

```go
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// Job mendefinisikan struktur unit data yang akan diproses oleh worker pool
type Job struct {
	ID      string
	Payload []byte
}

func Worker(ctx context.Context, id int, jobs <-chan Job, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("Worker %d shutting down gracefully...\n", id)
			return
		case job, ok := <-jobs:
			if !ok {
				return
			}
			// Simulasi pemrosesan event berkecepatan tinggi
			processEvent(job)
		}
	}
}

func processEvent(j Job) {
	time.Sleep(10 * time.Millisecond)
	fmt.Printf("Processed Job ID: %s\n", j.ID)
}
```

Ketiga, kami membangun mekanisme *Dead Letter Queue* (DLQ) otomatis. Setiap pesan yang gagal diproses setelah 3 kali percobaan (*max retries*) akan secara otomatis dialihkan ke topik Kafka khusus `orders-dlq`, yang kemudian dikonsumsi oleh layanan terpisah untuk analisis *manual review* atau perbaikan data tanpa mengganggu aliran transaksi utama.

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

Agar tidak ada lagi alarm tengah malam yang datang terlambat setelah sistem ambruk, kami memperketat sistem observabilitas menggunakan kombinasi Prometheus, Grafana, dan Alertmanager. Kami membuat aturan peringatan dini (*early warning rules*) yang langsung memicu notifikasi ke tim *on-call* sebelum latensi membahayakan pengguna.

Berikut adalah konfigurasi valid aturan *Alerting* Prometheus untuk mendeteksi lonjakan *Kafka Consumer Lag*:

```yaml
groups:
  - name: kafka-consumer-alerts
    rules:
      - alert: HighKafkaConsumerLag
        expr: sum(kafka_consumergroup_lag{topic="orders-v1"}) by (consumergroup) > 50000
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Kafka Consumer Lag terlalu tinggi di {{ $labels.consumergroup }}"
          description: "Jumlah antrean pesan yang belum diproses melebihi 50.000 pesan selama lebih dari 2 menit. Segera periksa worker pool!"
```

Dengan sistem deteksi dini ini, jika anomali serupa mulai merangkak naik di masa depan, tim *on-call* akan mendapatkan sinyal merah jauh sebelum *gateway timeout* menyapa para pelanggan setia kami di muka web.
