Bali, Indonesia
Insights

Cara Cek Replikasi Database Sudah Sinkron Sebelum Cutover ke Server Baru

October 23, 2026
0:00
Saat migrasi database dengan replikasi, banyak orang hanya melihat satu angka sebelum memindahkan trafik: Seconds_Behind_Source sudah 0. Angka itu terasa seperti tanda aman, padahal tidak membuktikan replika sudah punya semua transaksi dari server lama. Kalau cutover dilakukan saat replika masih tertinggal beberapa transaksi, data itu tidak rusak. Data itu tertinggal di server lama yang sudah tidak dipakai, dan biasanya baru ketahuan saat ada pesanan atau pembayaran yang tidak tercatat di server baru. Untuk paham kenapa angka nol bisa menipu, kita perlu melihat jalur sebuah transaksi dari server lama ke replika. Contoh di artikel ini memakai replikasi asynchronous bawaan MySQL. Konsepnya mirip di MariaDB, tapi nama perintahnya bisa berbeda.
Setiap kali aplikasi melakukan commit di server lama (kita sebut source), MySQL menulis perubahan itu ke binary log, atau binlog. Binlog adalah catatan berurutan dari semua perubahan data. Setiap event di dalamnya punya posisi: nama file binlog dan offset di dalam file tersebut. Di sisi source ada thread yang disebut binlog dump thread. Tugasnya mengirim event binlog ke replika yang terhubung. Di sisi replika, prosesnya terbagi menjadi dua tahap yang berjalan terpisah:
  1. Replication I/O thread (receiver) menerima event dari source lalu menuliskannya ke relay log, yaitu salinan lokal binlog di server replika.
  2. Replication SQL thread (applier) membaca relay log dan benar-benar mengeksekusi perubahan itu ke tabel. Kalau replikasi paralel aktif, SQL thread membagi pekerjaannya ke beberapa worker.
Pemisahan ini yang sering terlewat. Replika punya dua posisi berbeda: sejauh mana event sudah diterima, dan sejauh mana event sudah diterapkan. Lag bisa terjadi di salah satu tahap. Kalau jaringan lambat atau koneksi putus, tahap terima yang tertinggal. Kalau disk replika lambat atau ada transaksi besar, tahap terapkan yang tertinggal. Cutover hanya aman kalau kedua tahap sudah mencapai posisi terakhir source.
Seconds_Behind_Source (di versi lama namanya Seconds_Behind_Master) dihitung dari selisih waktu antara jam replika saat ini dan timestamp event yang sedang diterapkan SQL thread. Artinya, angka ini hanya mengukur tahap kedua. SQL thread tidak tahu apa yang masih ada di source tapi belum dikirim. Akibatnya ada beberapa situasi di mana angkanya terlihat bagus padahal replika belum lengkap: I/O thread masih berjalan tapi lambat menerima event, misalnya karena bandwidth antar data center terbatas. SQL thread sudah menghabiskan semua isi relay log dan sedang menunggu. Dari sudut pandangnya tidak ada yang tertinggal, jadi angkanya 0. I/O thread berhenti karena error koneksi atau autentikasi. Dalam kondisi ini nilainya biasanya menjadi NULL, bukan angka besar. Kalau monitoring hanya memeriksa apakah nilainya di atas ambang batas, NULL bisa lolos tanpa alarm. Source sedang sepi. Kalau tidak ada transaksi baru, lag memang 0. Tapi begitu trafik datang lagi sesaat sebelum cutover, angka itu sudah tidak berlaku. Jam source dan replika berbeda. MySQL mencoba mengoreksi selisih jam saat replika terhubung, tapi kalau jam bergeser setelah itu, nilai detiknya bisa ikut melenceng. Kesimpulannya, Seconds_Behind_Source berguna untuk memantau tren selama sinkronisasi awal, tapi bukan bukti untuk keputusan cutover. Untuk itu kita butuh perbandingan posisi yang eksak.
Ambil posisi terakhir dari source, lalu bandingkan dengan dua posisi di replika. Kedua perintah berikut hanya membaca data dan aman dijalankan kapan saja.
SQL
-- Di server lama (source). MySQL 8.4 ke atas: SHOW BINARY LOG STATUS;
SHOW MASTER STATUS;

-- Di server baru (replika). Sebelum MySQL 8.0.22: SHOW SLAVE STATUS\G
SHOW REPLICA STATUS\G
Output dari replika cukup panjang. Kolom yang relevan untuk cutover kira-kira seperti ini (contoh format output, nilainya ilustrasi):
TEXT
             Replica_IO_Running: Yes
            Replica_SQL_Running: Yes
                Source_Log_File: mysql-bin.000120
            Read_Source_Log_Pos: 4500000
          Relay_Source_Log_File: mysql-bin.000120
            Exec_Source_Log_Pos: 4500000
          Seconds_Behind_Source: 0
                  Last_IO_Error:
                 Last_SQL_Error:
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
Cara membacanya: Replica_IO_Running dan Replica_SQL_Running harus sama-sama Yes. Kalau salah satunya No atau Connecting, berhenti di sini dan cek Last_IO_Error atau Last_SQL_Error. Isi kedua kolom error itu juga harus kosong. Source_Log_File dan Read_Source_Log_Pos menunjukkan posisi yang sudah diterima I/O thread. Bandingkan dengan kolom File dan Position dari SHOW MASTER STATUS di source. Kalau berbeda, berarti masih ada event yang belum sampai ke replika. Relay_Source_Log_File dan Exec_Source_Log_Pos menunjukkan posisi yang sudah diterapkan SQL thread. Posisi ini dihitung dalam koordinat binlog source, jadi bisa langsung dibandingkan dengan output di source. Replika baru dianggap sinkron kalau posisi terima dan posisi terapkan sama-sama sama dengan posisi source. Perbandingan ini hanya berarti kalau source sedang tidak menerima penulisan. Kalau aplikasi masih menulis, posisi source terus bergerak dan hasil perbandingan sudah usang begitu kamu selesai membacanya. Dari sisi support, ini juga pola yang sering muncul. Ada yang bilang replikasinya sudah sinkron, tapi buktinya hanya screenshot lag 0 tanpa posisi source. Yang paling berguna untuk diminta adalah output lengkap SHOW REPLICA STATUS beserta posisi source yang diambil pada saat yang sama, setelah penulisan dihentikan.
Membandingkan nama file dan offset bisa membingungkan, apalagi kalau binlog sempat di-rotate. Kalau replikasi memakai GTID (Global Transaction Identifier), pengecekannya lebih mudah. Setiap transaksi punya ID unik berformat uuid_server:nomor_urut. Server menyimpan himpunan semua GTID yang sudah ia jalankan di variabel gtid_executed. Logikanya sederhana. Setelah penulisan di source dihentikan, ambil nilai gtid_executed dari source. Lalu minta replika menunggu sampai seluruh himpunan itu selesai diterapkan. Fungsi WAIT_FOR_EXECUTED_GTID_SET menangani hal ini dan mengembalikan 0 kalau berhasil atau 1 kalau timeout. Perhatikan bahwa langkah pertama di bawah mengubah state server produksi, jadi bukan sekadar diagnostik. Jalankan hanya saat maintenance window sudah dimulai dan aplikasi siap berhenti menulis.
SQL
-- 1. Server lama: hentikan semua penulisan (otomatis menyalakan read_only juga)
SET GLOBAL super_read_only = ON;

-- 2. Server lama: catat himpunan GTID yang sudah di-commit
SELECT @@GLOBAL.gtid_executed;

-- 3. Server baru: tunggu sampai himpunan itu selesai diterapkan, timeout 60 detik
SELECT WAIT_FOR_EXECUTED_GTID_SET('tempel_hasil_langkah_2', 60);
Pakai super_read_only, bukan hanya read_only. read_only biasa masih mengizinkan user dengan privilege tingkat tinggi seperti SUPER untuk menulis. Kalau aplikasi terhubung memakai user root, read_only saja tidak cukup untuk membekukan data. Kalau hasil langkah 3 adalah 0, berarti setiap transaksi yang pernah di-commit di source sudah ada di replika. Kalau hasilnya 1, jangan diulang terus sambil berharap hasilnya berubah. Cek dulu status thread dan kolom error di SHOW REPLICA STATUS. Untuk MariaDB, format GTID-nya berbeda (domain-server-urutan), dan fungsi tunggunya juga berbeda. Cek dokumentasi versi MariaDB yang kamu pakai sebelum menyalin perintah di atas.
Setelah mekanismenya jelas, urutannya jadi masuk akal:
  1. Hentikan semua sumber penulisan ke server lama. Ini mencakup aplikasi utama, cron, queue worker, dan proses lain yang menulis ke database. super_read_only adalah pengaman terakhir, bukan pengganti mematikan worker. Kalau worker masih jalan, mereka akan error saat mencoba menulis.
  2. Ambil posisi akhir source, berupa binlog file dan position atau gtid_executed.
  3. Di replika, pastikan kedua thread berstatus Yes, kolom error kosong, dan posisi terima serta posisi terapkan sudah sama dengan posisi source. Kalau memakai GTID, gunakan WAIT_FOR_EXECUTED_GTID_SET.
  4. Hentikan replikasi di server baru dengan STOP REPLICA. Kalau ingin membuang konfigurasi replikasi sepenuhnya, ada perintah reset, tapi efeknya tidak bisa dibatalkan. Sebaiknya lakukan setelah server baru terbukti stabil.
  5. Buat server baru bisa ditulis dengan mematikan super_read_only dan read_only, lalu arahkan aplikasi ke server baru. Kalau pemindahan memakai DNS, ingat bahwa sebagian client bisa masih ke IP lama untuk sementara. Pembahasannya ada di Kenapa Website Masih ke Server Lama Padahal TTL Sudah Diturunkan.
  6. Biarkan server lama tetap dalam mode read-only. Jangan buka penulisan di sana lagi. Kalau ada client yang masih nyasar, lebih baik mereka mendapat error yang terlihat daripada menulis data yang diam-diam tidak pernah sampai ke server baru.
Langkah 6 juga berfungsi sebagai jalan balik. Selama server lama tidak menerima data baru, kamu tahu persis posisi terakhirnya dan bisa membandingkannya kalau ada yang janggal. Intinya, perlakukan Seconds_Behind_Source sebagai indikator kemajuan, bukan bukti sinkron. Bukti yang bisa dipegang adalah penulisan yang sudah dibekukan di source, dua thread replika yang sehat, dan posisi binlog atau GTID yang sama persis di kedua sisi. Kalau ketiganya terpenuhi, cutover tinggal soal memindahkan koneksi. Kalau salah satunya belum, waktu tambahan beberapa menit di maintenance window jauh lebih murah daripada mencari transaksi yang tertinggal di server lama.