Kafka for Data Pipelines — Data Engineering

Apache Kafka untuk Data Pipelines Apache Kafka adalah distributed event streaming platform yang menjadi tulang punggung data pipeline modern. Kafka berfungsi…

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-loader dijalankan 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

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

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()

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

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

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

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.

Yang akan kamu pelajari