Apache Kafka adalah tempat menyimpan aliran event sebagai log: daftar yang cuma bisa ditambah di ujungnya, disimpan di beberapa server, dan dibaca oleh banyak pembaca, masing-masing dengan kecepatannya sendiri. Bayangin buku catatan pos satpam. Tiap kejadian ditulis di baris baru bernomor, gak pernah dihapus. Manajer, bagian keuangan, dan tim gudang masing-masing menyelipkan pembatas buku sendiri: membaca gak menghapus catatan, dan tiap orang tahu sampai baris mana dia sudah baca. Supaya bisa ditulis dan dibaca banyak orang sekaligus, catatannya dibagi ke beberapa buku, dan semua catatan tentang satu pesanan selalu masuk buku yang sama.
🧪 Tebakan cepat: topic Toko Buku punya 3 partisi. Grup consumer
warehouse-loaderdijalankan dengan 4 consumer supaya lebih cepat. Berapa consumer yang benar-benar bekerja? Tebak dulu, jawabannya ada di bagian "Consumer group: berbagi partisi".
Topic, partisi, offset
- Topic: aliran event dengan satu nama, tempat producer menulis dan consumer membaca. Toko Buku punya topic
order-lines: satu pesan per baris pesanan. - Partisi: topic dipecah jadi beberapa log terpisah supaya bisa ditulis dan dibaca paralel.
order-linespunya 3: partisi 0, 1, 2. - Offset: nomor urut pesan di dalam satu partisi, mulai dari 0. Urutan cuma dijamin di dalam satu partisi.
- Retention: berapa lama pesan disimpan. Default-nya 7 hari (
retention.ms= 604800000), terlepas dari sudah dibaca atau belum. Selama masih disimpan, consumer bisa membaca ulang dari offset mana pun (replay).
Pesan gak hilang saat dibaca. Itu beda besar dengan antrean (queue) biasa, dan alasan satu topic bisa dibaca warehouse, dashboard, dan indeks pencarian sekaligus.
Key menentukan partisi
Tiap pesan punya key dan value. Producer memilih partisi dari key: hash murmur2 dari key, dibagi jumlah partisi, sisanya nomor partisi. Key sama, partisi sama, jadi urutannya terjaga. Toko Buku memakai order_id sebagai key, supaya semua baris satu pesanan masuk partisi yang sama dan berurutan. Hasil partitioner kafka-python 3.0.11 (sama dengan klien Java) untuk 19 baris pesanan:
| Partisi | Pesanan (key) | Pesan (offset: item) |
|---|---|---|
| 0 | 1, 5, 7, 8, 11 | 0: item 1, 1: item 7, 2: item 10, 3: item 11, 4: item 15 |
| 1 | 4, 6, 10, 12, 13, 14 | 0: item 5, 1: item 6, 2: item 8, 3: item 9, 4: item 13, ... 9: item 19 |
| 2 | 2, 3, 9 | 0: item 2, 1: item 3, 2: item 4, 3: item 12 |
- Item 8 dan 9 (pesanan 6) bersebelahan di partisi 1, berurutan.
- Pembagiannya gak rata: 5, 10, 4. Hash gak tahu mana pesanan yang besar. Dengan ribuan key, baru rata.
- Tanpa key, producer menyebar pesan ke partisi mana saja, jadi pesan-pesan satu pesanan bisa terpisah dan urutannya gak terjamin.
Producer dan consumer
Producer dengan kafka-python 3.0.11 (di laptop ini gak ada broker Kafka, jadi kode ini gak dijalankan; Lab memodelkannya):
import json
from kafka import KafkaProducer
producer = KafkaProducer(
bootstrap_servers="localhost:9092",
key_serializer=str.encode,
value_serializer=lambda v: json.dumps(v).encode(),
acks="all",
)
for line in order_lines:
producer.send("order-lines", key=str(line["order_id"]), value=line)
producer.flush()
key=str(line["order_id"]): key menentukan partisi.acks="all": tunggu sampai semua replica yang sinkron (in-sync) menyimpan pesannya. Ini sudah default sejak Kafka 3.0; ditulis supaya kelihatan.flush(): tunggu semua pesan terkirim sebelum script selesai.
Consumer yang mengisi warehouse:
import json
from kafka import KafkaConsumer
consumer = KafkaConsumer(
"order-lines",
bootstrap_servers="localhost:9092",
group_id="warehouse-loader",
auto_offset_reset="earliest",
enable_auto_commit=False,
value_deserializer=json.loads,
)
for msg in consumer:
upsert_order_line(msg.value) # INSERT ... ON CONFLICT (item_id) DO UPDATE
consumer.commit() # commit offset sesudah tersimpan
group_id: nama grup; offset yang di-commit disimpan Kafka per grup per partisi.auto_offset_reset="earliest": grup baru mulai dari offset 0. Default-nyalatest, jadi grup baru cuma membaca pesan yang datang sesudah dia mulai.consumer.commit()sesudah data tersimpan: kalau consumer mati di antaranya, pesan itu dibaca lagi. Itu at-least-once.
Consumer group: berbagi partisi
Semua consumer dengan group_id yang sama berbagi pekerjaan: tiap partisi dipegang tepat satu consumer di grup itu. Dengan 1 consumer, dia memegang ketiganya. Dengan 2, yang pertama dapat partisi 0 dan 1, yang kedua partisi 2 (cara bagi range, default klien). Dengan 3, satu partisi satu consumer.
Dengan 4, consumer keempat menganggur: partisinya cuma 3. Itu jawaban tebakannya. Jumlah partisi adalah batas paralelisme satu grup, jadi tentukan sejak awal. Kalau satu consumer mati, Kafka melakukan rebalance: partisinya dibagi ulang ke yang masih hidup, yang melanjutkan dari offset terakhir yang di-commit. Grup lain (misalnya dashboard) membaca topic yang sama dengan offset-nya sendiri.
At-least-once dan consumer yang idempoten
Consumer yang memegang partisi 1 menyimpan offset 0 sampai 2, commit (offset 3), menyimpan offset 3 sampai 5 (item 9, 13, 14), lalu mati sebelum commit lagi. Penggantinya mulai dari offset 3, jadi tiga pesan itu disimpan dua kali:
| Cara menyimpan | Baris di warehouse |
|---|---|
INSERT biasa |
22: item 9, 13, 14 dobel |
upsert per item_id |
19 |
Kafka memilih "bisa dobel" daripada "bisa hilang" (commit dulu baru simpan, at-most-once, kehilangan pesan kalau mati di tengah). Jadi consumer harus idempoten: menyimpan pesan yang sama dua kali memberi hasil yang sama. Upsert per kunci yang unik, seperti di pelajaran CDC, adalah cara paling umum. Kafka punya transaksi untuk exactly-once, tapi itu berlaku antar-topic di dalam Kafka; untuk menulis ke database lain, tetap andalkan tulis yang idempoten.
Upsert per item_id gak membuang item 9: itu duplikat dari sumber dengan item_id sendiri, urusan cek kualitas data, bukan Kafka.
Kafka di pipeline: Connect, Debezium, dan versi 4
- Kafka Connect menjalankan konektor tanpa menulis producer atau consumer sendiri. Source connector membawa data masuk, misalnya Debezium yang membaca binlog MySQL (pelajaran CDC) dan menulis ke topic
tokobuku.toko_buku.order_items, ber-key primary key. Sink connector membawa data keluar, misalnya ke warehouse atau ke S3. - Kafka 4.x: versi 4.0.0 (18 Maret 2025) adalah rilis besar pertama yang berjalan sepenuhnya tanpa ZooKeeper. Metadata cluster dikelola Kafka sendiri dengan KRaft, broker butuh Java 17, dan protokol consumer group yang baru (KIP-848) resmi dipakai. Rilis terbaru 4.3.1 (25 Juni 2026). Tutorial lama yang menyalakan ZooKeeper dulu sudah gak berlaku.
- Alternatif yang memakai protokol Kafka: Redpanda (ditulis ulang tanpa JVM), dan layanan terkelola seperti Confluent Cloud, Amazon MSK, Aiven for Apache Kafka, Google Cloud Managed Service for Apache Kafka, dan Azure Event Hubs. Producer dan consumer di atas bisa dipakai ke semuanya.
Untuk Toko Buku hari ini, 19 baris dalam dua minggu, Kafka berlebihan: tabel di database ditambah job terjadwal sudah cukup. Kafka masuk akal kalau event-nya ribuan per detik, pembacanya banyak, atau data harus sampai dalam hitungan detik.
⚠️ Jebakan yang sering ketemu
- Consumer lebih banyak dari partisi. Yang lebih tetap menganggur. Tambah partisi kalau perlu paralel, dan ingat: menambah partisi mengubah partisi tujuan key yang lama.
- Commit sebelum menyimpan. Kalau consumer mati di antaranya, pesan hilang. Simpan dulu, commit kemudian, dan buat penyimpanannya idempoten.
- Tanpa key untuk event yang harus berurutan. "Dibayar" bisa diproses sebelum "dibuat". Pakai key yang mewakili urutannya, misalnya
order_id. - Mengira Kafka menyimpan selamanya. Lewat 7 hari (default), pesan lama dihapus. Kalau butuh semua sejarah, simpan juga di lake.
TL;DR: Kafka menyimpan event di topic, dipecah jadi partisi; tiap pesan punya offset berurutan di partisinya, dan membaca gak menghapus pesan (retention default 7 hari, bisa replay). Key menentukan partisi (murmur2), jadi key order_id menjaga urutan semua baris satu pesanan; di Toko Buku 19 pesan terbagi 5, 10, 4. Consumer dengan group_id yang sama berbagi partisi, satu partisi satu consumer, jadi 4 consumer untuk 3 partisi berarti satu menganggur. Commit sesudah menyimpan memberi at-least-once: pesan bisa datang dua kali (22 baris dengan INSERT, 19 dengan upsert), jadi consumer harus idempoten. Kafka Connect menjalankan konektor seperti Debezium; Kafka 4.x jalan dengan KRaft tanpa ZooKeeper; Redpanda dan layanan terkelola memakai protokol yang sama.