Replication adalah menyalin setiap perubahan dari satu database utama (primary) ke database lain (replica) terus-menerus, supaya replica bisa ikut melayani pembacaan dan siap menggantikan primary kalau primary mati. Bayangin pertandingan bola. Panitia mencatat skor resmi di buku pertandingan, lalu layar-layar di sekeliling stadion ikut diperbarui sebentar kemudian. Penonton yang melirik layar tepat setelah gol masih melihat skor lama. Kalau buku resminya hilang, panitia memakai layar yang paling baru sebagai catatan resmi, dan gol yang belum sempat tampil di layar itu ikut hilang.
🧪 Tebakan cepat: admin Kedai Senja mengubah harga Kopi Susu dari Rp18.000 jadi Rp20.000. UPDATE ke primary sukses. 1 ms kemudian, di request yang sama, Laravel membaca harganya lagi dari replica yang tertinggal 500 ms. Harga yang tampil: Rp18.000, Rp20.000, atau error? Tebak dulu, jawabannya ada di bagian "Replication lag: harga lama muncul lagi".
Primary dan replica
- Primary menerima semua write (
INSERT,UPDATE,DELETE) dan juga bisa dibaca. - Replica adalah salinan yang cuma bisa dibaca (read-only).
UPDATEyang nyasar ke replica ditolak denganERROR: cannot execute UPDATE in a read-only transaction. - PostgreSQL mencatat setiap perubahan di WAL (Write-Ahead Log), catatan yang ditulis sebelum datanya diubah. Ini WAL yang sama dengan yang diarsipkan untuk PITR di lesson Backup & Recovery. Dengan streaming replication, primary mengirim WAL itu ke replica, dan replica menerapkannya (replay) satu per satu.
Jadi write selalu ke primary, sementara read bisa dibagi antara primary dan replica. Write cuma ke satu tempat supaya gak ada konflik: kalau dua database sama-sama menerima write, yang satu bisa mengisi harga Rp20.000 dan yang lain Rp22.000 untuk baris yang sama.
Replication memberi dua hal: read scaling (SELECT yang banyak dibagi ke replica, primary fokus ke write) dan high availability atau HA (kalau primary mati, replica menggantikannya dalam hitungan detik, bukan jam).
Replication lag: harga lama muncul lagi
Replica selalu sedikit tertinggal dari primary. Jarak waktunya disebut replication lag. Biasanya di bawah satu detik, tapi bisa melonjak jadi beberapa detik saat database sibuk. Di Lab, lag-nya 500 ms, commit di primary makan 2 ms, dan replikasinya async (bawaan PostgreSQL):
| Waktu | Query | Dikirim ke | Hasil |
|---|---|---|---|
| 0 ms | UPDATE products SET price = 20000 WHERE id = 1 |
primary | commit di 2 ms |
| 3 ms | SELECT price FROM products WHERE id = 1 |
replica | 18000 |
| 502 ms | WAL perubahan tadi sampai, replica menerapkannya | replica | sekarang 20000 |
Halaman menampilkan Rp18.000. Gak ada error: replica menjawab dengan data yang dia punya, dan datanya belum berubah. Itu jawaban tebakannya.
Masalah ini namanya read-your-writes: kamu menulis sesuatu, lalu gak bisa membaca tulisanmu sendiri. Contoh yang sering ketemu: user membuat post, refresh halaman, dan post-nya belum muncul. Penyebabnya bukan database rusak, bukan bug frontend, bukan cache browser, tapi replication lag.
Read dan write di Laravel
Laravel bisa membagi query sendiri. Di config/database.php:
'pgsql' => [
'driver' => 'pgsql',
'read' => [
'host' => ['10.0.0.11'], // replica
],
'write' => [
'host' => ['10.0.0.10'], // primary
],
'sticky' => true,
'database' => 'kedai_senja',
// username, password, port, ...
],
SELECTdikirim ke host diread, sedangkanINSERT,UPDATE,DELETEdan query di dalamDB::transaction()ke host diwrite. Kalaureadberisi beberapa host, Laravel memilih salah satunya secara acak.'sticky' => true: setelah ada write di request yang sama, semuaSELECTberikutnya di request itu dikirim ke primary. Jadi$product->update(['price' => 20000]); return $product->fresh();mendapat Rp20.000.stickygak ikut ke request berikutnya. Setelah redirect atau refresh, request baru membaca dari replica lagi. Untuk halaman yang dibuka tepat setelah user menulis, tandai session-nya lalu baca dari primary beberapa detik, misalnya denganDB::connection()->useWriteConnectionWhenReading()di middleware, atau per query denganProduct::onWriteConnection()->find(1).
Latihannya memodelkan router ini dengan routeQuery(sql, context). Ambil kata pertama SQL setelah spasi di awal dibuang (trim()), lalu bandingkan tanpa peduli huruf besar kecil (toUpperCase()):
| SQL | context |
Tujuan |
|---|---|---|
SELECT ... |
{ justWrote: false } atau {} |
"replica" |
SELECT ... |
{ justWrote: true } |
"primary" (read-your-writes) |
INSERT, UPDATE, DELETE |
apa saja | "primary" |
lainnya: TRUNCATE, CREATE, MERGE |
apa saja | "primary" (default aman) |
Sync atau async
Async (bawaan PostgreSQL): commit selesai begitu primary menyimpan perubahannya. WAL dikirim ke replica sesudahnya. Sync membuat commit menunggu replica. Caranya di postgresql.conf primary:
synchronous_standby_names = 'replica1'
synchronous_commit = remote_apply
- Dengan
synchronous_commit = on(nilai bawaan), commit menunggu sampai replica menyimpan WAL-nya ke disk. Gak ada data yang hilang saat failover, tapi baca di replica tepat setelah commit masih bisa basi sesaat. - Dengan
remote_apply, commit menunggu sampai replica selesai menerapkannya, jadi baca di replica langsung segar. Ini yang dipakai mode sync di Lab: commit-nya jadi 502 ms, bukan 2 ms.
| Async | Sync | |
|---|---|---|
| Commit menunggu | primary saja | primary + replica |
| Kecepatan write | cepat (2 ms di Lab) | ditambah waktu ke replica (502 ms di Lab) |
| Primary mati | perubahan yang belum sampai replica hilang | gak ada yang hilang |
| Replica sync mati | primary jalan terus | write tertahan sampai replica kembali |
Async cocok untuk kebanyakan aplikasi web. Sync dipakai untuk data yang gak boleh hilang sama sekali, seperti saldo dan pembayaran.
Failover: saat primary mati
Saat primary mati, sistem HA melakukan failover:
- Mendeteksi primary gak menjawab health check beberapa kali berturut-turut.
- Memilih replica yang paling up-to-date, yaitu yang paling sedikit tertinggal.
- Promote: replica itu dijadikan primary baru, misalnya dengan
SELECT pg_promote();, dan mulai menerima write. - Aplikasi diarahkan ke primary baru lewat DNS, IP virtual, atau proxy.
Langkah-langkah ini dijalankan otomatis oleh tool seperti Patroni dan pg_auto_failover, atau oleh layanan terkelola seperti AWS RDS Multi-AZ dan Google Cloud SQL HA. Dengan setelan yang umum, write jalan lagi dalam sekitar 10 sampai 30 detik. Di Lab, failover dimodelkan 15 detik.
Dengan async, perubahan yang sudah commit di primary tapi belum sampai ke replica hilang:
| Waktu | Kejadian |
|---|---|
| 0 ms | UPDATE harga jadi Rp20.000, commit di 2 ms. Admin melihat "tersimpan". |
| 3 ms | Primary mati. WAL perubahan tadi belum sampai replica (baru akan sampai di 502 ms). |
| 15.003 ms | Replica di-promote. Harga di primary baru: Rp18.000. Perubahan Rp20.000 hilang. |
Dengan sync, commit baru selesai di 502 ms, setelah replica menerapkan perubahannya. Replica yang di-promote sudah punya Rp20.000, jadi gak ada yang hilang.
📌 Split-brain, beberapa replica, dan memantau lag
- Split-brain: primary lama hidup lagi dan masih menerima write, padahal replica sudah jadi primary baru. Sekarang ada dua primary dan datanya bercabang. Tool HA mencegahnya dengan fencing (mematikan atau mengisolasi node lama) dan quorum (keputusan butuh suara mayoritas, misalnya etcd di Patroni, minimal 3 node).
- Beberapa replica: yang di-promote adalah yang paling sedikit tertinggal, lalu replica lain mengikuti primary baru. Supaya write gak berhenti saat satu replica sync mati, pakai
synchronous_standby_names = 'ANY 1 (replica1, replica2)'. - Istilah lain: primary-replica dulu sering disebut master-slave. Ada juga multi-primary (master-master), di mana beberapa node menerima write dan konfliknya harus diselesaikan dengan aturan. Kebanyakan aplikasi web cukup dengan satu primary.
- Pantau lag di primary:
SELECT client_addr, replay_lag FROM pg_stat_replication;. Pasang alert kalau lag lewat beberapa detik.
⚠️ Jebakan yang sering ketemu
- Membaca dari replica tepat setelah menulis. Halaman sukses, halaman "Pesanan saya", atau
fresh()setelahupdate()harus membaca dari primary, atau user melihat data lama. - Mengira
stickyberlaku setelah redirect.stickycuma untuk sisa request yang sama. Request berikutnya butuh penanda di session. - Menganggap replica sebagai backup.
DELETE FROM orders;tanpaWHEREikut tersalin ke replica dalam milidetik. Backup dan PITR dari lesson Backup & Recovery tetap wajib. - Di latihan, rapikan SQL-nya dulu.
' select name from posts'diawali spasi dan huruf kecil: pakaitrim()dantoUpperCase().justWrotedicek dengan=== true, jadi{}tetap"replica". SQL yang gak dikenal, sepertiTRUNCATEdanMERGE, dikirim ke"primary".
TL;DR: Replication menyalin setiap perubahan dari primary ke replica lewat WAL. Write selalu ke primary; read bisa dibagi ke replica. Replica selalu tertinggal sedikit (replication lag), jadi SELECT ke replica tepat setelah UPDATE bisa mengembalikan data lama: di Lab, Rp18.000 padahal primary sudah Rp20.000. Laravel membagi query lewat read dan write di config/database.php; 'sticky' => true mengirim read ke primary setelah ada write, tapi cuma di request yang sama. Async cepat tapi saat failover perubahan yang belum sampai replica hilang; sync membuat commit menunggu replica, lebih lambat tapi gak ada yang hilang. Saat primary mati, failover mempromosikan replica yang paling up-to-date jadi primary baru, biasanya dalam 10 sampai 30 detik. Replica bukan backup.