Aplikasi web khas menjalankan puluhan SELECT untuk setiap tulisan: halaman katalog, daftar pesanan, notifikasi, autocomplete. Ketika traffic tumbuh, server tunggal ditekan dari arah baca jauh lebih dulu daripada arah tulisan. Read replica adalah jawaban arsitektural paling populer untuk tekanan itu: satu source menerima semua tulisan, sementara beberapa replica hanya menerima bacaan yang disebarkan di antara mereka. Pola ini relatif murah (server replica bisa berkelas lebih kecil), bertambah linear untuk beban baca, dan sekaligus menyediakan kandidat failover. Chapter ini menyusun polanya dari routing sederhana sampai topologi lanjutan — ditutup diskusi jujur kapan pola ini justru gagal menolong.

Pola Dasar: Tulis ke Source, Baca dari Replica #

Gambar besar pola ini berbentuk bintang. Aplikasi mengirim seluruh tulisan ke satu titik (source), lalu membagi-bagi bacaan ke replica-replica:

Sumber daya yang disharing adalah baca, bukan tulis. Setiap replica memuat salinan hampir seluruh data, sehingga query katalog di replica-1 dan query laporan di replica-3 tidak pernah berebut CPU atau buffer pool dengan transaksi di source. Tiga replica berarti kapasitas baca kira-kira tiga kali lipat daerah tulis — dan menambah replica keempat cuma biaya satu mesin baru, tanpa mengubah aplikasi sama sekali bila routing-nya sudah tertangani.

Membangun Pool Baca dan Routing Sederhana #

Keputusan routing baca pada akhirnya adalah data kecil: daftar server, perannya, dan kesehatannya. Kita bangun pool semacam itu di database latihan belajar_repl — persis struktur yang dipelihara tool atau modul koneksi aplikasi di produksi:

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

CREATE TABLE pool_server (
  host VARCHAR(30) NOT NULL PRIMARY KEY,
  peran VARCHAR(10) NOT NULL,
  lag_seconds INT NOT NULL
);

INSERT INTO pool_server (host, peran, lag_seconds) VALUES
  ('source-utama', 'tulis', 0),
  ('replica-1',    'baca',  0),
  ('replica-2',    'baca',  2),
  ('replica-3',    'baca',  17);

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, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

Router baca yang paling sederhana memilih hanya dari server sehat — sehat artinya perannya baca dan lag-nya rendah:

sql
SELECT host, lag_seconds
FROM pool_server
WHERE peran = 'baca' AND lag_seconds <= 2
ORDER BY lag_seconds, host;

Output yang Dihasilkan:

text
+-----------+-------------+
| host      | lag_seconds |
+-----------+-------------+
| replica-1 |           0 |
| replica-2 |           2 |
+-----------+-------------+
2 rows in set (0.00 sec)

Dua kandidat lolos: replica-1 dan replica-2; replica-3 yang tertinggal 17 detik disingkirkan dulu. Di aplikasi, daftar dua kandidat ini lalu diputar round-robin — permintaan pertama ke replica-1, kedua ke replica-2, ketiga kembali ke replica-1, dan seterusnya. Sketsa logikanya:

text
kandidat = ambil_replica_sehat()          -- query WHERE peran='baca' AND lag kecil
urut     = 0
untuk setiap permintaan baca:
    server = kandidat[urut mod jumlah(kandidat)]
    urut   = urut + 1
    jalankan SELECT di server

Dua keputusan penting hidup di luar sketsa. Pertama, kapan daftar kandidat diperbarui — nilai lag yang disimpan sejak semalam bisa menipu, jadi pool perlu di-refresh berkala (atau dibaca langsung dari SHOW REPLICA STATUS). Kedua, bacaan kritis — yang mengikuti tulisan pengguna yang sama — jangan masuk putaran round-robin sama sekali; kirim ke source sepanjang session, sebagaimana dibahas tuntas di chapter Replication Lag.

Konsistensi Eventual Adalah Harga Pola Ini #

Dengan bacaan tersebar, dua pengguna bisa melihat dunia yang berbeda untuk data yang sama pada detik yang sama: satu membaca dari replica yang mutakhir, satunya dari replica yang sedang tertinggal dua detik. Untuk katalog produk dan feed konten, selisih dua detik tidak menyakitkan. Untuk saldo dompet, status pesanan, dan stok tersisa, selisih itu bisa berubah menjadi kesalahan bisnis. Disiplinnya satu kalimat: klasifikasikan setiap query baca sebagai boleh-basi atau harus-segar, lalu rutekan sesuai kelasnya — bukan rutekan semuanya ke pool.

Topologi Lanjutan: Bercabang dan Berantai #

Bintang bukan satu-satunya bentuk. Dua pola lanjutan yang sering muncul:

TopologiBentukKapan dipakai
Star (bintang)Semua replica menarik langsung dari sourceJumlah replica sedikit, jaringan ke source murah
Chain (rantai)Replica menarik dari replica lainMenghemat beban stream binlog source; menjangkau lokasi jauh
Fan-out via relay replicaSatu replica “agregator” menyalurkan ke banyak anakSumber asli dilindungi dari puluhan koneksi penarik

Rantai bekerja karena replica yang mengaktifkan log_bin sekaligus bisa menjadi source untuk replica lain — MySQL menyebutnya replica bertingkat. Contoh klasiknya: kantor pusat punya source, cabang Bandung menarik dari source sebagai replica lokal, lalu server pelaporan kantor cabang menarik dari replica Bandung. Keuntungannya, source pusat hanya mengirim satu stream lintas-kota. Harganya: lag menumpuk sepanjang rantai — server pelaporan selalu tertinggal lebih lama daripada replica Bandung.

Menyembunyikan Beban Analitik di Replica #

Laporan bulanan yang memindai jutaan baris adalah tamu yang tidak sopan bagi server transaksi: ia memakan buffer pool, memicu disk I/O penuh, dan membuat checkout pelanggan tersendat. Pola read replica mengeluarkannya dari ruangan. Kita lihat wujudnya di simulasi — tabel pesanan_replica menjadi sasaran laporan berat, sementara pesanan_source (yang “dipakai aplikasi”) tidak tersentuh:

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

INSERT INTO pesanan_source (pelanggan, produk, jumlah, harga) VALUES
  ('Budi', 'Kopi Gayo',  2, 55000.00),
  ('Siti', 'Teh Melati', 3, 25000.00),
  ('Rina', 'Gula Aren',  4, 15000.00),
  ('Andi', 'Kopi Gayo',  1, 55000.00),
  ('Siti', 'Kopi Gayo',  3, 55000.00),
  ('Rina', 'Teh Melati', 2, 25000.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 (0.00 sec)

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

Query OK, 0 rows affected (0.00 sec)

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

Transaksi baru tetap mengalir lewat source, lalu event-nya diterapkan ke replica:

sql
INSERT INTO pesanan_source (pelanggan, produk, jumlah, harga) VALUES ('Dewi', 'Gula Aren', 2, 15000.00);
INSERT INTO pesanan_replica SELECT * FROM pesanan_source WHERE id = 7;

Output yang Dihasilkan:

text
Query OK, 1 row affected (0.00 sec)

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

Sekarang laporan omzet per produk — query agregasi yang di produksi bisa berjalan menit — diarahkan hanya ke replica:

sql
SELECT produk,
       COUNT(*) AS jumlah_transaksi,
       SUM(jumlah * harga) AS omzet
FROM pesanan_replica
GROUP BY produk
ORDER BY omzet DESC, produk;

Output yang Dihasilkan:

text
+------------+------------------+-----------+
| produk     | jumlah_transaksi | omzet     |
+------------+------------------+-----------+
| Kopi Gayo  |                3 | 330000.00 |
| Teh Melati |                2 | 125000.00 |
| Gula Aren  |                2 |  90000.00 |
+------------+------------------+-----------+
3 rows in set (0.00 sec)

Kopi Gayo memimpin omzet dengan 330000.00 dari tiga transaksi. Perhatikan apa yang tidak terjadi: pesanan_source tidak ikut dipindai, transaksi aplikasi tidak berdesakan dengan laporan, dan laporan boleh dijalankan berapa kali pun tanpa mengganggu siapa pun. Inilah alasan pola “replica khusus analitik” jadi standar di perusahaan dengan beban reporting besar.

Penyeimbang Beban di Depan Pool #

Saat jumlah server dan aplikasi bertambah, routing round-robin buatan sendiri biasanya digantikan proxy khusus. HAProxy menjadi gerbang TCP yang membagi koneksi dan memeriksa server mati lewat health check. ProxySQL naik kelas lagi: ia memahami protokol MySQL, bisa memisahkan query baca/tulis lewat aturan routing, dan memindahkan server yang lag-nya memburuk keluar dari kelompok baca secara otomatis. Aplikasi lalu hanya mengenal satu alamat (proxy) — daftar pool tidak lagi tersebar di kode aplikasi.

Kapan Read Replica Berhenti Membantu #

Pola ini punya batas yang perlu diakui sejak awal: beban tulis tidak berkurang sedikit pun. Setiap INSERT yang diterima source harus dijalankan ulang oleh setiap replica — menambah replica justru menambah total pekerjaan tulis di klaster. Bila kemacetan Anda ada di sisi tulis (antrian commit panjang, disk I/O jenuh saat import, tabel panas), read replica adalah obat yang salah:

GejalaObat yang tepat
Baca lambat, tulis masih legaRead replica (pola chapter ini)
Tulis jenuh meski baca sudah dialihkanScale-up: CPU/RAM/disk lebih cepat
Satu tabel raksasa menggerus semuanyaSharding/partisi data lintas server
Butuh failover otomatis + tulis multi-nodeGroup replication / InnoDB Cluster

Membersihkan Database Latihan #

sql
DROP DATABASE belajar_repl;

Output yang Dihasilkan:

text
Query OK, 3 rows affected (0.00 sec)

Referensi #

Bacaan kini punya pasukan penyokongnya sendiri, dan tulisan tetap terpusat di satu jenderal. Pertanyaan berikutnya sudah menunggu di garis depan: apa yang terjadi ketika si jenderal jatuh? Chapter Failover Concepts menyiapkan skenario terburuk — source mati mendadak — beserta prosedur mempromosikan replica, bahaya split-brain, dan alat otomatisasi seperti MHA dan Orchestrator.