Tujuh chapter terakhir membicarakan replikasi dengan nada optimis — dan memang patut: skala baca, ketersediaan, dan analitik terisolasi adalah kemenangan nyata. Namun arsitektur yang dipilih dengan berlebihan informasi berbeda dari yang dipilih dengan iklan. Replikasi punya batas-batas yang jelas dan kerap menghukum pemula yang menganggapnya obat serba bisa: replikasi bukan backup, beban tulis tidak berkurang sedikit pun, dan tulis ganda bisa merusak data dengan cara yang tidak terpikirkan sebelumnya. Chapter penutup ini menguji batas-batas tersebut satu per satu — sebagian bisa dibuktikan nyata di server uji kita, sebagian dibedah lewat simulasi yang jujur.
Replikasi Bukan Backup #
Kesalahpahaman klasiknya berbunyi: “replica menyimpan salinan data, jadi data aman”. Kata “aman” itulah yang keliru. Replica menyalin segala perubahan — termasuk yang merusak. DELETE tanpa WHERE, UPDATE yang salah sasaran, DROP TABLE akibat salah pilih database: semuanya adalah event binary log seperti event lainnya, dan replica menerapkannya dengan setia. Mari kita mainkan skenario tergelapnya di simulasi satu server:
DROP DATABASE IF EXISTS belajar_repl;
CREATE DATABASE belajar_repl;
USE belajar_repl;
CREATE TABLE produk_source (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(30) NOT NULL,
stok INT NOT NULL
);
INSERT INTO produk_source (nama, stok) VALUES
('Kopi Gayo', 40),
('Teh Melati', 25),
('Gula Aren', 15),
('Susu Kental', 30);
CREATE TABLE produk_replica LIKE produk_source;
INSERT INTO produk_replica SELECT * FROM produk_source;
SELECT
(SELECT COUNT(*) FROM produk_source) AS baris_source,
(SELECT COUNT(*) FROM produk_replica) AS baris_replica;Output yang Dihasilkan:
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, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
+--------------+---------------+
| baris_source | baris_replica |
+--------------+---------------+
| 4 | 4 |
+--------------+---------------+
1 row in set (0.00 sec)Kedua sisi memuat empat produk — replica “mutakhir”. Lalu terjadi human error klasik di source: DELETE tanpa WHERE dijalankan dalam kepanikan stok minus. Event-nya mengalir dan diterapkan replica (di sini kita mainkan peran SQL thread-nya):
DELETE FROM produk_source;
DELETE FROM produk_replica;
SELECT
(SELECT COUNT(*) FROM produk_source) AS baris_source,
(SELECT COUNT(*) FROM produk_replica) AS baris_replica;Output yang Dihasilkan:
Query OK, 4 rows affected (0.00 sec)
Query OK, 4 rows affected (0.00 sec)
+--------------+---------------+
| baris_source | baris_replica |
+--------------+---------------+
| 0 | 0 |
+--------------+---------------+
1 row in set (0.00 sec)Empat baris lawan empat baris: data lenyap di kedua server bersamaan. Replica menyalin kehancuran dengan kesungguhan yang sama seperti saat menyalin penjualan. Inilah bedanya cermin dan backup: cermin memantulkan apa pun yang terjadi sekarang, sedangkan backup membekukan keadaan pada titik waktu tertentu di penyimpanan terpisah. Sistem yang sehat memakai keduanya — backup terjadwal plus binary log untuk point-in-time recovery (materi lengkapnya ada di section Backup dan Recovery), dan replikasi untuk ketersediaan.
Beban Tulis Tidak Pernah Turun #
Replikasi membagi beban baca dan diam-diam menggandakan beban tulis. Setiap INSERT, UPDATE, dan DELETE yang diterima source harus dieksekusi ulang oleh setiap replica: satu transaksi import 10 juta baris berarti 10 juta baris yang sama ditulis ulang di semua mesin klaster. Menambah replica keempat menambah kapasitas baca sebesar satu mesin sekaligus menambah pekerjaan tulis sebesar satu mesin. Sumber daya disk I/O dan write throughput per-server justru bisa memburuk di seluruh klaster ketika replica-replica baru berebut sama-sama menulis ulang data yang sama — apalagi stream binlog di source ikut membesar bebannya. Bila kemacetan Anda di sisi tulis, jawabannya bukan replica tambahan, melainkan scale-up atau pemisahan data (sharding) — sebagaimana dibahas di chapter Read Replicas.
Tulis Ganda dan Tabrakan Auto-Increment #
Topologi yang berbahaya namun sering dicoba: dua source aktif bersamaan (misalnya di dua kantor yang saling mereplikasi), tulis di mana saja. Masalah fundamentalnya: kolom AUTO_INCREMENT di server A dan server B sama-sama menghitung dari 1 — dua tabel “pesanan” di dua kota akan memproduksi id yang sama untuk dua transaksi berbeda, dan ketika event saling direplikasikan, PRIMARY KEY menabrak. Yang sering disalahpahami: server_id yang berbeda tidak menyelamatkan data — server_id hanya menandai asal event agar event tidak berputar-putar dalam loop replikasi; ia sama sekali tidak mengubah nilai data yang ditulis.
Solusi klasik untuk tulis ganda adalah membagi rentang nomor: setiap server hanya boleh menghasilkan id pada jalurnya sendiri lewat dua variabel sesi — auto_increment_increment (langkah antar-nomor) dan auto_increment_offset (nomor awal). Server A memakai increment 2 offset 1 → hanya bilangan ganjil; server B increment 2 offset 2 → hanya bilangan genap. Kita buktikan polanya di satu server:
SET @@SESSION.auto_increment_increment = 2;
SET @@SESSION.auto_increment_offset = 1;
CREATE TABLE pesanan_a (
id INT AUTO_INCREMENT PRIMARY KEY,
pelanggan VARCHAR(30) NOT NULL
);
INSERT INTO pesanan_a (pelanggan) VALUES ('Budi'), ('Siti'), ('Andi');
SET @@SESSION.auto_increment_offset = 2;
CREATE TABLE pesanan_b (
id INT AUTO_INCREMENT PRIMARY KEY,
pelanggan VARCHAR(30) NOT NULL
);
INSERT INTO pesanan_b (pelanggan) VALUES ('Dewi'), ('Rina');
SET @@SESSION.auto_increment_increment = 1;
SET @@SESSION.auto_increment_offset = 1;
SELECT id, pelanggan, 'pesanan_a' AS sumber FROM pesanan_a
UNION ALL
SELECT id, pelanggan, 'pesanan_b' AS sumber FROM pesanan_b
ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
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, 0 rows affected (0.00 sec)
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+----+-----------+-----------+
| id | pelanggan | sumber |
+----+-----------+-----------+
| 1 | Budi | pesanan_a |
| 2 | Dewi | pesanan_b |
| 3 | Siti | pesanan_a |
| 4 | Rina | pesanan_b |
| 5 | Andi | pesanan_a |
+----+-----------+-----------+
5 rows in set (0.00 sec)Kedua “server” menghasilkan id yang berselang-seling tanpa pernah bertabrakan: pesanan_a mengisi 1, 3, 5 dan pesanan_b mengisi 2, 4 — meskipun kolomnya sama-sama mulai dari 1. Perhatikan juga dua statement terakhir yang mengembalikan nilai bawaan variabel sesi — kebiasaan yang menjaga koneksi tidak membawa setting aneh ke blok berikutnya. Pembagian ganjil-genap ini sederhana namun mahal harganya di kasus nyata: separuh rentang id terbuang, dan replikasi dua arah (active-active) tetap rentan konflik non-primary-key yang tidak tertolong offset mana pun. Karena itu pendekatan modern — GTID dan group replication dengan mekanisme konsensus — jauh lebih disarankan untuk kebutuhan tulis multi-node.
Fungsi Nondeterministik dan Format Binary Log #
Replikasi tertua merekam statement aslinya (format STATEMENT) dan menjalankan ulang statement yang sama di replica. Format ini murah tapi punya lubang: statement yang hasilnya berbeda tiap eksekusi akan menghasilkan data berbeda di source dan replica. Tiga kelompok fungsi berbahaya: fungsi waktu (NOW(), SYSDATE()), fungsi acak (RAND(), UUID()), dan LIMIT tanpa ORDER BY (baris mana yang terpilih tidak terjamin sama). Kita buktikan sifat acaknya RAND() dengan dua pemanggilan ber-seed sama dan tanpa seed:
SELECT RAND(42) AS a, RAND(42) AS b;Output yang Dihasilkan:
+--------------------+--------------------+
| a | b |
+--------------------+--------------------+
| 0.6629110161800972 | 0.6629110161800972 |
+--------------------+--------------------+
1 row in set (0.00 sec)Dengan seed 42, kedua pemanggilan menghasilkan nilai yang persis sama — RAND ber-seed bersifat deterministik. Bandingkan dengan tanpa seed:
-- @check
SELECT RAND() AS x1, RAND() AS x2;Output yang Dihasilkan:
+---------------------+---------------------+
| x1 | x2 |
+---------------------+---------------------+
| 0.85536315231989545 | 0.20049534007719452 |
+---------------------+---------------------+
1 row in set (0.00 sec)Nilai di blok terakhir sengaja ilustratif — tanpa seed, setiap eksekusi menghasilkan nilai baru, termasuk di mesin Anda; yang pasti hanyalah x1 dan x2 hampir selalu berbeda. Nah, bayangkan statement INSERT INTO undian (nilai) VALUES (RAND()); direplikasi dengan format STATEMENT: replica menjalankan ulang statement-nya, RAND() berbunyi sekali lagi, dan undian di replica berisi nilai yang berbeda dari source — data diam-diam terpisah. Masalah sejenis menimpa LIMIT tanpa ORDER BY karena baris yang “lima pertama” di source belum tentu lima pertama di replica.
MySQL 8.4 menutup lubang ini dengan menjadikan format ROW sebagai bawaan — dan bahkan variabelnya sendiri kini berstatus deprecated:
SELECT @@binlog_format AS binlog_format;
SHOW WARNINGS;Output yang Dihasilkan:
+---------------+
| binlog_format |
+---------------+
| ROW |
+---------------+
1 row in set, 1 warning (0.00 sec)
+---------+------+--------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------------------------+
| Warning | 1287 | '@@binlog_format' is deprecated and will be removed in a future release. |
+---------+------+--------------------------------------------------------------------------+
1 row in set (0.00 sec)Nilainya ROW — binary log merekam hasil akhir setiap baris yang berubah (baris lama dan baris baru), bukan statement pembentuknya. RAND(), NOW(), dan LIMIT menjadi tidak relevan: apa pun hasilnya di source, hasil itulah yang dikirim ke replica. Warning 1287 pada blok di atas menegaskan arah MySQL: format binary log sedang dikunci ke ROW permanen, variabel pengaturnya dipensiunkan.
Biaya Operasional yang Sering Dilupakan #
Setiap replica adalah server penuh: lisensi/pemeliharaan, RAM dan disk sekelas sumber (data yang sama!), konfigurasi my.cnf sendiri, akun replikasi, dan pemantauan sendiri. Operator harus menjaga lag tetap sehat, binary log terpurg dengan disiplin, disk relay log tidak membengkak, dan versi MySQL naik secara terkoordinasi. Saat terjadi insiden, replikasi menambah satu dimensi diagnosis: “apakah ini terjadi di source saja, di satu replica, atau di semua?” Untuk tim kecil dengan satu aplikasi sederhana, tiga replica dan ProxySQL bisa jauh lebih mahal daripada satu server yang diperbesar — keputusan topologi adalah keputusan biaya, bukan hanya teknik.
Kapan Replikasi Cukup, Kapan Saatnya Cluster #
Replikasi klasik (asinkron/semi-sync) dan cluster konsensus menjawab kebutuhan yang berbeda. Peta ringkasnya:
| Kebutuhan | Replikasi klasik cukup? | Alternatif |
|---|---|---|
| Skala baca | Ya — bahkan sangat pas | — |
| Cadangan dingin + failover terencana | Ya | — |
| Failover otomatis < 10 detik | Kurang pas | Group replication + router |
| Tulis di banyak node sekaligus | Tidak | Group replication / InnoDB Cluster |
| Jaminan konsisten saat partisi jaringan | Tidak | Konsensus (group replication) |
Group Replication — pengantar singkatnya sudah kita baca di chapter Asynchronous Replication — membangun klaster yang menyetujui transaksi secara konsensus sebelum commit, otomatis memilih primary baru saat terjadi kegagalan, dan menolak tulis di node yang tidak melihat mayoritas klaster. Di atasnya, InnoDB Cluster menambahkan MySQL Router dan alat administrasi sehingga aplikasi tinggal mengenal satu alamat. Harganya: butuh minimal tiga server, latensi tulis naik karena putaran konsensus, dan konsep operasional yang jauh lebih dalam dari satu pasangan source-replica. Aturan pragmatisnya: mulailah dari replikasi klasik yang dipahami betul, dan naik kelas hanya saat kebutuhan pada tabel di atas benar-benar menyala.
Membersihkan Database Latihan #
DROP DATABASE belajar_repl;Output yang Dihasilkan:
Query OK, 4 rows affected (0.00 sec)Referensi #
- Replication Features and Issues — MySQL 8.4 Reference Manual
- Replication — MySQL 8.4 Reference Manual
- The Binary Log — MySQL 8.4 Reference Manual
Dengan batas-batas ini terpetakan, kita menutup section Replication dan High Availability dengan imbang: replikasi memberi kita skala baca, kesiapan failover, dan ruang bernapas untuk analitik — dengan disiplin backup yang terpisah, routing yang sadar lag, dan kesadaran bahwa tulis tetap terpusat. Perjalanan course masih berlanjut ke wilayah yang menghubungkan database dengan kode: section berikutnya membahas integrasi aplikasi — mulai dari PHP, Go, hingga Python.