Pipeline batch kita berjalan di lima worker yang dipicu jadwal yang sama pada pukul 06:00. Beberapa menit kemudian, laporan harian tercatat ganda karena dua worker mengambil partisi pesanan yang sama. Masalah ini tidak bisa diselesaikan dengan mutex biasa: kedua worker hidup di mesin berbeda tanpa memori bersama. Distributed coordination adalah disiplin membuat sekumpulan node saling sepakat tentang siapa mengerjakan apa dan siapa yang boleh menulis. Bab ini membahas dua pola inti — leader election dan distributed lock — evolusinya di Kafka, lalu strategi meminimalkan koordinasi.
Masalah Koordinasi Lintas Node #
Di dalam satu proses, koordinasi hampir gratis: mutex melindungi variabel bersama, antrian internal membagi tugas. Di klaster, setiap node hanya melihat memori lokal; satu-satunya jalur komunikasi adalah jaringan yang bisa lambat atau terputus — karakter dasar Chapter Dasar Distributed Systems. Dari situ muncul tiga kebutuhan klasik:
- Mutual exclusion: satu sumber daya — berkas output, baris tabel, partition topic — hanya boleh dipegang satu worker.
- Kesepakatan urutan: penulisan metadata harus punya satu urutan yang diakui semua node.
- Penunjukan peran: klaster perlu tahu coordinator saat ini dan penggantinya bila coordinator berhenti bekerja.
Tanpa koordinasi muncul pemrosesan ganda dan penulisan saling menimpa. Namun koordinasi juga mahal: setiap keputusan menuntut komunikasi antar node dan menciptakan titik yang diawasi saat node gagal — tema Chapter Fault Tolerance.
Leader Election: Menunjuk Satu Koordinator #
Klaster besar menunjuk satu node sebagai leader: menetapkan pemilik partition, menerima penulisan metadata, dan memimpin pemulihan. Leader election adalah mekanisme memilih node tersebut otomatis — dijalankan saat klaster mulai berjalan dan diulang tiap kali leader terputus. Keluarga algoritma Raft memilih lewat voting mayoritas dengan penanda periode (term) agar dua kandidat tidak sama-sama mengaku menang — fondasi yang sama dengan replikasi log pada Chapter Replication.
Distributed Lock dengan Redis dan etcd #
Distributed lock memindahkan mutex ke lintas mesin: satu worker mengunci sumber daya, worker lain menunggu, kunci lepas setelah selesai. Setiap kunci dibundel dengan TTL (time to live) agar tidak menggantung selamanya ketika pemegangnya berhenti mendadak. Di Redis, akuisisi atomik lewat perintah SET dengan opsi NX (gagal bila kunci sudah ada) dan PX (TTL dalam milidetik). Di etcd, worker membuat lease berbatas waktu lalu menulis kunci dengan compare-and-swap.
Dari ZooKeeper ke KRaft #
Kafka lama menyelesaikan koordinasi dengan ZooKeeper: layanan terpisah yang menyimpan metadata klaster dan memilih broker controller. Desain ini berfungsi, tetapi operator menjalankan dua sistem sekaligus dan metadata tidak menskalakan seiring jumlah partition. Kafka kemudian menggantinya dengan KRaft — implementasi Raft milik Kafka — sehingga metadata pindah ke internal topic __cluster_metadata dan pemilihan controller berjalan tanpa layanan luar. Sejak Kafka 4.0, KRaft menjadi satu-satunya mode — detailnya di dokumentasi Apache Kafka
.
Contoh Mini: Leader Election dengan Lock File dan TTL #
Untuk merasakan logikanya, kita bangun leader election mini: satu berkas lock berisi identitas pemimpin dan waktu heartbeat terakhir. Kandidat menang bila berkas kosong atau usianya melewati TTL; pemimpin yang masih hidup rutin memperbarui heartbeat. Waktu disuntik lewat parameter sekarang agar skenario gagal-lalu-ambil-alih berjalan deterministik.
"""Simulasi leader election mini: lock file berisi pemimpin + waktu heartbeat."""
import json
from pathlib import Path
BERKAS_LOCK = Path("leader.lock")
TTL = 10.0 # detik: batas usia lock sebelum boleh diambil alih
def baca_lock():
"""Kembalikan isi lock file, atau None bila hilang atau rusak."""
try:
return json.loads(BERKAS_LOCK.read_text())
except (FileNotFoundError, json.JSONDecodeError):
return None
def kandidat_menang(worker, sekarang):
"""Menang bila lock kosong atau usianya melewati TTL."""
lock = baca_lock()
if lock is None or sekarang - lock["heartbeat"] > TTL:
BERKAS_LOCK.write_text(json.dumps(
{"pemimpin": worker, "heartbeat": sekarang}))
return True
return False
def heartbeat(worker, sekarang):
"""Pemimpin aktif memperbarui usia lock agar tidak diambil alih."""
lock = baca_lock()
if lock and lock["pemimpin"] == worker:
BERKAS_LOCK.write_text(json.dumps(
{"pemimpin": worker, "heartbeat": sekarang}))
return True
return False
BERKAS_LOCK.unlink(missing_ok=True) # mulai dari kondisi bersih
skenario = [
(0.0, "worker-a", "kandidat"),
(4.0, "worker-b", "kandidat"),
(9.0, "worker-a", "heartbeat"),
(14.0, "worker-c", "kandidat"),
(22.0, "worker-b", "kandidat"),
(23.0, "worker-a", "kandidat"),
]
for sekarang, worker, aksi in skenario:
if aksi == "kandidat":
if kandidat_menang(worker, sekarang):
print(f"t={sekarang:4.0f}s {worker} kandidat -> MENANG, jadi pemimpin")
else:
lock = baca_lock()
usia = sekarang - lock["heartbeat"]
print(f"t={sekarang:4.0f}s {worker} kandidat -> kalah "
f"(pemimpin={lock['pemimpin']}, usia lock={usia:.0f}s)")
else:
sukses = heartbeat(worker, sekarang)
hasil = "lock diperbarui" if sukses else "bukan pemimpin"
print(f"t={sekarang:4.0f}s {worker} heartbeat -> {hasil}")Output yang Dihasilkan:
t= 0s worker-a kandidat -> MENANG, jadi pemimpin
t= 4s worker-b kandidat -> kalah (pemimpin=worker-a, usia lock=4s)
t= 9s worker-a heartbeat -> lock diperbarui
t= 14s worker-c kandidat -> kalah (pemimpin=worker-a, usia lock=5s)
t= 22s worker-b kandidat -> MENANG, jadi pemimpin
t= 23s worker-a kandidat -> kalah (pemimpin=worker-b, usia lock=1s)Mari kita telaah baris-baris kode di atas:
- Baris 5-6 (
BERKAS_LOCKdanTTL): berkas lock dan ambang usia 10 detik ditetapkan sebagai konstanta — satu-satunya “kesepakatan” yang dibaca semua kandidat. - Baris 9-14 (
baca_lock): fungsi membaca dan mengurai JSON dari berkas lock; berkas yang hilang atau rusak dianggap lock kosong, bukan galat. - Baris 17-24 (
kandidat_menang): inti pemilihan — lock kosong atau berumur lebih dari TTL berarti tidak ada pemimpin sah, jadi kandidat menulis dirinya sebagai pemimpin baru. - Baris 27-33 (
heartbeat): hanya pemimpin sah yang boleh memperbarui isi lock, sehingga worker lain tidak bisa menyegarkan umur kepemimpinan yang bukan miliknya. - Baris 37-45: skenario enam peristiwa menggambarkan worker-a menang, dua kandidat kalah, lalu worker-a berhenti mengirim heartbeat setelah t=9.
- Baris 46-58: setiap peristiwa dijalankan dan hasilnya dicetak lengkap dengan usia lock saat itu.
Peristiwa kunci di t=22: heartbeat terakhir worker-a tertulis pada t=9, usia lock sudah 13 detik — melewati TTL 10 detik — dan worker-b sah mengambil alih. Worker-a yang kembali pada t=23 kalah: lock milik worker-b masih segar. Klaster pun menyembuhkan diri tanpa campur tangan operator.
Meminimalkan Koordinasi #
Koordinasi selalu menjadi titik pembotolan dan sumber mode kegagalan baru, sehingga desain terbaik justru menghindarinya. Tiga pola efektif:
- Kepemilikan statis: bagi rentang partisi di awal — worker 1 memproses partisi 0-3, worker 2 partisi 4-7 — tanpa perundingan saat pipeline berjalan.
- Kepemilikan per partition: setiap partition punya pemimpin sendiri seperti pada Chapter Replication; kegagalan satu pemimpin hanya mengganggu satu partition.
- Choreography: setiap stage bereaksi pada event stage sebelumnya tanpa coordinator pusat — pola Chapter Event-Driven Workflows.
Pertanyaan “siapa yang mengerjakan” kini terjawab. Pertanyaan berikutnya: di mana data diletakkan agar pekerjaan efisien. Chapter Data Locality membahas prinsip memindahkan komputasi ke data — bukan sebaliknya — dan alasan bandwidth jaringan menjadikannya keharusan di big data.