Kafka, log di jantung sistem Anda.
Penelusuran dari prinsip dasar tentang platform event-streaming terdistribusi yang menggerakkan sistem saraf LinkedIn, Uber, Netflix, dan kebanyakan fintech modern: dari satu gagasan yang menopang segalanya (commit log append-only), lewat partisi, consumer group, offset dan semantik pengiriman, sampai retensi, skema, pola event-driven, trik outbox/CDC, dan stream processing. Ditulis bukan hanya menyebut apa fungsi tiap bagian tapi kenapa ia ada dan bagaimana ia bekerja di dalamnya. Kode producer/consumer di Go dan Python.
Apa Sebenarnya Kafka Itu
Buang pemasaran dan Kafka satu hal: log peristiwa terdistribusi, append-only, tahan lama yang dibaca banyak consumer independen dengan kecepatan mereka sendiri. Bukan antrean, bukan database, bukan message bus dalam arti klasik, sebuah log. Producer menambahkan record ke ujung; consumer membaca maju dari posisi yang mereka lacak sendiri; dan record tetap setelah dibaca, selama retensi terkonfigurasi. Begitu satu gambar itu masuk kepala Anda, setiap fitur di manual ini konsekuensinya.
Deskripsi resminya "platform event streaming terdistribusi," dan tiap kata layak tempatnya. Distributed: ia berjalan sebagai klaster broker, mereplikasi data demi toleransi kegagalan dan scaling horizontal. Event streaming: ia dibangun di sekitar aliran event immutable yang tak terbatas dan berkelanjutan (sesuatu terjadi: order ditempatkan, pembayaran jelas, sensor membaca), bukan panggilan request/response. Platform: producer, consumer, ekosistem konektor, dan stream processing semuanya duduk di log yang sama.
Kafka commit log terdistribusi, tahan lama, append-only berisi event immutable, dipartisi untuk skala dan direplikasi untuk toleransi kegagalan, yang dibaca banyak consumer independen di offset mereka sendiri: dengan event dipertahankan setelah dibaca. Segala hal lain detail di atas itu.
Antrean vs Log: Kenapa Kafka Berbeda
Ini bagian terpenting di manual, karena mencampurkan Kafka dengan message queue tradisional akar sebagian besar kebingungan. Bab task-queues mencakup model antrean secara mendalam, producer, worker, broker seperti RabbitMQ dan SQS, retry, visibility timeout. Kafka jenis benda yang berbeda, dan perbedaannya bukan detail; ia mengubah masalah apa yang dipecahkan.
| Dimensi | Antrean tradisional (bab 10) | Log Kafka |
|---|---|---|
| Setelah pesan dibaca | Dihapus (dikonsumsi sekali) | Dipertahankan (sampai retensi kedaluwarsa) |
| Siapa yang menerima pesan | Satu consumer yang bersaing | Setiap consumer group, independen |
| Replay / baca ulang | Tidak, sudah hilang | Ya, seek ke offset mana pun |
| Posisi consumer | Dilacak & dihapus broker | Consumer melacak offsetnya sendiri |
| Urutan | Lemah/tidak ada lintas consumer | Ketat dalam satu partisi |
| Model mental | Distribusi kerja (daftar tugas) | Riwayat peristiwa / sumber kebenaran (logbook) |
| Titik manis | Offloading tugas, pemrosesan job | Event streaming, banyak subscriber, replay, throughput tinggi |
Anda sering memakai keduanya: Kafka sebagai tulang punggung event tahan lama (order, pembayaran, klik di-stream ke banyak sistem), dan task queue (Celery/SQS, bab 10) untuk job latar diskret (kirim email ini, resize gambar itu). Perbandingan penuh dan "yang mana kapan" di bagian 17. Untuk sekarang pegang bedanya: antrean = distribusikan kerja dan lupakan; log = catat event dan biarkan banyak sistem mengonsumsi & memutar ulangnya.
Commit Log
Semua yang Kafka lakukan bertumpu pada struktur data paling rendah hati dalam komputasi: log append-only: urutan record terurut yang hanya bisa Anda tambahkan di ujung, tidak pernah menyisipkan di tengah atau memodifikasi. Tiap record mendapat offset yang naik monoton (0, 1, 2, ...) yang menjadi alamat permanennya. Ini struktur yang sama yang dipakai database untuk write-ahead log dan daya tahannya; wawasan Kafka menjadikan log itu sendiri abstraksi primer yang publik alih-alih detail internal tersembunyi.
Kenapa log append-only begitu cepat dan begitu berguna
- I/O sekuensial murah. Menambahkan ke ujung file dan membacanya maju operasi disk sekuensial, jauh lebih cepat daripada akses acak, bahkan di SSD. Kafka bersandar pada OS page cache dan transfer zero-copy untuk memindahkan data dari disk ke jaringan tanpa melewati memori aplikasi, begitulah ia menopang jutaan pesan per detik di perangkat keras sederhana.
- Immutabilitas sederhana & aman. Record tidak pernah berubah, jadi tidak ada lock untuk update, tidak ada konflik baca/tulis, dan pembaca serentak tidak pernah mengganggu. Riwayat immutable juga sepele di-cache dan mudah dinalar.
- Log adalah riwayatnya. Karena tidak ada yang ditimpa, log catatan lengkap terurut dari semua yang terjadi, persis yang membuat replay (bagian 11), event sourcing (bagian 14), dan memberi makan consumer baru dari awal mungkin.
Pegang log append-only dengan mantap: tulis menambahkan dan mendapat offset; baca bergerak maju; record immutable dan dipertahankan. Partisi (bagian 4) hanya banyak log seperti itu; consumer group (bagian 7) penanda buku di dalamnya; semantik pengiriman (bagian 9) soal seberapa hati-hati Anda melacak penanda itu. Seluruh sistem gagasan log, di-scaling dan direplikasi.
Topik & Partisi
Topic aliran bernama dari event terkait, orders, payments, page-views. Ia kanal logis yang ditulis producer dan dibaca consumer. Tapi topic bukan satu log; ia dipecah menjadi partisi, dan partisi tempat semua mekanik nyata hidup. Partisi sekaligus unit Kafka untuk paralelisme, urutan, dan penyimpanan, tiga properti terpenting dalam sistem, semuanya menumpang satu konsep ini.
Tiga peran partisi
- Paralelisme. Partisi bisa hidup di broker berbeda dan dikonsumsi consumer berbeda sekaligus, jadi throughput topic berskala dengan jumlah partisinya. Lebih banyak partisi -> lebih banyak konsumsi paralel (sampai batas consumer group, bagian 7).
- Urutan. Kafka menjamin urutan dalam satu partisi dan tidak berjanji apa pun lintas partisi. Ini kompromi sentral (bagian 10): untuk menjaga event terkait terurut, rutekan mereka ke partisi yang sama via key bersama (semua event untuk
order-42memakai key"order-42", jadi mereka terurut ketat relatif satu sama lain). - Penyimpanan. Setiap partisi secara fisik satu set file segmen di disk broker, log append-only aktual dari bagian 3. Retensi dan kompaksi (bagian 11) beroperasi per partisi.
Anda bisa menaikkan partisi topic belakangan, tapi melakukannya memecah stabilitas key-ke-partisi (modulo berubah, jadi key yang ada bisa pindah) dan karenanya memecah jaminan urutan untuk key yang sedang berjalan. Dan Anda tidak bisa dengan mudah menurunkannya. Jadi pilih jumlah partisi sengaja di depan berdasarkan target throughput dan paralelisme consumer maksimum yang akan Anda butuhkan, terlalu sedikit membatasi scaling Anda, terlalu banyak menambah overhead (bagian 19, bagian 20).
Broker, Replikasi & Daya Tahan
Klaster Kafka satu set broker (server); partisi tersebar di antara mereka, dan begitulah klaster berskala dan selamat dari kegagalan. Daya tahan datang dari replikasi: tiap partisi punya satu replika leader dan beberapa replika follower di broker lain. Semua baca dan tulis pergi ke leader; follower terus menyalin darinya. Jika broker leader mati, follower dipromosikan, tanpa data hilang, ketidaktersediaan singkat. Ini pola pikir desired-state/failover yang sama dengan bab Kubernetes, diterapkan ke data.
Kenop daya tahan: acks & ISR
Seberapa aman sebuah tulis bergantung dua setelan. acks producer menyebut berapa replika yang harus mengonfirmasi sebelum tulis dianggap selesai: acks=0 (fire-and-forget, bisa kehilangan data), acks=1 (leader saja, hilang jika leader mati sebelum follower menyalin), atau acks=all (leader plus semua in-sync replica: pilihan aman). ISR (in-sync replica set) follower yang saat ini mengejar leader; memasangkan acks=all dengan min.insync.replicas=2 berarti tulis hanya sukses jika setidaknya dua replika memilikinya, selamat dari satu kehilangan broker dengan nol kehilangan data.
| Setelan | Jaminan | Kompromi |
|---|---|---|
acks=0 | Tidak ada, fire and forget | Tercepat; bisa diam-diam kehilangan data |
acks=1 | Leader memilikinya | Cepat; hilang jika leader gagal sebelum replikasi |
acks=all + min.insync.replicas=2 | >=2 replika memilikinya sebelum sukses | Paling aman; latensi sedikit lebih tinggi, standar untuk data penting |
Kafka lama mengandalkan klaster ZooKeeper terpisah untuk menyimpan metadata dan memilih leader. Kafka modern memakai KRaft (Kafka Raft), melipat peran itu ke broker itu sendiri via quorum controller berbasis Raft, lebih sedikit bagian yang bergerak, failover lebih cepat, ops lebih sederhana. Jika Anda membaca dokumen lama menyebut ZooKeeper, KRaft penggantinya.
Producer
Producer menerbitkan record ke topic. Tiga perhatian producer membentuk kebenaran dan performa: key (yang menentukan partisi dan karenanya urutan, bagian 4), acks (daya tahan, bagian 5), dan batching (producer mengelompokkan record dan mengompresnya untuk throughput). Record pasangan key/value plus header opsional dan timestamp; value payload event Anda (sering Avro/Protobuf, bagian 12).
// Using confluent-kafka-go (librdkafka). kafka-go (segmentio) is a pure-Go alternative.
p, _ := kafka.NewProducer(&kafka.ConfigMap{
"bootstrap.servers": "localhost:9092",
"acks": "all", // durability: leader + in-sync replicas (sec 5)
"enable.idempotence": true, // no duplicates on producer retry (sec 9)
"linger.ms": 5, // wait up to 5ms to batch records (throughput)
"compression.type": "zstd",
})
defer p.Close()
topic := "orders"
// The KEY decides the partition -> all events for one order stay ordered (sec 4, sec 10).
err := p.Produce(&kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
Key: []byte("order-42"),
Value: []byte(`{"event":"placed","amount":1999}`),
}, nil)
if err != nil { log.Fatal(err) }
// Producing is async + batched; Flush blocks until queued messages are delivered.
p.Flush(5000)
// Delivery reports tell you the final partition/offset (or an error) per message:
// for e := range p.Events() { if m, ok := e.(*kafka.Message); ok { ... } }# Using confluent-kafka (librdkafka). kafka-python / aiokafka are alternatives.
from confluent_kafka import Producer
p = Producer({
"bootstrap.servers": "localhost:9092",
"acks": "all", # durability: leader + in-sync replicas (sec 5)
"enable.idempotence": True, # no duplicates on producer retry (sec 9)
"linger.ms": 5, # batch window for throughput
"compression.type": "zstd",
})
def on_delivery(err, msg): # async callback: final partition/offset or error
if err:
print("delivery failed:", err)
else:
print(f"delivered to {msg.topic()}[{msg.partition()}]@{msg.offset()}")
# The KEY decides the partition -> all events for one order stay ordered (sec 4, sec 10).
p.produce(
"orders",
key=b"order-42",
value=b'{"event":"placed","amount":1999}',
on_delivery=on_delivery,
)
p.flush() # block until queued messages are deliveredJika Anda tidak set key, record tersebar lintas partisi (round-robin / sticky), memaksimalkan paralelisme tapi melepas hubungan urutan apa pun di antara mereka. Itu baik untuk event independen; itu bug jika event itu perlu tetap terurut (semua pembaruan satu akun, misalnya). Key berdasarkan batas urutan/entitas Anda: biasanya ID entitas, kapan pun urutan penting (bagian 10).
Consumer & Consumer Group
Consumer membaca record dari partisi. Abstraksi yang kuat consumer group: satu set consumer berbagi group ID yang secara kooperatif membagi partisi topic di antara mereka supaya setiap partisi dibaca tepat satu consumer dalam group. Tambah consumer ke group dan Kafka rebalance, menetapkan ulang partisi untuk menyebar beban. Begitulah Anda scaling konsumsi horizontal, dan ini mekanisme di balik berbagi-kerja dan broadcast.
c, _ := kafka.NewConsumer(&kafka.ConfigMap{
"bootstrap.servers": "localhost:9092",
"group.id": "billing", // the consumer GROUP, scale by adding instances
"auto.offset.reset": "earliest", // where to start if no committed offset (sec 8)
"enable.auto.commit": false, // we'll commit manually for at-least-once (sec 9)
})
defer c.Close()
c.Subscribe("orders", nil) // Kafka assigns this instance some partitions
for {
msg, err := c.ReadMessage(-1) // blocks for the next record
if err != nil { continue }
process(msg.Value) // do the work FIRST...
c.CommitMessage(msg) // ...THEN commit the offset (at-least-once, sec 9)
}from confluent_kafka import Consumer
c = Consumer({
"bootstrap.servers": "localhost:9092",
"group.id": "billing", # the consumer GROUP, scale by adding instances
"auto.offset.reset": "earliest", # where to start if no committed offset (sec 8)
"enable.auto.commit": False, # commit manually for at-least-once (sec 9)
})
c.subscribe(["orders"]) # Kafka assigns this instance some partitions
try:
while True:
msg = c.poll(1.0) # wait up to 1s for the next record
if msg is None:
continue
if msg.error():
print("error:", msg.error()); continue
process(msg.value()) # do the work FIRST...
c.commit(msg) # ...THEN commit the offset (at-least-once, sec 9)
finally:
c.close() # leave the group cleanly -> triggers a rebalanceKarena satu partisi dikonsumsi paling banyak satu consumer per group, sebuah group bisa berguna memiliki paling banyak consumer aktif sebanyak partisi topic. Topic 4 partisi dengan 6 anggota group meninggalkan 2 consumer menganggur. Jadi paralelisme konsumsi maksimum Anda ditetapkan jumlah partisi (bagian 4), alasan lain mengukur partisi untuk throughput puncak Anda di depan (bagian 20).
Offset & Manajemen Offset
Offset posisi record di partisinya (bagian 3). Tugas consumer melacak offset mana yang sudah ia proses, committed offset-nya: supaya saat restart atau rebalance ia melanjutkan dari tempat yang tepat alih-alih membaca ulang semuanya atau melewatkan record. Kafka menyimpan offset committed ini di topic internal (_consumeroffsets), ber-key group + partisi. Celah antara offset terbaru di partisi dan committed offset group adalah consumer lag: metrik kesehatan terpenting tunggal untuk consumer (bagian 19).
Kapan Anda commit menentukan jaminan Anda
Pilihan antara auto-commit (Kafka secara berkala commit di latar) dan manual commit (Anda commit eksplisit) sebenarnya pilihan tentang semantik pengiriman (bagian 9). Urutan "proses pesan" versus "commit offset" segalanya: commit setelah memproses dan crash di antara berarti Anda akan memproses ulang (at-least-once); commit sebelum memproses dan crash berarti Anda akan melewatkan (at-most-once). Auto-commit pada timer bisa melakukan entah yang mana tergantung timing, itulah kenapa consumer yang sensitif kebenaran commit manual setelah kerja selesai (seperti kode bagian 7).
Karena setiap group punya offset committed sendiri, group baru bisa mulai dari earliest dan memutar ulang seluruh riwayat yang dipertahankan, dasar untuk mem-bootstrap consumer baru atau membangun ulang tampilan (bagian 14). Anda juga bisa eksplisit seek consumer ke offset atau timestamp mana pun untuk memproses ulang rentang. Immutabilitas log yang dipertahankan (bagian 3) yang membuat ini mungkin; antrean tidak bisa melakukannya.
Semantik Pengiriman
Pertanyaan yang harus dijawab setiap sistem pesan terdistribusi: jika hal gagal dan retry, berapa kali sebuah pesan mungkin dikirim? Ada tiga jaminan mungkin, dan Kafka bisa memberi Anda salah satunya tergantung konfigurasi, jadi Anda harus memilih secara sadar berdasarkan apa yang bisa ditoleransi pemrosesan Anda.
Exactly-once end-to-end sejati sempit: enable.idempotence Kafka menghentikan duplikat producer, dan transaksi memberi exactly-once untuk stream processing Kafka-ke-Kafka (konsumsi, transformasi, produksi, dan commit offset secara atomik). Tapi begitu consumer Anda menulis ke sistem eksternal (database, API pembayaran), exactly-once melintasi batas itu tidak gratis. Pendekatan teruji perang: pilih at-least-once dan buat pemrosesan Anda idempotent: aman menerapkan event yang sama dua kali (dedup dengan event ID, upsert alih-alih insert). Ini disiplin idempotensi yang sama dengan bab task-queues (bagian 14 di sana) dan idempotency key REST, dan itulah kenapa duplikat jadi tidak berbahaya alih-alih katastrofal.
-- The consumer may see the same event twice on retry/rebalance.
-- Make the write idempotent so a duplicate is a no-op:
-- (a) dedupe by a unique event id
INSERT INTO processed_events (event_id) VALUES ($1)
ON CONFLICT (event_id) DO NOTHING; -- second delivery inserts nothing
-- ...only do the side effect if the insert affected a row.
-- (b) or make the effect itself idempotent, upsert the end state, don't increment
INSERT INTO account_balance (account_id, balance) VALUES ($1, $2)
ON CONFLICT (account_id) DO UPDATE SET balance = EXCLUDED.balance;
-- applying the same "balance = 100" twice lands on the same state.
Jaminan Urutan
Satu kalimat untuk dihafal: Kafka menjamin urutan dalam partisi, dan tidak di tempat lain. Record di partisi P dikirim ke consumer dalam urutan offset persis saat ditulis. Lintas partisi topic yang sama, tidak ada urutan global, record saling bersisip arbitrer. Ini bukan keterbatasan yang harus dikerjakan melainkan harga langsung paralelisme tingkat partisi (bagian 4): Anda bisa punya urutan total, atau skala horizontal, tapi tidak keduanya sekaligus untuk stream yang sama.
Pertama, key skew: jika urutan menuntut keying pada nilai kasar (mis. satu mega-tenant), semua lalu lintas tenant itu mengerucut ke satu partisi, membatasi throughput-nya berapa pun jumlah partisi yang ada (bagian 18). Kedua, retry bisa mengubah urutan: dengan producer non-idempotent dan banyak request in-flight, pesan yang di-retry bisa mendarat setelah yang belakangan. Mengaktifkan enable.idempotence (bagian 6) mengawetkan urutan per-partisi bahkan lintas retry, alasan lain ia default yang masuk akal.
Retensi & Kompaksi Log
Karena record dipertahankan setelah dibaca (bagian 2), Kafka butuh aturan kapan menghapusnya, kalau tidak log tumbuh selamanya. Ada dua kebijakan pembersihan, dan mereka menjawab kebutuhan berbeda. Retensi waktu/ukuran (default) menyimpan record selama jendela, katakanlah 7 hari atau 50 GB per partisi, lalu menghapus yang tertua. Kompaksi log lebih cerdas: ia menyimpan record terbaru untuk tiap key dan mengumpulkan versi lama key itu, mengubah log menjadi snapshot yang terus diperbarui dari "nilai saat ini per key."
Topic ter-kompaksi cara Kafka memegang state saat ini selamanya tanpa pertumbuhan tak terbatas, basis untuk snapshot event-sourcing (bagian 14), state store dan KTable Kafka Streams (bagian 16), dan data config/pencarian yang bisa diputar ulang consumer baru dari nol untuk membangun ulang gambaran lengkap. Topic retensi-waktu menjawab "apa yang terjadi baru-baru ini"; topic ter-kompaksi menjawab "apa state terbaru dari semuanya." Pilih kebijakan berdasarkan pertanyaan mana yang ada untuk dijawab topic Anda.
Skema & Schema Registry
Kafka sendiri memperlakukan nilai record sebagai byte buram, ia tidak tahu atau peduli isinya. Kebebasan itu jebakan di skala: jika producer dan consumer tidak setuju pada bentuk payload, producer yang mengubah field diam-diam memecah setiap consumer hilir (bahaya contract-drift yang diperingatkan bab gRPC dan serialisasi, kini tersebar lintas banyak tim independen). Perbaikannya skema untuk tiap topic plus Schema Registry yang menyimpan skema, menetapkan ID, dan menegakkan kompatibilitas saat mereka berevolusi.
Format & kompatibilitas
Format serialisasi umum Avro (pasangan tradisional Kafka, ringkas dengan kisah evolusi skema kaya), Protobuf (IDL yang sama dengan bab gRPC, makin populer), dan JSON Schema (terbaca, lebih besar). Registry menegakkan mode kompatibilitas: biasanya kompatibilitas backward, artinya consumer baru bisa membaca data lama: Anda boleh menambah field opsional dan menghapus yang punya default, tapi tidak boleh mengganti nama atau mengubah tipe field. Ini persis disiplin perubahan aditif dari bab gRPC (bagian 04 di sana: nomor field, jangan pernah pakai ulang) dan versioning REST, ditegakkan otomatis oleh server.
Di armada microservice, skema topic API publik yang diandalkan banyak tim independen. Schema Registry mengubah "tolong jangan pecahkan payload" dari konvensi suku menjadi gerbang yang diperiksa mesin, peran yang sama dimainkan buf untuk file .proto gRPC. Perlakukan perubahan skema dengan perhatian yang sama seperti perubahan API apa pun: aditif dan kompatibel secara default.
Pola Event-Driven
Kafka substrat untuk event-driven architecture (EDA), tempat layanan berkomunikasi dengan memancarkan dan bereaksi terhadap event alih-alih saling memanggil langsung. Alih-alih service order memerintah service email, inventaris, dan analitik (kopling ketat, service order harus kenal semuanya), ia cukup mengumumkan "OrderPlaced" ke topic, dan service mana pun yang tertarik berlangganan. Producer tidak tahu, atau peduli, siapa yang mengonsumsi. Ini pembayaran dalam dari decoupling log (bagian 1): Anda menambah consumer baru tanpa menyentuh producer.
| Pola | Apa yang dibawa event | Kompromi |
|---|---|---|
| Event notification | Hanya "X terjadi" + ID | Pesan kecil; consumer harus memanggil balik untuk detail (kopling, beban) |
| Event-carried state transfer | State relevan penuh di event | Consumer mandiri (tanpa callback); pesan lebih besar, kemungkinan duplikasi |
| Choreography | Service bereaksi ke event secara otonom | Kopling longgar, mudah diperluas; alur lebih sulit dilihat end-to-end |
| Orchestration | Koordinator menggerakkan langkah-langkah (saga) | Alur eksplisit terlihat; memperkenalkan kembali koordinator sentral |
EDA menukar kejelasan panggilan langsung demi fleksibilitas. Sisi bawahnya tidak ada satu tempat yang mendeskripsikan seluruh alur kerja, "apa yang terjadi saat order ditempatkan?" kini tersebar di banyak subscriber, yang membuat debugging dan penalaran lebih sulit. Mitigasi dengan observabilitas baik (trace/correlation ID lewat header event, gagasan yang sama dengan bagian 13/bagian 15 bab lapisan dan logging), skema event jelas (bagian 12), dan memilih orkestrasi (saga eksplisit) saat alur benar-benar butuh urutan terkoordinasi yang terlihat.
Event Sourcing & CQRS
Dua pola lanjutan yang dimungkinkan log, sering dikacaukan, sering dipasangkan. Event sourcing menyimpan state sebagai urutan event yang menghasilkannya alih-alih hanya snapshot saat ini: alih-alih baris balance = 100, Anda menyimpan Deposited 60, Deposited 50, Withdrew 10, dan menurunkan saldo dengan memutar ulangnya. Log immutable terurut yang dipertahankan Kafka (bagian 3) event store alami. CQRS (Command Query Responsibility Segregation) memisahkan model tulis dari satu atau lebih model baca yang dioptimalkan untuk query.
Manfaatnya nyata: jejak audit lengkap (setiap perubahan fakta tercatat, emas untuk finansial dan kepatuhan), kemampuan membangun ulang state di titik masa lalu mana pun dengan memutar ulang ke offset, dan banyak model baca khusus yang masing-masing bisa dibangun ulang dari log yang sama (kompaksi, bagian 11, menyediakan snapshot untuk menghindari memutar ulang dari awal zaman).
Event sourcing menambah kompleksitas nyata: evolusi skema event historis sulit (Anda tidak pernah bisa mengubah masa lalu, hanya cara menafsirkannya), membangun ulang state punya biaya, dan "apa nilai saat ini?" kini butuh proyeksi alih-alih baca baris. Ia bersinar untuk domain audit-berat dan kompleks (pembayaran, buku besar, tepat fintech) tempat riwayat adalah nilainya. Untuk CRUD biasa, database normal lebih sederhana dan benar, pakai polanya tempat jejak audit dan replay benar-benar membayar kompleksitasnya, bukan sebagai arsitektur default.
Pola Outbox & Change Data Capture
Masalah halus dan penting di sistem event-driven: saat service harus memperbarui database-nya dan menerbitkan event, melakukannya sebagai dua operasi terpisah tidak aman. Jika commit DB sukses tapi publish Kafka gagal (atau sebaliknya), sistem Anda menyimpang, order ada tapi tidak ada event "OrderPlaced", atau event menyala untuk order yang di-rollback. Ini masalah dual-write, dan Anda tidak bisa memperbaikinya dengan menukar urutan dua tulis, selalu ada jendela crash di antara keduanya.
Pola outbox menyelesaikannya dengan satu transaksi lokal: tulis perubahan bisnis Anda dan baris "outbox" yang mendeskripsikan event ke dalam transaksi database yang sama, jadi mereka commit secara atomik. Proses relay terpisah lalu membaca baris outbox baru dan menerbitkannya ke Kafka (retry sampai sukses, at-least-once, karenanya consumer tetap idempotent, bagian 9). Cara modern ber-friction-rendah menjalankan relay itu Change Data Capture (CDC): tool seperti Debezium men-tail log transaksi database dan meng-stream perubahan yang di-commit langsung ke Kafka, tanpa polling aplikasi.
BEGIN;
-- 1) the actual business change
INSERT INTO orders (id, customer_id, amount, status)
VALUES ('order-42', 'cust-7', 1999, 'PLACED');
-- 2) the event to publish, written in the SAME transaction -> atomic with (1)
INSERT INTO outbox (id, aggregate, event_type, payload, created_at)
VALUES (gen_random_uuid(), 'order-42', 'OrderPlaced',
'{"id":"order-42","amount":1999}', now());
COMMIT; -- both succeed together, or neither does, no dual-write gap
-- A relay (or Debezium CDC on the transaction log) then reads new `outbox`
-- rows and produces them to Kafka, marking each published. Retries are safe
-- because downstream consumers are idempotent (sec 9).
Kapan pun sebuah tulis harus secara atomik menghasilkan event, umum di pembayaran dan sistem order, raih outbox (sering via Debezium CDC). Ia mengubah masalah transaksi terdistribusi yang mustahil menjadi transaksi lokal biasa plus relay idempotent yang andal. Ini salah satu pola paling berharga praktis di seluruh toolkit event-driven.
Stream Processing
Sejauh ini consumer membaca event satu per satu. Stream processing disiplin menghitung secara berkelanjutan di atas stream, memfilter, mentransformasi, mengagregasi, meng-join, menghasilkan stream turunan baru. Alih-alih "baca event, lakukan satu hal," Anda mengekspresikan query berdiri seperti "hitungan bergulir 5 menit dari pembayaran gagal per merchant" yang diperbarui selamanya saat event tiba. Library Kafka untuk ini Kafka Streams (JVM); ksqlDB menawarkan lapisan mirip SQL, dan Apache Flink kelas berat untuk job besar kompleks.
| Konsep | Makna |
|---|---|
| KStream | Stream tak terbatas dari event independen (tampilan append log), setiap record penting. |
| KTable | Changelog yang ditafsirkan sebagai state saat ini per key, tampilan topic ter-kompaksi (bagian 11); nilai terbaru menang. |
| Operasi ber-state | Agregasi/join yang mengingat lintas event, didukung state store lokal (sendiri topic Kafka ter-kompaksi untuk toleransi kegagalan). |
| Windowing | Mengelompokkan event berdasarkan waktu (tumbling, hopping, session) untuk mengagregasi "per 5 menit," dll. |
| Event vs processing time | Kapan event terjadi vs kapan Anda memproses-nya, keduanya berbeda, dan data terlambat/tak-terurut harus ditangani. |
Gagasan inti yang elegan: stream dan table dua tampilan dari data yang sama. Stream perubahan yang diagregasi per key memberi table (state saat ini); changelog table adalah stream pembaruan. Ini persis perbedaan retensi-vs-kompaksi (bagian 11) dinaikkan ke model pemrograman, dan itulah kenapa topic ter-kompaksi adalah table dan topic normal adalah stream. Kebanyakan engineer backend mengonsumsi stream dengan consumer polos (bagian 7); raih framework stream-processing saat Anda butuh agregasi ber-state, join, atau windowing yang menyakitkan jika diracik tangan.
Kafka vs Antrean Tradisional
Kembali ke perbedaan bagian 2 dengan konteks cukup untuk memutuskan dengan baik. Ini pertanyaan yang mengirim orang ke bab task-queues dan kembali: apakah saya mau Kafka atau message queue (RabbitMQ, SQS, dunia Celery/Sidekiq)? Mereka cukup tumpang tindih untuk membingungkan dan cukup berbeda untuk berpengaruh.
| Dimensi | Kafka (log) | RabbitMQ / SQS (antrean) |
|---|---|---|
| Model inti | Log event tahan lama, bisa diputar ulang | Antrean kerja sementara |
| Setelah konsumsi | Dipertahankan; bisa diputar ulang group mana pun | Dihapus setelah di-ack |
| Fan-out ke banyak sistem | Natif, tiap group membaca semua | Butuh konfig exchange/fan-out; bukan default |
| Throughput | Sangat tinggi (jutaan/detik) | Tinggi, tapi biasanya lebih rendah |
| Urutan | Kuat per partisi | Lemah; mode FIFO terbatas/lambat |
| Routing/prioritas/TTL per pesan | Terbatas, bukan modelnya | Kaya (routing key, prioritas, delay, DLQ) |
| Replay / time-travel | Ya, seek ke offset mana pun | Tidak |
| Terbaik untuk | Event streaming, banyak subscriber, replay, analitik, event sourcing | Offloading tugas, job mirip RPC, routing kompleks, kontrol per-pesan |
Rengkuh Kafka saat event adalah fakta yang dipedulikan banyak sistem, saat Anda butuh throughput tinggi, urutan, atau kemampuan memutar ulang riwayat, tulang punggung streaming. Rengkuh antrean (bab 10) saat Anda menyerahkan unit kerja diskret untuk dilakukan sekali, terutama dengan routing per-pesan, prioritas, delay, atau penanganan dead-letter. Mereka saling melengkapi: sangat banyak sistem nyata menjalankan Kafka sebagai tulang punggung event dan task queue untuk job latar. Jangan memaksa semantik task-queue ke Kafka (TTL per-pesan, prioritas, ack selektif), itu berenang melawan desainnya.
Jebakan Consumer
Kebanyakan rasa sakit Kafka produksi muncul di sisi consumer. Ini mode kegagalan yang menggigit tim nyata, layak dikenali sebelum mereka page Anda jam 2 pagi.
| Jebakan | Apa yang terjadi | Mitigasi |
|---|---|---|
| Badai rebalance | Rebalance group yang sering (pemrosesan lambat memicu poll timeout, anggota dinyatakan mati) menghentikan konsumsi berulang | Jaga kerja per-poll terbatas, setel max.poll.records / max.poll.interval.ms; geser kerja lambat; pakai cooperative/incremental rebalancing |
| Poison pill | Satu pesan yang tidak bisa diproses (skema buruk, data buruk) membuat consumer crash & retry selamanya, partisi macet | Tangkap error per-record; rutekan record buruk ke dead-letter topic dan lanjutkan |
| Consumer lag | Producer melampaui consumer; lag tumbuh tanpa batas; data basi | Tambah partisi + consumer (bagian 7), percepat pemrosesan, batch; alert pada lag (bagian 19) |
| Skew partisi / key | Key panas (satu tenant raksasa) membebani partisi tunggalnya sementara yang lain menganggur (bagian 10) | Pilih key kardinalitas lebih tinggi, atau key komposit; isolasi tenant panas |
| Pemrosesan lambat memblokir urutan | Untuk menjaga urutan Anda memproses partisi serial, jadi satu record lambat menunda sisanya | Paralelkan lintas partisi, bukan di dalam; pertimbangkan pola paralelisme per-key |
| Reprocessing pada celah commit | Crash antara pemrosesan dan commit offset mengirim ulang record | Diharapkan di bawah at-least-once, buat pemrosesan idempotent (bagian 9) |
Satu pesan salah bentuk tidak boleh pernah mampu menghentikan seluruh partisi tanpa batas, itu poison pill, dan penyebab pemadaman umum. Bungkus pemrosesan per-record dalam penanganan error: pada kegagalan persisten, terbitkan record yang bermasalah (plus error dan metadata) ke dead-letter topic, commit melewatinya, dan jaga stream mengalir. Anda menyelidiki dead-letter topic belakangan alih-alih membiarkan satu record buruk memblokir semua orang, disiplin DLQ yang sama yang diterapkan bab task-queues pada job gagal.
Operasi & Monitoring
Menjalankan Kafka dengan baik bermuara pada memantau beberapa sinyal dan mengukur beberapa hal dengan benar. Anda tidak perlu menghafal setiap metrik, tapi harus tahu yang memprediksi masalah.
| Pantau | Kenapa penting |
|---|---|
| Consumer lag | Metrik kesehatan utama (bagian 8): lag yang tumbuh berarti consumer tidak bisa mengejar, data makin basi. Alert padanya. |
| Partisi under-replicated | Replika jatuh keluar dari ISR (bagian 5), daya tahan berisiko; broker mungkin kesulitan. |
| Disk & throughput broker | Retensi (bagian 11) berarti topic mengonsumsi disk nyata; disk atau jaringan jenuh men-throttle klaster. |
| Frekuensi rebalance | Rebalance sering (bagian 18) menandakan consumer tidak stabil dan menyebabkan keheningan. |
| Latensi & error request | Latensi dan tingkat error produce/fetch memunculkan masalah broker atau klien lebih awal. |
Keputusan sizing yang benar-benar Anda buat
- Jumlah partisi: men-set paralelisme consumer maksimum dan throughput (bagian 4, bagian 7). Perkirakan dari target throughput dan jumlah consumer puncak; condong sedikit tinggi, tapi tidak liar (tiap partisi punya overhead, dan ribuan per broker menyakitkan). Sulit diubah belakangan, jadi pikirkan ke depan (bagian 20).
- Faktor replikasi: biasanya 3 di produksi: mentoleransi satu kehilangan broker dengan ruang cadangan. Pasangkan dengan
min.insync.replicas=2danacks=all(bagian 5). - Retensi: berapa lama event hidup (dan karenanya pemakaian disk dan jendela replay). Set per topic seberapa jauh ke belakang consumer mungkin perlu membaca.
Observabilitas standar: ekspor metrik JMX broker dan klien ke Prometheus + Grafana (stack yang sama dipakai bab task-queues dan logging), dan awasi consumer lag secara khusus dengan lag exporter atau UI seperti Kafka UI / Conduktor / AKHQ. kafka-consumer-groups.sh --describe memberi bacaan CLI cepat lag group per partisi saat men-debug langsung.
Mendesain dengan Kafka
Menarik manual ke penilaian desain. Kafka tool kuat dan tajam; dipakai di tempatnya transformatif, dipakai sebagai message bus generik liabilitas. Beberapa prinsip memisahkan desain Kafka yang baik dari yang menyakitkan.
Prinsip desain
- Modelkan topic di sekitar event/fakta, bukan perintah atau RPC. Topic adalah stream dari "hal yang terjadi" (
PaymentSettled), dikonsumsi siapa pun yang peduli, bukan cara menyuruh service spesifik melakukan sesuatu. Jika Anda mendapati ingin request/response, Anda ingin gRPC/REST, bukan Kafka. - Pilih partition key berdasarkan batas urutan Anda. Key berdasarkan entitas yang event-nya harus tetap terurut (akun, order, user), tapi pada granularitas yang tetap menyebar beban, untuk menghindari partisi panas (bagian 10, bagian 18). Key keputusan per-topic paling konsekuensial.
- Asumsikan at-least-once; bangun consumer idempotent. Jangan mengejar exactly-once end-to-end lintas sistem eksternal (bagian 9). Dedup dengan event ID atau pakai upsert supaya duplikat tidak berbahaya, lebih murah dan lebih kokoh.
- Kelola skema sejak hari pertama. Pakai Schema Registry dengan kompatibilitas backward (bagian 12) supaya tim independen bisa berevolusi dengan aman. Skema topic kontrak.
- Pakai outbox untuk tulis DB-dan-event. Jangan pernah dual-write; buat event atomik dengan data dan relay (bagian 15).
- Ukur partisi untuk puncak di depan. Repartisi memecah urutan key (bagian 4), jadi rencanakan pertumbuhan alih-alih bereaksi padanya.
Rengkuh Kafka saat Anda punya stream event yang dikonsumsi banyak sistem, butuh throughput tinggi atau replay, sedang melakukan event-driven architecture, event sourcing, pipeline analitik, atau ingestion log/metrik. Jangan merengkuhnya untuk request/response sederhana (pakai gRPC/REST, bab gRPC), untuk job latar diskret dengan routing atau prioritas per-pesan (pakai task queue, bab 10), atau sebagai satu-satunya datastore untuk pencarian titik (pakai database). Mencocokkan tool ke bentuk masalah seluruh keterampilannya.
Crib-Sheet
Seluruh manual dipadatkan menjadi yang Anda raih saat tertekan.
| Konsep | Satu baris |
|---|---|
| Kafka | Commit log append-only terdistribusi, tahan lama, dari event yang dibaca banyak consumer di offset mereka sendiri. |
| Antrean vs log | Antrean: dikonsumsi sekali & dihapus. Log: dipertahankan, bisa diputar ulang, tiap group membaca semua. |
| Commit log | Append-only, immutable, ber-alamat offset, I/O sekuensial, basis dari segalanya. |
| Topic | Stream bernama, dipecah menjadi partisi. |
| Partisi | Unit paralelisme, urutan, dan penyimpanan. Key sama -> partisi sama. |
| Broker / replikasi | Klaster server; tiap partisi punya replika leader + follower (ISR). |
| acks / ISR | acks=all + min.insync.replicas=2 = tanpa kehilangan data pada satu kegagalan broker. |
| Producer | Menambahkan record ber-key; key menentukan partisi; idempotence menghindari dup saat retry. |
| Consumer group | Membagi partisi di antara anggota. Group sama = berbagi kerja; group berbeda = broadcast. |
| Batas paralelisme | Consumer berguna maksimum per group = jumlah partisi. |
| Offset / lag | Posisi Anda per partisi; lag = ekor - posisi = metrik kesehatan kunci. |
| Waktu commit | Commit setelah memproses = at-least-once; sebelum = at-most-once. |
| Pengiriman | At-most / at-least / exactly-once. Pilih at-least-once + consumer idempotent. |
| Urutan | Dijamin hanya dalam partisi. Key berdasarkan batas urutan Anda. |
| Retensi | Hapus berdasarkan waktu/ukuran, atau kompak untuk menyimpan nilai terbaru per key. |
| Kompaksi | Terbaru-per-key -> snapshot state saat ini yang tahan lama dan bisa diputar ulang (table). |
| Schema Registry | Skema ber-versi (Avro/Protobuf) dengan kompatibilitas backward yang ditegakkan. |
| EDA | Pancarkan event; consumer bereaksi independen, kopling longgar, mudah diperluas. |
| Event sourcing / CQRS | Log sumber kebenaran; model baca proyeksi yang bisa dibangun ulang. |
| Outbox / CDC | Tulis data + event dalam satu txn DB; relay (Debezium) ke Kafka, tanpa celah dual-write. |
| Stream processing | Komputasi berkelanjutan (filter/agregasi/join/window); dualitas KStream vs KTable. |
| Jebakan | Badai rebalance, poison pill (pakai DLQ), lag, key skew. |
| vs antrean | Kafka = event streaming/replay/throughput; antrean = kerja + routing/prioritas. Pakai keduanya. |
Seluruh topik dalam satu tarikan napas: Kafka commit log append-only terdistribusi, tahan lama (bagian 1-3), tidak seperti antrean, event dipertahankan dan setiap consumer group membaca semuanya dan bisa memutar ulang (bagian 2). Topic dipecah menjadi partisi, unit paralelisme, urutan, dan penyimpanan (bagian 4), tersebar lintas broker dan direplikasi untuk daya tahan via leader, follower, dan ISR (bagian 5). Producer menambahkan record ber-key (bagian 6); consumer group membagi partisi untuk scaling keluar, dengan paralelisme dibatasi jumlah partisi (bagian 7). Setiap consumer melacak offsetnya, dan lag ke ekor sinyal kesehatan kunci (bagian 8); kapan Anda commit menentukan semantik pengiriman Anda, dan pilihan pragmatis at-least-once dengan consumer idempotent (bagian 9). Urutan hanya berlaku dalam partisi, jadi Anda key berdasarkan batas urutan dan waspadai skew (bagian 10); retensi menghapus berdasarkan usia sementara kompaksi menyimpan nilai terbaru per key (bagian 11). Schema Registry mengatur payload lintas tim (bagian 12); log menopang event-driven architecture (bagian 13), event sourcing dan CQRS (bagian 14), dan pola outbox/CDC untuk tulis DB-dan-event atomik (bagian 15). Stream processing menghitung berkelanjutan di atasnya (bagian 16); pilih Kafka di atas antrean untuk streaming, replay, dan throughput (bagian 17), jaga dari badai rebalance, poison pill, lag, dan skew (bagian 18), pantau lag dan ISR (bagian 19), dan rancang topic di sekitar event yang dipilih key dengan masuk akal, dengan consumer idempotent dan skema terkelola (bagian 20).
Berpijak pada dokumentasi Apache Kafka & Confluent / literatur log/stream-table (Kreps) / Debezium untuk CDC / contoh Go 1.22+ (confluent-kafka-go) / Python 3.11+ (confluent-kafka).