Sejauh ini kita membicarakan replica seolah data mengalir seketika. Realitasnya berbeda: mode bawaan MySQL adalah asynchronous replication — source meng-commit transaksi dan membalas aplikasi tanpa menunggu replica menerima apa pun. Pilihan desain ini membuat tulisan di source tetap secepat server berdiri sendiri, tetapi membuka jendela waktu ketika data sudah “aman” di source padahal belum sampai ke replica — dan bila source mati tepat di jendela itu, transaksi hilang dari replica yang tersisa. Chapter ini membedah alur sebuah commit dari aplikasi hingga diterapkan di replica, membuktikan polanya lewat simulasi, lalu menimbang semi-synchronous replication sebagai kompromi antara kecepatan dan ketahanan.

Bagaimana Sebuah Commit Mengalir #

Mari kita ikuti satu transaksi — misalnya INSERT pesanan baru — dari awal hingga tampil di replica. Diagram sequence berikut memetakan keenam pelakunya:

Urutannya perlu dibaca pelan-pelan karena posisi “jawaban ke aplikasi” adalah kunci seluruh chapter. Langkah 1: aplikasi mengirim INSERT lalu COMMIT ke source. Langkah 2: source menulis data ke tabel dan — pada saat commit — menuliskan event perubahan ke binary log. Langkah 3: source membalas aplikasi Query OK; sejauh ini replica belum terlibat sama sekali. Langkah 4-6: IO thread replica, yang sejak awal menunggu di koneksi terbuka, menarik event baru dari source; event disimpan di relay log, lalu SQL thread replica menjalankan ulang event tersebut sehingga baris yang sama muncul di replica.

Kata kuncinya: langkah 3 tidak menunggu langkah 4-6 selesai. Aplikasi menerima konfirmasi setelah event ditulis ke binary log milik source, bukan setelah event diterapkan di replica. Dengan kata lain, jaminan durability replikasi bawaan hanya berhenti di disk source — itulah arti harfiah “asynchronous”.

Konsekuensi: Jendela Kehilangan Data #

Bila source crash di antara langkah 3 dan langkah 5, event sudah tercatat di binary log source tetapi belum pernah sampai ke replica. Failover ke replica (bab Failover Concepts) lalu membuat transaksi itu hilang dari klaster — aplikasi sempat menerima “Query OK”, pelanggan pesanannya, tetapi datanya tidak ada di server yang hidup. Besarnya risiko ini diukur dengan istilah RPO (recovery point objective): berapa banyak data yang boleh hilang. Replikasi asinkron antar-server-di-rack-yang-sama biasanya berarti RPO kecil (milidetik hingga detik), namun “kecil” bukan “nol”.

Menutup jendela itu membutuhkan kesediaan menunggu — dan menunggu berarti latensi tulisan naik. Kita bisa memilih di titik mana menunggu:

ModeSource menunggu sampaiData hilang saat source crashLatensi tulisan
Asynchronous (bawaan)Tidak menunggu sama sekaliBisa: event yang belum ditarik replicaTerendah
Semi-synchronousMinimal satu replica mengirim ackSangat kecil (event sudah di relay log)Sedikit naik
Fully synchronousSemua replica selesai menerapkanNol secara teoriTinggi — jarang dipakai

Membuktikan Pola Asinkron dengan Simulasi Satu Server #

Kita tidak punya dua server, tetapi pola “source maju duluan, replica mengejar belakangan” bisa dibuktikan dengan jujur memakai dua tabel: pesanan_source berperan sebagai data di source, pesanan_replica sebagai salinannya. Kali ini kita hanya memindahkan perubahan (bukan seluruh tabel) — persis seperti event binary log. Siapkan panggungnya:

sql
DROP DATABASE IF EXISTS belajar_repl;
CREATE DATABASE belajar_repl;
USE belajar_repl;

CREATE TABLE pesanan_source (
  id INT AUTO_INCREMENT PRIMARY KEY,
  pelanggan VARCHAR(30) NOT NULL,
  total DECIMAL(10,2) NOT NULL
);

INSERT INTO pesanan_source (pelanggan, total) VALUES
  ('Budi', 55000.00),
  ('Siti', 25000.00),
  ('Andi', 62000.00);

CREATE TABLE pesanan_replica LIKE pesanan_source;
INSERT INTO pesanan_replica SELECT * FROM pesanan_source;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

Ketiga baris awal disalin sekali (peran salinan awal mysqldump), jadi kedua “server” mulai dari kondisi sama. Sekarang transaksi baru datang ke source saja — kita tampilkan juga status sinkronnya dengan IF:

sql
INSERT INTO pesanan_source (pelanggan, total) VALUES ('Dewi', 15000.00);
SELECT
  (SELECT COUNT(*) FROM pesanan_source) AS baris_source,
  (SELECT COUNT(*) FROM pesanan_replica) AS baris_replica,
  IF((SELECT COUNT(*) FROM pesanan_source) = (SELECT COUNT(*) FROM pesanan_replica),
     'sinkron', 'tertinggal') AS status_replica;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

+--------------+---------------+----------------+
| baris_source | baris_replica | status_replica |
+--------------+---------------+----------------+
|            4 |             3 | tertinggal     |
+--------------+---------------+----------------+
1 row in set (0.00 sec)

Empat baris lawan tiga baris: status_replica membaca tertinggal. Inilah isi jendela asinkron — di sistem sungguhan, durasinya adalah selisih waktu antara commit di source dan selesainya SQL thread replica. Kini kita mainkan peran kedua thread replica: IO thread “menarik” event (INSERT baris keempat), SQL thread “menerapkannya” ke tabel replica:

sql
INSERT INTO pesanan_replica SELECT * FROM pesanan_source WHERE id = 4;
SELECT
  (SELECT COUNT(*) FROM pesanan_source) AS baris_source,
  (SELECT COUNT(*) FROM pesanan_replica) AS baris_replica,
  IF((SELECT COUNT(*) FROM pesanan_source) = (SELECT COUNT(*) FROM pesanan_replica),
     'sinkron', 'tertinggal') AS status_replica;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

+--------------+---------------+----------------+
| baris_source | baris_replica | status_replica |
+--------------+---------------+----------------+
|            4 |             4 | sinkron        |
+--------------+---------------+----------------+
1 row in set (0.00 sec)

Status kembali sinkron — baris keempat kini ada di kedua sisi. Perhatikan bahwa yang dipindahkan hanya baris baru (WHERE id = 4), bukan seluruh tabel: replikasi asinkron bekerja secara incremental, event demi event. Bila kita tiga kali mengulang cepat pola insert-nya, replica akan tertinggal lagi lalu menyusul lagi — kitarinya persis siklus lag yang kita ukur di chapter Replication Lag.

Semi-Synchronous Replication #

MySQL menutup sebagian jendela asinkron dengan plugin semi-synchronous replication. Idenya: ketika plugin aktif di source, setiap commit menunggu minimal satu replica mengirim acknowledgment bahwa event sudah diterima ke relay log-nya — bukan sudah diterapkan, cukup sudah diterima. Latensi tulisan hanya naik sedikit (sekali round-trip jaringan), namun jaminannya besar: transaksi yang sudah dijawab “Query OK” pasti sudah ada di dua mesin.

Plugin ini tidak terpasang secara bawaan, jadi variabel-variabelnya pun belum ada di server yang segar. Kita buktikan keadaan itu di server uji:

sql
SHOW VARIABLES LIKE 'rpl_semi_sync%';

Output yang Dihasilkan:

text
Empty set (0.00 sec)

Hasil Empty set menegaskan variabel rpl_semi_sync_source_enabled dan kawan-kawannya belum terdaftar. Mencoba membacanya langsung malah ditolak:

sql
-- @error
SELECT @@rpl_semi_sync_source_enabled;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1193 (HY000): Unknown system variable 'rpl_semi_sync_source_enabled'
Bye

ERROR 1193 (unknown system variable) terjadi karena variabel tersebut baru dibuat oleh plugin saat plugin dipasang. Di server sungguhan, operator memasangnya lewat INSTALL PLUGIN lalu mengaktifkannya:

sql
-- @notest
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;

Blok di atas tidak kita jalankan di server uji — memasang plugin mengubah daftar plugin server secara permanen, dan hanya relevan pada pasangan source-replica sungguhan (di sisi replica, plugin-nya bernama rpl_semi_sync_replica dengan variabel rpl_semi_sync_replica_enabled). Ada satu parameter yang wajib dipahami sebelum mengandalkan semi-sync: rpl_semi_sync_source_timeout (bawaan 10 detik). Bila dalam waktu itu tidak ada satu replica pun mengirim ack — replica sedang down atau lagging — source turun kelas ke mode asinkron demi tidak menggantung seluruh aplikasi. Semi-sync mengecilkan jendela kehilangan data, bukan menutupnya dengan gembok.

Group Replication: Konsensus, Bukan Penungguan #

Bila kebutuhannya lebih tinggi lagi — tidak boleh ada jendela hilang dan failover harus otomatis — MySQL menyediakan Group Replication: sekelompok server yang menyetujui setiap transaksi lewat protokol konsensus ala Paxos sebelum di-commit, sehingga anggota yang gagal mayoritas tidak membawa transaksi yang diragukan. Group replication adalah fondasi dari InnoDB Cluster dan MySQL Cluster, topik lanjutan yang layak dibaca setelah seluruh section ini selesai. Untuk sebagian besar kasus, kombinasi “asinkron + semi-sync pada replica kritis” sudah mencukupi dengan biaya operasional yang jauh lebih rendah.

Membersihkan Database Latihan #

sql
DROP DATABASE belajar_repl;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)

Referensi #

Kita kini paham mengapa replica bisa tertinggal bukan karena bug, melainkan karena desain: source membalas aplikasi lebih cepat daripada replica mengejar. Keterlambatan kecil itu punya nama, ukuran, dan cara memantaunya — Seconds_Behind_Source — serta daftar penyebab yang panjang dan menarik. Chapter berikutnya menyelami replication lag dari sisi pengukuran, akar masalah, dan strategi mitigasinya.