# Merancang Arsitektur Event-Driven Skala Besar dengan Apache Kafka dan Go Workers

> Diterbitkan oleh Literally Web ID pada 21 Oct 2026
> Sumber: https://literally.web.id/articles/merancang-arsitektur-event-driven-skala-besar-dengan-apache-kafka-dan-go-workers

---

## 1. Titik Awal Masalah: Mengapa Arsitektur Lama Sering Ambruk di Produksi

Ingatkah Anda malam ketika *traffic flash sale* mendadak melonjak seratus kali lipat, dan dashboard monitoring berubah menjadi lautan warna merah menyala? Di balik layar, database MySQL Anda merintih kesakitan, koneksi PHP-FPM habis terpakai (*max_children reached*), dan Nginx mulai melempar error *502 Bad Gateway* ke seluruh penjuru internet. Masalah klasik ini sering kali berakar dari desain web monolitik atau komunikasi sinkron (*REST/gRPC*) yang tidak siap menahan hantaman gelombang data masif secara simultan.

Sebagai seorang systems engineer yang telah berkali-kali terbangun tengah malam karena pagerDuty berteriak, saya belajar satu hal mutlak: kopling erat (*tight coupling*) antar servis adalah bom waktu. Ketika layanan *checkout* e-commerce memanggil API *inventory*, API *payment*, dan layanan *notification* secara sekuensial, kegagalan di satu titik kecil akan merontokkan seluruh ekosistem. Kita tidak bisa lagi hanya mengandalkan penambahan RAM atau skala vertikal server Redis dan database tanpa membenahi fondasi aliran datanya. Di sinilah pergeseran paradigma menuju arsitektur *event-driven* berbasis Apache Kafka menjadi penyelamat mutlak.

Namun, memindahkan beban kerja dari sinkron ke asinkron bukanlah pil ajaib yang langsung menyembuhkan segalanya tanpa efek samping. Kompleksitas baru pun lahir: bagaimana memastikan pesan tidak hilang saat *worker* Go Anda mengalami *crash* mendadak? Bagaimana mengatur *offset* agar pembacaan data tidak berantakan? Pertanyaan-pertanyaan operasional inilah yang membawa kita menelusuri jeroan sistem *messaging* industri berat ini secara mendalam.

## 2. Di Balik Layar: Bagaimana Sistem Ini Bekerja Tanpa Sihir

Mari kita bedah apa yang sebenarnya terjadi di balik kap mesin Apache Kafka ketika jutaan *payload* JSON melintas setiap detiknya. Bayangkan Kafka bukan sekadar antrean (*message queue*) biasa seperti RabbitMQ, melainkan sebuah *distributed commit log* raksasa yang bertindak seperti buku besar abadi. Setiap pesan yang masuk dicatat secara berurutan (*append-only*) ke dalam disk dengan efisiensi I/O yang luar biasa tinggi berkat pemanfaatan *page cache* sistem operasi dan teknik *zero-copy network transmission*.

Di dalam ekosistem Kafka, data dikelompokkan ke dalam sebuah **Topic**. Agar skalabilitas horizontal tercapai, sebuah topic dipecah lagi menjadi beberapa **Partition**. Partisi-partisi inilah unit paralelisme sejati. Jika Anda memiliki 4 partisi dalam satu topic, maksimal ada 4 *consumer* aktif dalam satu **Consumer Group** yang bisa membaca data secara bersamaan tanpa saling berebut.

```
[ Producer ] ---> ( Topic: orders ) 
                     ├── Partition 0 ---> [ Go Worker 1 ]
                     ├── Partition 1 ---> [ Go Worker 2 ]
                     ├── Partition 2 ---> [ Go Worker 3 ]
                     └── Partition 3 ---> [ Go Worker 4 ]
```

Di sisi hilir, *worker pool* berbasis Go yang kita bangun akan bergabung ke dalam *Consumer Group* yang sama. Kafka secara otomatis bertindak sebagai dirigen orkestra, membagi kepemilikan partisi-partisi tersebut ke *worker* Anda melalui protokol *rebalance*. Jika salah satu *worker* mati karena kehabisan memori atau gangguan jaringan, Kafka dengan sigap mengalihkan partisinya ke *worker* lain yang masih hidup. Transisi mulus inilah yang membuat sistem tetap bernapas bahkan di tengah kekacauan infrastruktur.

## 3. Pilihan Pendekatan & Kompromi yang Harus Diambil

Sebelum mengetikkan baris kode pertama, kita harus jujur pada diri sendiri mengenai kompromi arsitektural yang sedang kita ambil. Di dunia *distributed systems*, tidak ada makan siang gratis. Teorema CAP dan hukum realitas lapangan memaksa kita memilih antara kecepatan pemrosesan atau jaminan pengiriman pesan (*delivery semantics*).

Secara garis besar, kita dihadapkan pada tiga pilihan *offset commit strategy*:

1. **At-Most-Once:** Pesan di-*commit* sebelum diproses. Sangat cepat, namun jika *worker* Anda *panic* di tengah jalan, data tersebut lenyap selamanya tanpa jejak.
2. **At-Least-Once:** Pesan diproses terlebih dahulu, baru *offset*-nya di-*commit*. Ini adalah standar emas untuk aplikasi finansial dan e-commerce. Konsekuensinya? Risiko *duplicate processing* sangat tinggi jika *worker* terputus sebelum sempat melakukan *commit*.
3. **Exactly-Once:** Impian setiap engineer, namun membutuhkan konfigurasi transaksional yang kompleks dan membebani latensi jaringan secara signifikan.

Untuk mayoritas backend web modern—mulai dari pemrosesan log Nginx, sinkronisasi cache Redis, hingga webhook pihak ketiga—strategi **At-Least-Once** yang dikombinasikan dengan mekanisme *Idempotency* di level database adalah kompromi paling rasional dan teruji di lapangan.

## 4. Meracik Lingkungan: Persiapan & Prasyarat Lapangan

Mari kita siapkan arena bermain kita menggunakan Docker Compose. Kita akan menjalankan Zookeeper (atau Kafka dalam mode KRaft untuk versi modern, namun mari kita gunakan kombinasi klasik yang stabil), satu *broker* Kafka, dan UI manajemen Kafka agar kita bisa memantau aliran pesan dengan mata kepala sendiri.

Simpan konfigurasi berikut sebagai `docker-compose.yml` di direktori kerja Anda:

```yaml
version: '3.8'

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
```

Jalankan perintah `docker-compose up -d` di terminal Anda. Pastikan kontainer berjalan mulus tanpa ada *restart loop* yang mencurigakan. Anda baru saja mendirikan fondasi *message broker* enterprise di dalam laptop lokal Anda.

## 5. Langkah Implementasi: Membangun Solusi yang Tangguh

Sekarang, mari kita beralih ke jantung aplikasi backend kita: **Go (Golang)**. Kita akan menggunakan pustaka `segmentio/kafka-go` atau `confluent-kafka-go` (yang membungkus librari C `librdkafka` yang legendaris). Untuk kestabilan jangka panjang di lingkungan produksi tanpa ketergantungan *compiler CGO*, mari kita gunakan `segmentio/kafka-go` yang ditulis murni dalam Go.

Inisialisasi modul Go Anda:

```bash
go mod init kafka-worker-demo
go get github.com/segmentio/kafka-go
```

Struktur direktori kita akan dibuat sangat bersih: satu file `producer.go` untuk mengirimkan simulasi data transaksi web, dan satu file `worker.go` yang menjalankan *worker pool* konkurensi tinggi untuk memproses pesan tersebut secara asinkron.

## 6. Membedah Baris Kode Krusial yang Kerap Menjebak

Mari kita tulis implementasi *concurrent consumer* di Go. Di sinilah banyak junior engineer terjebak: mereka membaca pesan dari Kafka secara sekuensial dalam satu *loop* tunggal, membuat throughput aplikasi ambruk total.

Berikut adalah pola *worker pool* yang benar, di mana pesan yang ditarik dari Kafka didistribusikan ke sejumlah *goroutine worker* melalui channel internal:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"
	"syscall"

	"github.com/segmentio/kafka-go"
)

const (
	topic     = "user-signup-events"
	broker    = "localhost:9092"
	groupID   = "auth-service-workers"
	workerNum = 5
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Konfigurasi Kafka Reader dengan Consumer Group
	r := kafka.NewReader(kafka.ReaderConfig{
		Brokers:  []string{broker},
		Topic:    topic,
		GroupID:  groupID,
		MinBytes: 10e3, // 10KB
		MaxBytes: 10e6, // 10MB
	})
	defer r.Close()

	jobs := make(chan kafka.Message, 100)
	var wg sync.WaitGroup

	// Spawn Worker Pool
	for i := 1; i <= workerNum; i++ {
		wg.Add(1)
		go worker(ctx, i, jobs, r, &wg)
	}

	// Graceful shutdown listener
	sigchan := make(chan os.Signal, 1)`,
  "excerpt": "Panduan komprehensif merancang arsitektur event-driven skala besar menggunakan Apache Kafka dan worker pool berbasis Go untuk performa tinggi.