Pertahankan urutan yang diperlukan
Partisi, consumer group, offset, rebalance, dan replay. Urutan global itu mahal, jadi tentukan urutan mana yang benar-benar dibutuhkan.
Pertanyaan
Urutan pesan mana yang benar-benar harus dijaga, dan berapa paralelisme yang kita korbankan untuk itu?
Setelah baca ini, kamu bisa memilih partition key untuk sebuah alur, menjelaskan apa yang terjadi pada offset saat consumer crash atau rebalance, dan merencanakan replay tanpa merusak keadaan turunan.
Prasyarat dan contoh
Kamu sudah paham event stream sebagai log berurutan (M10.01), perilaku ack dan pengiriman ulang (M10.02), serta batas jumlah consumer (M10.03). Replikasi dan waktu logis dari M08 membantu.
Sebuah akun mengalami tiga event: saldo_masuk 100.000, saldo_keluar 80.000, saldo_keluar 50.000. Kalau dua event terakhir diproses terbalik, pembayaran 50.000 diperiksa dengan saldo
100.000 dan lolos, sedangkan urutan benar menolaknya. Tapi nggak ada gunanya menjaga urutan antara
event akun A dan akun B. Urutan yang dibutuhkan adalah per akun, bukan global.
Mekanisme
Log berpartisi. Satu topik dibagi menjadi N partisi. Tiap partisi adalah log berurutan sendiri dengan nomor posisi (offset) yang naik. Urutan dijamin di dalam satu partisi, bukan antar partisi.
topik "saldo", 3 partisi
P0: [a1 a4 a7 ...] key hash(akun) mod 3 = 0
P1: [b2 b5 ...] key hash(akun) mod 3 = 1
P2: [c3 c6 ...]
Partition key. Producer memilih partisi dari key, misalnya hash(akun_id) mod N. Semua event
satu akun masuk partisi yang sama, jadi urutannya terjaga, sementara akun berbeda diproses
paralel. Memilih key = memilih unit urutan. Key yang terlalu kasar (misalnya negara) membuat
paralelisme kecil dan partisi miring. Key yang terlalu halus (ID event acak) membuang urutan.
Consumer group. Beberapa consumer yang berbagi satu grup membagi partisi: satu partisi dimiliki satu consumer dalam grup pada satu waktu. Konsekuensinya: paralelisme maksimum grup sama dengan jumlah partisi. Dengan 12 partisi, consumer ke-13 menganggur. Grup berbeda mendapat salinan lengkap dan berjalan masing-masing (penagihan dan analitik membaca topik yang sama).
Offset dan commit. Consumer mencatat offset terakhir yang selesai diproses (commit). Setelah restart, ia lanjut dari offset itu. Ini versi stream dari ack, dengan semua trade-off yang sama:
- Commit sebelum memproses: crash berarti event hilang.
- Commit setelah memproses: crash berarti event terakhir diproses ulang. Butuh consumer idempoten (M10.02).
Karena commit dilakukan berkala, jendela pemrosesan ulang bisa mencakup puluhan event, bukan satu.
Rebalance. Saat consumer bergabung, keluar, atau dianggap mati (heartbeat terlambat), partisi dibagi ulang. Selama itu, partisi yang berpindah berhenti diproses sebentar, dan consumer baru lanjut dari offset terakhir yang di-commit. Dua bahaya: consumer lama yang lambat (bukan mati) masih memproses dan menulis efek setelah partisinya diambil orang lain, dan commit offset yang tertunda hilang sehingga event diproses ulang. Pakai idempotensi dan, untuk penulisan penting, fencing (nomor generasi) supaya penulis lama ditolak.
Replay. Karena log disimpan, consumer bisa mundur ke offset lebih awal atau ke waktu tertentu. Dipakai untuk membangun ulang tampilan turunan atau memperbaiki bug. Replay aman jika:
- Pemrosesan idempoten, atau tampilan turunan dihapus lalu dibangun dari awal.
- Efek keluar (email, tagihan) dimatikan atau dideduplikasi selama replay.
- Retensi log cukup. Kalau retensi 7 hari dan bug ditemukan hari ke-9, data pemicu sudah hilang.
- Kapasitas diperhitungkan: 7 hari data pada 5.000 event/detik berisi 3,02 miliar event. Kalau consumer replay hanya punya sisa 5.000 event/detik di atas beban normal, replay butuh 7 hari penuh. Pakai grup terpisah dengan kapasitas khusus.
Saat rancangan ini gagal
- Key miring. Satu merchant besar menyumbang 40% event. Partisinya jenuh, partisi lain
menganggur. Menambah consumer tidak membantu. Pilihan: pecah key (
merchant#bucket) dan terima bahwa urutan hanya terjaga per bucket. - Menambah partisi mengubah pemetaan. Dari 12 ke 24 partisi,
hash mod Nberubah, jadi event baru akun A masuk partisi lain sementara event lamanya masih diproses. Urutan rusak sementara. Rencanakan jumlah partisi dari awal atau lakukan migrasi terkontrol. - Paralelisme di dalam consumer. Consumer membaca satu partisi lalu memproses event dengan thread pool. Urutan per key hilang di dalam proses. Pertahankan urutan per key di tingkat worker juga.
- Retry di tengah aliran. Event 5 gagal dan dikirim ke DLQ atau antre ulang, sementara event 6 lolos. Urutan rusak. Keputusannya: hentikan partisi sampai 5 beres (aman, tapi memblokir), atau terima urutan longgar.
- Rebalance badai. Pemrosesan satu pesan 40 detik, batas heartbeat 30 detik. Consumer dianggap mati, rebalance, pesan diproses ganda, dan ini berulang.
- Replay menimpa data baru. Replay menulis nilai lama atas nilai lebih baru. Sertakan versi atau offset dalam tulisan dan tolak penulisan yang lebih tua.
Contoh tandingan: "pakai satu partisi biar urutannya pasti". Memang urut, tapi throughput terbatas pada satu consumer, mis. 2.000 event/detik, dan satu event lambat menahan semuanya.
Latihan
Topik pesanan berisi event dibuat, dibayar, dikirim per pesanan, 8 partisi, 8 consumer
dalam satu grup, rata-rata 1.600 event/detik. Satu reseller besar menghasilkan 35% event.
- Pilih partition key dan jelaskan apa yang urutannya dijamin.
- Hitung beban partisi terbesar dan apa masalahnya.
- Consumer 3 crash setelah memproses 40 event sejak commit terakhir. Apa yang terjadi?
Lihat pembahasan
- Key
pesanan_id. Semua event satu pesanan masuk satu partisi, jadidibuatselalu sebelumdibayar. Urutan antar pesanan tidak dijamin dan memang tidak dibutuhkan. - Kalau key
reseller_id, partisi reseller besar menerima 35% × 1.600 = 560 event/detik, jauh di atas rata-rata 200/detik per partisi (1.600 / 8). Dengan keypesanan_id, beban relatif merata karena pesanan reseller tersebar. Itu alasan memilihpesanan_id. Kalau urutan per reseller dibutuhkan, itu syarat berbeda yang harus dibenarkan. - Rebalance: partisi 3 dipindah ke consumer lain, yang lanjut dari offset commit terakhir. 40
event diproses ulang. Aman hanya jika consumer idempoten, misalnya mengabaikan
dibayaruntuk pesanan yang sudah berstatusdibayar. Selama rebalance partisi 3 tidak diproses beberapa detik, jadi lag-nya naik sementara.
Terkait
- M10.02 Tentukan perilaku pengiriman menjelaskan ack dan idempotensi yang juga berlaku untuk commit offset.
- M10.05 Publikasikan event dengan andal membahas bagaimana event masuk log tanpa celah dari database.
- M10.03 Kendalikan beban consumer memakai lag per partisi sebagai sinyal.
- Partisi data umum dibahas di M07, dan klien real-time yang tersambung ulang di M13.
Catatan
- Asumsi: broker bergaya log berpartisi. Istilah dan perilaku rebalance berbeda antar produk.
- Hitungan replay: 7 × 86.400 × 5.000 = 3,024 × 10^9 event, dibagi sisa kapasitas 5.000/detik.
- Yang nggak dibahas: transaksi lintas partisi, kompaksi log, dan urutan lintas wilayah.
- Terakhir ditinjau: 26 Oktober 2026.