Replikasi yang tertunda memungkinkan slave replikasi untuk secara sengaja tertinggal di belakang master setidaknya dalam jangka waktu tertentu. Sebelum mengeksekusi suatu event, slave terlebih dahulu akan menunggu, jika perlu, hingga waktu yang ditentukan telah berlalu sejak event tersebut dibuat pada master. Hasilnya adalah bahwa budak akan mencerminkan keadaan tuannya beberapa waktu lalu. Fitur ini didukung sejak MySQL 5.6 dan MariaDB 10.2.3. Ini dapat berguna jika terjadi penghapusan data yang tidak disengaja, dan harus menjadi bagian dari rencana pemulihan bencana Anda.
Masalah saat menyiapkan budak replikasi tertunda adalah berapa banyak penundaan yang harus kita lakukan. Waktu yang terlalu singkat dan Anda mempertaruhkan kueri yang buruk untuk mendapatkan budak yang tertunda sebelum Anda dapat melakukannya, sehingga membuang-buang waktu untuk memiliki budak yang tertunda. Secara opsional, Anda dapat membuat waktu tunda Anda menjadi sangat lama sehingga diperlukan waktu berjam-jam bagi slave Anda yang tertunda untuk mengejar tempat master berada pada saat kesalahan.
Untungnya dengan Docker, isolasi proses adalah kekuatannya. Menjalankan beberapa instance MySQL cukup nyaman dengan Docker. Hal ini memungkinkan kita untuk memiliki beberapa budak tertunda dalam satu host fisik untuk meningkatkan waktu pemulihan dan menghemat sumber daya perangkat keras. Jika menurut Anda penundaan 15 menit terlalu singkat, kami dapat membuat instance lain dengan penundaan 1 jam atau 6 jam untuk snapshot database kami yang lebih lama.
Dalam posting blog ini, kita akan menerapkan beberapa budak tertunda MySQL pada satu host fisik tunggal dengan Docker, dan menunjukkan beberapa skenario pemulihan. Diagram berikut mengilustrasikan arsitektur akhir yang ingin kita bangun:
Arsitektur kami terdiri dari Replikasi MySQL 2-simpul yang sudah digunakan yang berjalan di server fisik (biru) dan kami ingin menyiapkan tiga budak MySQL lainnya (hijau) dengan perilaku berikut:
- penundaan 15 menit
- Keterlambatan 1 jam
- tertunda 6 jam
Perhatikan bahwa kita akan memiliki 3 salinan data yang sama persis di server fisik yang sama. Pastikan host Docker kami memiliki penyimpanan yang diperlukan, jadi alokasikan ruang disk yang cukup sebelumnya.
Persiapan Master MySQL
Pertama, login ke server master dan buat pengguna replikasi:
mysql> GRANT REPLICATION SLAVE ON *.* TO [email protected]'%' IDENTIFIED BY 'YlgSH6bLLy';
Kemudian, buat cadangan yang kompatibel dengan PITR di master:
$ mysqldump -uroot -p --flush-privileges --hex-blob --opt --master-data=1 --single-transaction --skip-lock-tables --skip-lock-tables --triggers --routines --events --all-databases | gzip -6 -c > mysqldump_complete.sql.gz
Jika Anda menggunakan ClusterControl, Anda dapat membuat cadangan yang kompatibel dengan PITR dengan mudah. Buka Cadangan -> Buat Cadangan dan pilih "Complete PITR-compatible" di bawah dropdown "Dump Type":
Terakhir, transfer cadangan ini ke host Docker:
$ scp mysqldump_complete.sql.gz [email protected]:~
File cadangan ini akan digunakan oleh wadah budak MySQL selama proses bootstrap budak, seperti yang ditunjukkan di bagian berikutnya.
Penerapan Budak Tertunda
Siapkan direktori container Docker kami. Buat 3 direktori (mysql.conf.d, datadir dan sql) untuk setiap container MySQL yang akan kita luncurkan (Anda dapat menggunakan loop untuk menyederhanakan perintah di bawah):
$ mkdir -p /storage/mysql-slave-15m/mysql.conf.d
$ mkdir -p /storage/mysql-slave-15m/datadir
$ mkdir -p /storage/mysql-slave-15m/sql
$ mkdir -p /storage/mysql-slave-1h/mysql.conf.d
$ mkdir -p /storage/mysql-slave-1h/datadir
$ mkdir -p /storage/mysql-slave-1h/sql
$ mkdir -p /storage/mysql-slave-6h/mysql.conf.d
$ mkdir -p /storage/mysql-slave-6h/datadir
$ mkdir -p /storage/mysql-slave-6h/sql
Direktori "mysql.conf.d" akan menyimpan file konfigurasi MySQL kustom kami dan akan dipetakan ke dalam wadah di bawah /etc/mysql.conf.d. "datadir" adalah tempat kami ingin Docker menyimpan direktori data MySQL, yang memetakan ke /var/lib/mysql dari wadah dan direktori "sql" menyimpan file SQL kami - file cadangan dalam format .sql atau .sql.gz ke stage slave sebelum mereplikasi dan juga file .sql untuk mengotomatiskan konfigurasi replikasi dan startup.
Budak Tertunda 15 menit
Siapkan file konfigurasi MySQL untuk budak tertunda 15 menit kami:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
Dan tambahkan baris berikut:
[mysqld]
server_id=10015
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON
** Nilai server-id yang kami gunakan untuk slave ini adalah 10015.
Selanjutnya, di bawah direktori /storage/mysql-slave-15m/sql, buat dua file SQL, satu untuk RESET MASTER (1reset_master.sql) dan satu lagi untuk membuat link replikasi menggunakan pernyataan CHANGE MASTER (3setup_slave.sql).
Buat file teks 1reset_master.sql dan tambahkan baris berikut:
RESET MASTER;
Buat file teks 3setup_slave.sql dan tambahkan baris berikut:
CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=900;
START SLAVE;
MASTER_DELAY=900 sama dengan 15 menit (dalam detik). Kemudian salin file cadangan yang diambil dari master kami (yang telah ditransfer ke host Docker kami) ke direktori "sql" dan beri nama 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-15m/sql/2mysqldump_complete.tar.gz
Tampilan akhir dari direktori "sql" kita seharusnya seperti ini:
$ pwd
/storage/mysql-slave-15m/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Perhatikan bahwa kami mengawali nama file SQL dengan bilangan bulat untuk menentukan urutan eksekusi saat Docker menginisialisasi wadah MySQL.
Setelah semuanya siap, jalankan penampung MySQL untuk budak tertunda 15 menit kami:
$ docker run -d \
--name mysql-slave-15m \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-15m/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-15m/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-15m/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7
** Nilai MYSQL_ROOT_PASSWORD harus sama dengan kata sandi root MySQL pada master.
Baris berikut adalah apa yang kami cari untuk memverifikasi apakah MySQL berjalan dengan benar dan terhubung sebagai budak ke master kami (192.168.55.171):
$ docker logs -f mysql-slave-15m
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4
Anda kemudian dapat memverifikasi status replikasi dengan pernyataan berikut:
$ docker exec -it mysql-slave-15m mysql -uroot -p -e 'show slave status\G'
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
SQL_Delay: 900
Auto_Position: 1
...
Pada titik ini, penampung budak tertunda 15 menit kami mereplikasi dengan benar dan arsitektur kami terlihat seperti ini:
Budak Tertunda 1 jam
Siapkan file konfigurasi MySQL untuk budak tertunda 1 jam kami:
$ vim /storage/mysql-slave-1h/mysql.conf.d/my.cnf
Dan tambahkan baris berikut:
[mysqld]
server_id=10060
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON
** Nilai server-id yang kami gunakan untuk slave ini adalah 10060.
Selanjutnya, di bawah direktori /storage/mysql-slave-1h/sql, buat dua file SQL, satu untuk RESET MASTER (1reset_master.sql) dan satu lagi untuk membuat link replikasi menggunakan pernyataan CHANGE MASTER (3setup_slave.sql).
Buat file teks 1reset_master.sql dan tambahkan baris berikut:
RESET MASTER;
Buat file teks 3setup_slave.sql dan tambahkan baris berikut:
CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=3600;
START SLAVE;
MASTER_DELAY=3600 sama dengan 1 jam (dalam detik). Kemudian salin file cadangan yang diambil dari master kami (yang telah ditransfer ke host Docker kami) ke direktori "sql" dan beri nama 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-1h/sql/2mysqldump_complete.tar.gz
Tampilan akhir dari direktori "sql" kita seharusnya seperti ini:
$ pwd
/storage/mysql-slave-1h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Perhatikan bahwa kami mengawali nama file SQL dengan bilangan bulat untuk menentukan urutan eksekusi saat Docker menginisialisasi wadah MySQL.
Setelah semuanya siap, jalankan penampung MySQL untuk budak tertunda 1 jam kami:
$ docker run -d \
--name mysql-slave-1h \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-1h/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-1h/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-1h/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7
** Nilai MYSQL_ROOT_PASSWORD harus sama dengan kata sandi root MySQL pada master.
Baris berikut adalah apa yang kami cari untuk memverifikasi apakah MySQL berjalan dengan benar dan terhubung sebagai budak ke master kami (192.168.55.171):
$ docker logs -f mysql-slave-1h
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4
Anda kemudian dapat memverifikasi status replikasi dengan pernyataan berikut:
$ docker exec -it mysql-slave-1h mysql -uroot -p -e 'show slave status\G'
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
SQL_Delay: 3600
Auto_Position: 1
...
Pada titik ini, penampung budak tertunda MySQL 15 menit dan 1 jam kami mereplikasi dari master dan arsitektur kami terlihat seperti ini:
Budak Tertunda 6 jam
Siapkan file konfigurasi MySQL untuk budak tertunda 6 jam kami:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
Dan tambahkan baris berikut:
[mysqld]
server_id=10006
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON
** Nilai server-id yang kami gunakan untuk slave ini adalah 10006.
Selanjutnya, di bawah direktori /storage/mysql-slave-6h/sql, buat dua file SQL, satu untuk RESET MASTER (1reset_master.sql) dan satu lagi untuk membuat link replikasi menggunakan pernyataan CHANGE MASTER (3setup_slave.sql).
Buat file teks 1reset_master.sql dan tambahkan baris berikut:
RESET MASTER;
Buat file teks 3setup_slave.sql dan tambahkan baris berikut:
CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=21600;
START SLAVE;
MASTER_DELAY=21600 sama dengan 6 jam (dalam detik). Kemudian salin file cadangan yang diambil dari master kami (yang telah ditransfer ke host Docker kami) ke direktori "sql" dan beri nama 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-6h/sql/2mysqldump_complete.tar.gz
Tampilan akhir dari direktori "sql" kita seharusnya seperti ini:
$ pwd
/storage/mysql-slave-6h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Perhatikan bahwa kami mengawali nama file SQL dengan bilangan bulat untuk menentukan urutan eksekusi saat Docker menginisialisasi wadah MySQL.
Setelah semuanya siap, jalankan penampung MySQL untuk budak tertunda 6 jam kami:
$ docker run -d \
--name mysql-slave-6h \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-6h/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-6h/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-6h/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7
** Nilai MYSQL_ROOT_PASSWORD harus sama dengan kata sandi root MySQL pada master.
Baris berikut adalah apa yang kami cari untuk memverifikasi apakah MySQL berjalan dengan benar dan terhubung sebagai budak ke master kami (192.168.55.171):
$ docker logs -f mysql-slave-6h
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4
Anda kemudian dapat memverifikasi status replikasi dengan pernyataan berikut:
$ docker exec -it mysql-slave-6h mysql -uroot -p -e 'show slave status\G'
...
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
SQL_Delay: 21600
Auto_Position: 1
...
Pada titik ini, container slave tertunda 5 menit, 1 jam, dan 6 jam kami mereplikasi dengan benar dan arsitektur kami terlihat seperti ini:
Skenario Pemulihan Bencana
Katakanlah pengguna secara tidak sengaja menjatuhkan kolom yang salah di meja besar. Perhatikan pernyataan berikut yang dieksekusi pada master:
mysql> USE shop;
mysql> ALTER TABLE settings DROP COLUMN status;
Jika Anda cukup beruntung untuk segera menyadarinya, Anda dapat menggunakan budak tertunda 15 menit untuk mengejar momen sebelum bencana terjadi dan mempromosikannya menjadi master, atau mengekspor data yang hilang dan mengembalikannya ke master.
Pertama, kita harus menemukan posisi log biner sebelum bencana terjadi. Ambil waktu sekarang() pada master:
mysql> SELECT now();
+---------------------+
| now() |
+---------------------+
| 2018-12-04 14:55:41 |
+---------------------+
Kemudian, dapatkan file log biner aktif di master:
mysql> SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| binlog.000004 | 20260658 | | | 1560665e-ed2b-11e8-93fa-000c29b7f985:1-12031,
1b235f7a-d37b-11e8-9c3e-000c29bafe8f:1-62519,
1d8dc60a-e817-11e8-82ff-000c29bafe8f:1-326575,
791748b3-d37a-11e8-b03a-000c29b7f985:1-374 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Menggunakan format tanggal yang sama, ekstrak informasi yang kita inginkan dari log biner, binlog.000004. Kami memperkirakan waktu mulai membaca dari binlog sekitar 20 menit yang lalu (04-12-2018 14:35:00) dan memfilter output untuk menampilkan 25 baris sebelum pernyataan "jatuhkan kolom":
$ mysqlbinlog --start-datetime="2018-12-04 14:35:00" --stop-datetime="2018-12-04 14:55:41" /var/lib/mysql/binlog.000004 | grep -i -B 25 "drop column"
'/*!*/;
# at 19379172
#181204 14:54:45 server id 1 end_log_pos 19379232 CRC32 0x0716e7a2 Table_map: `shop`.`settings` mapped to number 766
# at 19379232
#181204 14:54:45 server id 1 end_log_pos 19379460 CRC32 0xa6187edd Write_rows: table id 766 flags: STMT_END_F
BINLOG '
tSQGXBMBAAAAPAAAACC0JwEAAP4CAAAAAAEABnNidGVzdAAHc2J0ZXN0MgAFAwP+/gME/nj+PBCi
5xYH
tSQGXB4BAAAA5AAAAAS1JwEAAP4CAAAAAAEAAgAF/+AYwwAAysYAAHc0ODYyMjI0NjI5OC0zNDE2
OTY3MjY5OS02MDQ1NTQwOTY1Ny01MjY2MDQ0MDcwOC05NDA0NzQzOTUwMS00OTA2MTAxNzgwNC05
OTIyMzM3NzEwOS05NzIwMzc5NTA4OC0yODAzOTU2NjQ2MC0zNzY0ODg3MTYzOTswMTM0MjAwNTcw
Ni02Mjk1ODMzMzExNi00NzQ1MjMxODA1OS0zODk4MDQwMjk5MS03OTc4MTA3OTkwNQEAAADdfhim
'/*!*/;
# at 19379460
#181204 14:54:45 server id 1 end_log_pos 19379491 CRC32 0x71f00e63 Xid = 622405
COMMIT/*!*/;
# at 19379491
#181204 14:54:46 server id 1 end_log_pos 19379556 CRC32 0x62b78c9e GTID last_committed=11507 sequence_number=11508 rbr_only=no
SET @@SESSION.GTID_NEXT= '1560665e-ed2b-11e8-93fa-000c29b7f985:11508'/*!*/;
# at 19379556
#181204 14:54:46 server id 1 end_log_pos 19379672 CRC32 0xc222542a Query thread_id=3162 exec_time=1 error_code=0
SET TIMESTAMP=1543906486/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
ALTER TABLE settings DROP COLUMN status
Di beberapa baris bawah dari output mysqlbinlog, Anda harus memiliki perintah yang salah yang dieksekusi pada posisi 19379556. Posisi yang harus kita pulihkan adalah satu langkah sebelumnya, yaitu pada posisi 19379491. Ini adalah posisi binlog yang kita inginkan. budak tertunda hingga.
Kemudian, pada slave tertunda yang dipilih, hentikan slave replikasi yang tertunda dan mulai lagi slave ke posisi akhir tetap yang kami temukan di atas:
$ docker exec -it mysql-slave-15m mysql -uroot -p
mysql> STOP SLAVE;
mysql> START SLAVE UNTIL MASTER_LOG_FILE = 'binlog.000004', MASTER_LOG_POS = 19379491;
Pantau status replikasi dan tunggu hingga Exec_Master_Log_Pos sama dengan nilai Hingga_Log_Pos. Ini bisa memakan waktu. Setelah tertangkap, Anda akan melihat yang berikut:
$ docker exec -it mysql-slave-15m mysql -uroot -p -e 'SHOW SLAVE STATUS\G'
...
Exec_Master_Log_Pos: 19379491
Relay_Log_Space: 50552186
Until_Condition: Master
Until_Log_File: binlog.000004
Until_Log_Pos: 19379491
...
Terakhir verifikasi apakah data hilang yang kita cari ada (kolom "status" masih ada):
mysql> DESCRIBE shop.settings;
+--------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| sid | int(10) unsigned | NO | MUL | 0 | |
| param | varchar(100) | NO | | | |
| value | varchar(255) | NO | | | |
| status | int(11) | YES | | 1 | |
+--------+------------------+------+-----+---------+----------------+
Kemudian ekspor tabel dari wadah budak kami dan transfer ke server master:
$ docker exec -it mysql-slave-1h mysqldump -uroot -ppassword --single-transaction shop settings > shop_settings.sql
Jatuhkan tabel yang bermasalah dan kembalikan ke master:
$ mysql -uroot -p -e 'DROP TABLE shop.settings'
$ mysqldump -uroot -p -e shop < shop_setttings.sql
Kami sekarang telah memulihkan meja kami kembali ke keadaan semula sebelum peristiwa bencana. Singkatnya, replikasi tertunda dapat digunakan untuk beberapa tujuan:
- Untuk melindungi dari kesalahan pengguna pada master. DBA dapat memutar kembali slave yang tertunda ke waktu tepat sebelum bencana.
- Untuk menguji bagaimana sistem berperilaku ketika ada lag. Misalnya, dalam sebuah aplikasi, lag mungkin disebabkan oleh beban yang berat pada slave. Namun, mungkin sulit untuk menghasilkan tingkat beban ini. Replikasi yang tertunda dapat mensimulasikan lag tanpa harus mensimulasikan beban. Ini juga dapat digunakan untuk men-debug kondisi yang terkait dengan slave yang tertinggal.
- Untuk memeriksa seperti apa database sebelumnya, tanpa harus memuat ulang cadangan. Misalnya, jika penundaannya adalah satu minggu dan DBA perlu melihat seperti apa database sebelum pengembangan beberapa hari terakhir, slave yang tertunda dapat diperiksa.
Pemikiran Terakhir
Dengan Docker, menjalankan beberapa instance MySQL pada host fisik yang sama dapat dilakukan secara efisien. Anda dapat menggunakan alat orkestrasi Docker seperti Docker Compose dan Swarm untuk menyederhanakan penerapan multi-kontainer yang bertentangan dengan langkah-langkah yang ditampilkan dalam entri blog ini.