# Membangun Event-Driven Architecture Berbasis Kafka & Go untuk High-Throughput

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/membangun-event-driven-architecture-berbasis-kafka-go-untuk-high-throughput

---

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

Pukul 02:14 WIB, ponsel saya bergetar liar memecah keheningan kamar. PagerDuty tidak sekadar bernyanyi, ia berteriak. Di layar dashboard Grafana, kurva *throughput* layanan backend Go yang biasanya stabil di angka 150.000 request per detik mendadak terjun bebas, sementara metrik *consumer lag* pada klaster Apache Kafka melompat curam menembus angka jutaan pesan yang belum terproses. Latensi yang biasanya bersembunyi manis di bawah 20 milidetik kini merayap naik, melewati batas SLA dan memicu rantai timeout di seluruh gateway Nginx kami.

Kepanikan kecil sempat menyergap tim *on-call*. Apakah database MySQL utama kita mengalami deadlock masif? Atau ada serangan DDoS yang menyusup di balik jubah Cloudflare? Yang pasti, infrastruktur *event-driven* kami yang dirancang untuk menangani jutaan event per detik kini sedang megap-megap di ambang kolaps total. Tidak ada waktu untuk menebak-nebak; sebagai Senior Infrastructure Engineer, saya harus segera membuka terminal, menyeduh kopi hitam, dan mulai menyelidiki bangkai sistem yang sedang sekarat ini.

Transisi dari kepanikan menuju investigasi yang dingin menuntut kita untuk segera masuk ke dalam perut sistem dan membaca jejak digital yang ditinggalkan oleh para pekerja Go dan broker Kafka.

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

Langkah pertama seorang detektif infrastruktur adalah memeriksa tempat kejadian perkara: *centralized logging* dan metrik telemetri. Saya langsung melompat ke terminal untuk mengintip apa yang sedang diteriakkan oleh container Docker yang menjalankan aplikasi Go kami.

```bash
# Memantau error log secara real-time dari pod backend Go yang mulai kewalahan
kubectl logs -n production -l app=event-processor --tail=100 -f | grep -iE "panic|fatal|error|connection refused"
```

Hasilnya langsung menampar muka saya. Ratusan baris log memuntahkan pesan: `fatal error: concurrent map write` diikuti oleh tumpukan memori (*stack trace*) dari pustaka `segmentio/kafka-go` yang kami gunakan. Tidak lama kemudian, memori kontainer melonjak dari 512MB menjadi 8GB dalam hitungan detik, memicu OOM (Out of Memory) Killer dari kernel Linux yang dengan kejam membunuh pod-pod kami satu per satu.

> [!WARNING]
> Jangan pernah meremehkan operasi *concurrent write* pada *native map* di Go saat menangani konsumsi pesan paralel dari banyak *goroutine*. Kesalahan kecil dalam manajemen state per *consumer group* adalah pembunuh senyap yang paling sering lolos dari *code review*.

Menelusuri lebih dalam ke dalam konfigurasi Kafka dan cara kami mendistribusikan *partitioning*, akar masalah mulai menampakkan wujud aslinya. Mari kita lihat bagaimana struktur partisi dan consumer group dirancang sebelum bencana ini terjadi.

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

Setelah membongkar kode sumber, terungkaplah dosa-dosa arsitektural kami. Pertama, kami menggunakan strategi *partitioning* yang buruk pada topik Kafka utama. Dari 64 partisi yang tersedia, seluruh event dengan tipe pembayaran terkonsentrasi hanya pada 2 partisi karena kesalahan pemilihan *partition key*. Akibatnya, terjadi *hotspotting* ekstrem: dua *consumer* bekerja hingga CPU-nya meleleh 100%, sementara 62 *consumer* lainnya menganggur kelaparan.

Kedua, ketika salah satu *consumer* membaca pesan korup yang dikirim oleh layanan pihak ketiga (kita sebut ini sebagai *Poison Pill Message*), aplikasi Go kami gagal menangani error tersebut secara elegan. Pesan itu memicu *infinite retry loop* di dalam memori tanpa pernah dikirim ke *Dead Letter Queue* (DLQ), menyebabkan antrean lokal membludak dan memicu kebocoran memori.

Berikut adalah cuplikan kode implementasi awal yang bermasalah, di mana manajemen *consumer group* dan penanganan error dilakukan secara serampangan:

```go
// CONTOH KODE BERBAHAYA (BEFORE): Rentan terhadap Poison Pill dan Hotspotting
package main

import (
	"context"
	"log"
	"github.com/segmentio/kafka-go"
)

func startBadConsumer(ctx context.Context, r *kafka.Reader) {
	for {
		m, err := r.ReadMessage(ctx)
		if err != nil {
			break
		}
		// Berbahaya: Jika ProcessMessage gagal karena data korup, loop akan terus mencoba
		go func(msg kafka.Message) {
			if err := ProcessMessage(msg.Value); err != nil {
				log.Printf("Error processing: %v", err)
				// Tidak ada mekanisme retry limit atau DLQ, berujung pada hang/panic
			}
		}(m)
	}
}
```

Melihat kerapuhan ini, kita tidak boleh tinggal diam. Kita memerlukan tindakan bedah darurat detik ini juga untuk menyelamatkan layanan sebelum bos besar menelpon.

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

Kita butuh solusi instan untuk menghentikan pendarahan. Langkah pertama adalah melakukan *rolling restart* pada deployment Go dengan membatasi konkurensi, lalu mengalihkan *traffic* sementara, dan membuang pesan beracun yang menyumbat pipa.

Berikut adalah rangkaian perintah terminal darurat (Emergency Runbook) yang saya eksekusi langsung di server produksi untuk meredakan situasi dalam waktu kurang dari 60 detik:

```bash
# 1. Skala turunkan replika pod yang mengalami OOM untuk menghentikan lonjakan beban database
kubectl scale deployment event-processor --replicas=0 -n production

# 2. Hapus consumer group yang macet agar Kafka mereset offset yang rusak
kafka-consumer-groups.sh --bootstrap-server kafka.internal:9092 --describe --group payment-processor-group
kafka-consumer-groups.sh --bootstrap-server kafka.internal:9092 --delete --group payment-processor-group

# 3. Skala kembali deployment dengan versi hotfix yang membatasi goroutine pool
kubectl scale deployment event-processor --replicas=10 -n production
```

> [!TIP]
> Selalu pastikan Anda memiliki skrip otomasi CLI untuk *reset consumer group* dan *purge dead messages* yang siap dieksekusi dalam satu baris perintah saat insiden tengah malam terjadi.

Setelah api berhasil dipadamkan dan sistem kembali bernapas, saatnya kita merancang ulang fondasi pertahanan agar insiden serupa tidak pernah mampir lagi ke sistem kita.

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

Pengalaman hampir meruntuhkan sistem ini melahirkan kebijakan arsitektural baru yang ketat. Kami merombak total cara worker Go berinteraksi dengan Kafka. Kami menerapkan *Worker Pool pattern* berbasis channel untuk membatasi konkurensi, mengimplementasikan *Dead Letter Queue* (DLQ) otomatis untuk *Poison Pill*, dan memperbaiki strategi *hashing key* pada produsen agar distribusi partisi merata.

Berikut adalah implementasi kode Go yang bersih, aman, tangguh, dan tahan banting untuk menangani *high-throughput event processing*:

```go
// CONTOH KODE AMAN (AFTER): Implementasi Worker Pool, DLQ, dan Graceful Handling
package main

import (
	"context"
	"log"
	"github.com/segmentio/kafka-go"
)

type WorkerPool struct {
	workerCount int
	taskQueue   chan kafka.Message
	writer      *kafka.Writer
}

func NewWorkerPool(count int, dlqWriter *kafka.Writer) *WorkerPool {
	return &WorkerPool{
		workerCount: count,
		taskQueue:   make(chan kafka.Message, 1000),
		writer:      dlqWriter,
	}
}

func (wp *WorkerPool) Start(ctx context.Context) {
	for i := 0; i < wp.workerCount; i++ {
		go func(workerID int) {
			for {
				select {
				case <-ctx.Done():
					return
				case msg := <-wp.taskQueue:
					if err := wp.safeProcess(ctx, msg); err != nil {
						log.Printf("[Worker %d] Kirim pesan ke DLQ karena error: %v", workerID, err)
						// Lempar ke Dead Letter Queue agar tidak memblokir antrean utama
						_ = wp.writer.WriteMessages(ctx, msg)
					}
				}
			}
		}(i)
	}
}

func (wp *WorkerPool) safeProcess(ctx context.Context, msg kafka.Message) error {
	// Simulasi pemrosesan data yang aman dari panic
	// Jika gagal setelah 3 kali retry, kembalikan error untuk dipindahkan ke DLQ
	return nil
}
```

Selain perbaikan kode, kita juga harus memastikan bahwa siklus hidup aplikasi Go kita mendukung *Graceful Shutdown* agar tidak ada pesan yang terputus di tengah jalan saat proses *rolling update* dilakukan.

```go
// Implementasi Graceful Shutdown pada Go Worker untuk menangkap sinyal SIGTERM
package main

import (
	"context"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func WaitForShutdown(cancel context.CancelFunc) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan
	
	log.Println("Menerima sinyal shutdown, membersihkan sumber daya...")
	cancel()
	
	// Berikan waktu maksimal 30 detik bagi worker untuk menyelesaikan tugas aktif
	time.Sleep(5 * time.Second)
	log.Println("Aplikasi berhasil dimatikan dengan aman.")
}
```

Agar kita bisa tidur nyenyak tanpa dihantui rasa cemas, langkah terakhir adalah memasang sistem peringatan dini yang proaktif.

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

Jangan menunggu pelanggan melapor atau PagerDuty berteriak kesakitan di tengah malam. Kita harus mendeteksi anomali sebelum dampaknya terasa ke pengguna akhir. Berikut adalah konfigurasi Prometheus Alertmanager yang kami pasang untuk memantau kesehatan klaster Kafka dan worker Go secara real-time:

```yaml
groups:
- name: KafkaConsumerAlerts
  rules:
  - alert: HighConsumerLag
    expr: kafka_consumergroup_lag{topic="payment-events"} > 50000
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Consumer lag sangat tinggi di topik payment-events"
      description: "Lag telah melewati angka 50.000 pesan selama lebih dari 2 menit. Segera periksa kesehatan worker Go!"

  - alert: WorkerMemoryLeak
    expr: container_memory_usage_bytes{container="event-processor"} > 6144000000
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Penggunaan memori pod event-processor mendekati batas maksimal (6GB)"
      description: "Kemungkinan besar terjadi kebocoran memori atau loop pemrosesan pesan korup."

  - alert: HighDLQRate
    expr: rate(kafka_producer_message_count{topic="payment-events-dlq"}[5m]) > 10
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Lonjakan pesan masuk ke Dead Letter Queue"
      description: "Lebih dari 10 pesan per detik dibuang ke DLQ. Periksa integritas data dari hulu."
```

Dengan sistem peringatan dini ini, arsitektur *event-driven* berbasis Kafka dan Go kami kini bukan lagi sebuah bom waktu yang siap meledak, melainkan sebuah mesin pengangkut data raksasa yang tangguh, andal, dan siap menghadapi terjangan jutaan event per detik tanpa kehilangan satu byte pun data berharga.