Partitioning membuat kapasitas tumbuh, tetapi setiap shard kini berdiri sendiri: saat node pemilik shard berhenti, potongan data yang ditampungnya ikut tidak tersedia dan risiko kehilangan permanen meningkat. Replication menjawabnya dengan menyimpan salinan data yang sama di beberapa node, sehingga satu node berhenti hanya mengurangi kapasitas, bukan meniadakan akses. Model yang paling banyak dipakai platform data adalah leader-follower: satu leader menerima seluruh tulisan, follower menjaga salinannya. Chapter ini mensimulasikan model tersebut dan membedah dua konsekuensi yang wajib dipahami: replication lag pada bacaan dan risiko kehilangan tulisan saat failover.
Mengapa Menyalin Data #
Replication menambah jumlah salinan data, bukan jumlah data berbeda. Alasan utamanya:
- Ketersediaan: baca tetap dilayani replika lain saat satu replika berhenti.
- Skala baca: beban pembacaan dibagi ke banyak replika — nyaris semua workload analitik bersifat read-heavy.
- Latensi: replika ditempatkan dekat pengguna tiap wilayah sehingga jarak tempuh permintaan turun.
- Daya tahan: salinan di node berbeda melindungi data dari kegagalan disk tunggal.
Model Leader-Follower #
Pada model single-leader, seluruh tulisan diarahkan ke satu node bernama leader. Leader mencatat setiap perubahan ke replication log bernomor urut (LSN), lalu follower menerapkan entri log yang sama dengan urutan yang sama sehingga salinan tetap identik. Pembacaan boleh dilayani leader maupun follower — dan di sinilah sumber semua konsekuensi menariknya.
Saat mengonfirmasi tulisan ke klien, ada dua pilihan yang menentukan karakter sistem. Replication synchronous menunggu follower ikut menulis sebelum menjawab, sedangkan asynchronous langsung menjawab dan membiarkan follower mengejar di belakang.
| Aspek | Synchronous | Asynchronous |
|---|---|---|
| Konfirmasi tulis ke klien | Setelah follower juga menulis | Sebelum follower selesai menyalin |
| Risiko tulisan hilang saat failover | Nyaris tidak ada | Tulisan yang belum tersalin hilang |
| Latensi tulis | Tinggi: menunggu follower terjauh | Rendah |
| Ketahanan saat jaringan lambat | Tulis gagal sampai follower sehat | Tulis tetap berjalan |
Simulasi: Lag, Baca Basi, dan Failover #
Simulasi berikut memakai dictionary sebagai tabel dan list sebagai replication log. Skenarionya tiga tahap: tiga tulisan masuk ke leader, follower hanya sempat menyalin dua entri (menciptakan lag), lalu leader berhenti dan follower dipromosikan menjadi leader baru.
"""Simulasi replication leader-follower: lag follower, baca basi, dan failover."""
class Leader:
"""Menerima seluruh tulisan dan mencatatnya ke replication log."""
def __init__(self, nama):
self.nama = nama
self.aktif = True
self.tabel = {}
self.log = [] # tiap entri: (lsn, kunci, nilai)
def tulis(self, kunci, nilai):
lsn = len(self.log) + 1
self.log.append((lsn, kunci, nilai))
self.tabel[kunci] = nilai
return lsn
def baca(self, kunci):
return self.tabel.get(kunci, "<tidak ada>")
def salin_log(self, mulai, jumlah):
"""Follower menarik entri log; leader yang berhenti tidak bisa melayani."""
if not self.aktif:
raise ConnectionError(f"{self.nama} tidak merespons")
return self.log[mulai:mulai + jumlah]
class Follower:
"""Mereplikasi log leader secara asinkron, sebagian entri per tarikan."""
def __init__(self, nama, leader):
self.nama = nama
self.leader = leader
self.tabel = {}
self.lsn = 0 # posisi log terakhir yang sudah diterapkan
def tarik(self, maksimal):
for lsn, kunci, nilai in self.leader.salin_log(self.lsn, maksimal):
self.tabel[kunci] = nilai
self.lsn = lsn
def baca(self, kunci):
return self.tabel.get(kunci, "<tidak ada>")
def promosi(self):
"""Failover: follower naik menjadi leader baru."""
self.leader.aktif = False
return LeaderBaru(self)
class LeaderBaru(Leader):
"""Leader hasil promosi; melanjutkan dari data yang sudah dimiliki follower."""
def __init__(self, follower):
super().__init__(follower.nama + "-promosi")
self.tabel = dict(follower.tabel)
self.log = [] # log baru dimulai dari data yang selamat
leader = Leader("leader-1")
follower = Follower("follower-1", leader)
print("[1] Tiga tulisan masuk ke leader-1 (sumber tunggal tulisan)")
leader.tulis("[email protected]", 3)
leader.tulis("[email protected]", 1)
leader.tulis("[email protected]", 2)
print(f" tabel leader : {leader.tabel}")
print(f" panjang log : {len(leader.log)} entri")
print("[2] Follower menarik log secara asinkron, baru 2 entri (replication lag)")
follower.tarik(maksimal=2)
print(f" tabel follower: {follower.tabel}")
print(f" follower lag : {len(leader.log) - follower.lsn} entri di belakang leader")
print(f" baca [email protected] dari leader : {leader.baca('[email protected]')}")
print(f" baca [email protected] dari follower: {follower.baca('[email protected]')}")
print("[3] Leader-1 berhenti; follower dipromosikan (failover)")
leader_baru = follower.promosi()
try:
leader.salin_log(0, 1)
except ConnectionError as galat:
print(f" tulisan ke leader lama gagal: {galat}")
print(f" tabel leader baru: {leader_baru.tabel}")
print(f" tulisan terakhir ([email protected]=2) tidak ikut: belum sempat direplikasi")
leader_baru.tulis("[email protected]", 4)
print(f" tulisan baru diterima leader baru: {leader_baru.tabel}")Output yang Dihasilkan:
[1] Tiga tulisan masuk ke leader-1 (sumber tunggal tulisan)
tabel leader : {'[email protected]': 3, '[email protected]': 1, '[email protected]': 2}
panjang log : 3 entri
[2] Follower menarik log secara asinkron, baru 2 entri (replication lag)
tabel follower: {'[email protected]': 3, '[email protected]': 1}
follower lag : 1 entri di belakang leader
baca [email protected] dari leader : 2
baca [email protected] dari follower: <tidak ada>
[3] Leader-1 berhenti; follower dipromosikan (failover)
tulisan ke leader lama gagal: leader-1 tidak merespons
tabel leader baru: {'[email protected]': 3, '[email protected]': 1}
tulisan terakhir ([email protected]=2) tidak ikut: belum sempat direplikasi
tulisan baru diterima leader baru: {'[email protected]': 4, '[email protected]': 1}Mari kita telaah baris-baris kode di atas:
- Baris 13-17 (
tulis): setiap tulisan mendapat nomor urut LSN dari panjang log lalu dicatat — nomor urut inilah yang membuat lag bisa diukur. - Baris 22-26 (
salin_log): follower menarik potongan log mulai posisi tertentu; bila leader sudah tidak aktif, pemanggilan ini memunculkan galat koneksi. - Baris 38-41 (
tarik): follower menerapkan sebagian entri saja per tarikan dan mencatat posisi terakhirnya diself.lsn— perilaku khas replication asinkron yang mengejar di belakang. - Baris 46-58 (
promosidanLeaderBaru): failover menonaktifkan leader lama lalu mengangkat follower; leader baru hanya membawa data yang sudah berhasil disalinnya. - Baris 71-76 (lag dan baca basi): follower tertinggal satu entri, sehingga kunci yang baru ditulis leader terbaca
<tidak ada>dari follower — pembaca mendapat jawaban basi tanpa sadar. - Baris 78-87 (failover): leader lama menolak tulisan, leader baru melayani tanpa
[email protected]— tulisan yang belum tersalin tidak ikut pindah.
Membaca Hasil: Trade-off di Balik Salinan #
Tahap dua memperlihatkan replication lag: follower tertinggal satu entri sehingga pembacaan dari follower mengembalikan keadaan lama. Lag beberapa detik pada sistem nyata adalah hal biasa, dan selama klien membaca dari follower, jawaban basi selalu mungkin terjadi. Tahap tiga memperlihatkan biaya kedua: failover mengangkat follower yang belum sempat menyalin entri terakhir, sehingga tulisan [email protected] = 2 tidak dibawa dan dianggap tidak pernah ada.
Dua biaya tersebut adalah harga replication asinkron. Beralih ke synchronous menghilangkan risiko kehilangan tulisan tetapi membuat setiap tulisan menunggu follower — persis pertukaran pada tabel di awal chapter. Sistem produksi biasanya memilih titik tengah: asynchronous dengan monitoring lag dan peringatan dini, atau synchronous hanya untuk data yang tidak boleh hilang.
Replication menyelesaikan ketersediaan, tetapi menciptakan pertanyaan baru: bila isi replika bisa berbeda sesaat, janji apa yang boleh diberikan kepada pembaca? Pertanyaan tersebut dibedah pada Chapter Consistency Models.