遅延レプリケーションにより、レプリケーションスレーブは、少なくとも指定された時間だけマスターよりも意図的に遅れることができます。イベントを実行する前に、スレーブは、必要に応じて、イベントがマスターで作成されてから指定された時間が経過するまで、最初に待機します。その結果、スレーブは過去のある時点でマスターの状態を反映します。この機能は、MySQL5.6およびMariaDB10.2.3以降でサポートされています。誤ってデータを削除した場合に便利であり、災害復旧計画の一部にする必要があります。
遅延レプリケーションスレーブを設定する際の問題は、どれだけの遅延を設定する必要があるかです。時間が短すぎると、遅延したスレーブに到達する前に不正なクエリが遅延したスレーブに到達するリスクがあり、遅延したスレーブを持つという点が無駄になります。必要に応じて、遅延時間を長くして、遅延したスレーブがエラー時のマスターの位置に追いつくまでに数時間かかるようにすることができます。
幸い、Dockerの場合、プロセスの分離がその強みです。 Dockerを使用すると、複数のMySQLインスタンスを実行すると非常に便利です。これにより、単一の物理ホスト内に複数の遅延スレーブを配置して、リカバリ時間を改善し、ハードウェアリソースを節約できます。 15分の遅延が短すぎると思われる場合は、1時間の遅延、またはデータベースのさらに古いスナップショットの6時間の別のインスタンスを作成できます。
このブログ投稿では、Dockerを使用して複数のMySQL遅延スレーブを単一の物理ホストにデプロイし、いくつかのリカバリシナリオを示します。次の図は、構築する最終的なアーキテクチャを示しています。
私たちのアーキテクチャは、物理サーバー上で実行されているすでにデプロイされた2ノードのMySQLレプリケーション(青)で構成されており、次の動作でさらに3つのMySQLスレーブ(緑)をセットアップしたいと考えています。
- 15分の遅延
- 1時間の遅延
- 6時間の遅延
同じ物理サーバー上にまったく同じデータのコピーが3つあることに注意してください。 Dockerホストに必要なストレージがあることを確認してください。そのため、事前に十分なディスク容量を割り当ててください。
MySQLマスターの準備
まず、マスターサーバーにログインして、レプリケーションユーザーを作成します。
mysql> GRANT REPLICATION SLAVE ON *.* TO [email protected]'%' IDENTIFIED BY 'YlgSH6bLLy';
次に、マスター上にPITR互換のバックアップを作成します。
$ 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
ClusterControlを使用している場合は、PITR互換のバックアップを簡単に作成できます。 [バックアップ]->[バックアップの作成]に移動し、[ダンプの種類]ドロップダウンで[完全なPITR互換]を選択します。
最後に、このバックアップをDockerホストに転送します。
$ scp mysqldump_complete.sql.gz [email protected]:~
このバックアップファイルは、次のセクションに示すように、スレーブブートストラッププロセス中にMySQLスレーブコンテナによって使用されます。
スレーブ展開の遅延
Dockerコンテナディレクトリを準備します。起動するMySQLコンテナごとに3つのディレクトリ(mysql.conf.d、datadir、sql)を作成します(ループを使用して、以下のコマンドを簡略化できます):
$ 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
「mysql.conf.d」ディレクトリはカスタムMySQL構成ファイルを保存し、/ etc/mysql.conf.dの下のコンテナにマップされます。 「datadir」は、DockerがMySQLデータディレクトリを格納する場所です。これは、コンテナの/ var / lib / mysqlにマップされ、「sql」ディレクトリは、SQLファイル(ステージングするための.sqlまたは.sql.gz形式のバックアップファイル)を格納します。複製する前のスレーブと、複製の構成と起動を自動化するための.sqlファイル。
15分の遅延スレーブ
15分の遅延スレーブ用にMySQL構成ファイルを準備します:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
そして、次の行を追加します:
[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
**このスレーブに使用したserver-id値は10015です。
次に、/ storage / mysql-slave-15m /sqlディレクトリの下に2つのSQLファイルを作成します。1つはRESETMASTER(1reset_master.sql)に、もう1つはCHANGE MASTERステートメント(3setup_slave.sql)を使用してレプリケーションリンクを確立します。
>テキストファイル1reset_master.sqlを作成し、次の行を追加します。
RESET MASTER;
テキストファイル3setup_slave.sqlを作成し、次の行を追加します。
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は15分(秒単位)に相当します。次に、マスターから取得したバックアップファイル(Dockerホストに転送されたもの)を「sql」ディレクトリにコピーし、名前を2mysqldump_complete.sql.gz:
に変更します。$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-15m/sql/2mysqldump_complete.tar.gz
「sql」ディレクトリの最終的な外観は次のようになります。
$ pwd
/storage/mysql-slave-15m/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
DockerがMySQLコンテナを初期化するときの実行順序を決定するために、SQLファイル名の前に整数を付けることに注意してください。
すべてが整ったら、15分の遅延スレーブのMySQLコンテナを実行します。
$ 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
** MYSQL_ROOT_PASSWORD値は、マスターのMySQLルートパスワードと同じである必要があります。
次の行は、MySQLが正しく実行され、スレーブとしてマスター(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
次に、次のステートメントを使用してレプリケーションステータスを確認できます。
$ 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
...
この時点で、15分の遅延スレーブコンテナは正しく複製されており、アーキテクチャは次のようになっています。
1時間の遅延スレーブ
1時間の遅延スレーブ用にMySQL構成ファイルを準備します:
$ vim /storage/mysql-slave-1h/mysql.conf.d/my.cnf
そして、次の行を追加します:
[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
**このスレーブに使用したserver-id値は10060です。
次に、/ storage / mysql-slave-1h /sqlディレクトリの下に2つのSQLファイルを作成します。1つはRESETMASTER(1reset_master.sql)に、もう1つはCHANGE MASTERステートメント(3setup_slave.sql)を使用してレプリケーションリンクを確立します。
>テキストファイル1reset_master.sqlを作成し、次の行を追加します。
RESET MASTER;
テキストファイル3setup_slave.sqlを作成し、次の行を追加します。
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は1時間(秒単位)に相当します。次に、マスターから取得したバックアップファイル(Dockerホストに転送されたもの)を「sql」ディレクトリにコピーし、名前を2mysqldump_complete.sql.gz:
に変更します。$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-1h/sql/2mysqldump_complete.tar.gz
「sql」ディレクトリの最終的な外観は次のようになります。
$ pwd
/storage/mysql-slave-1h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
DockerがMySQLコンテナを初期化するときの実行順序を決定するために、SQLファイル名の前に整数を付けることに注意してください。
すべてが整ったら、1時間遅延したスレーブのMySQLコンテナを実行します。
$ 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
** MYSQL_ROOT_PASSWORD値は、マスターのMySQLルートパスワードと同じである必要があります。
次の行は、MySQLが正しく実行され、スレーブとしてマスター(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
次に、次のステートメントを使用してレプリケーションステータスを確認できます。
$ 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
...
この時点で、15分と1時間のMySQL遅延スレーブコンテナがマスターから複製されており、アーキテクチャは次のようになっています。
6時間の遅延スレーブ
6時間の遅延スレーブ用にMySQL構成ファイルを準備します:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
そして、次の行を追加します:
[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
**このスレーブに使用したserver-id値は10006です。
次に、/ storage / mysql-slave-6h /sqlディレクトリの下に2つのSQLファイルを作成します。1つはRESETMASTER(1reset_master.sql)に、もう1つはCHANGE MASTERステートメント(3setup_slave.sql)を使用してレプリケーションリンクを確立します。
>テキストファイル1reset_master.sqlを作成し、次の行を追加します。
RESET MASTER;
テキストファイル3setup_slave.sqlを作成し、次の行を追加します。
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は6時間(秒単位)に相当します。次に、マスターから取得したバックアップファイル(Dockerホストに転送されたもの)を「sql」ディレクトリにコピーし、名前を2mysqldump_complete.sql.gz:
に変更します。$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-6h/sql/2mysqldump_complete.tar.gz
「sql」ディレクトリの最終的な外観は次のようになります。
$ pwd
/storage/mysql-slave-6h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
DockerがMySQLコンテナを初期化するときの実行順序を決定するために、SQLファイル名の前に整数を付けることに注意してください。
すべてが整ったら、6時間遅延したスレーブのMySQLコンテナを実行します。
$ 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
** MYSQL_ROOT_PASSWORD値は、マスターのMySQLルートパスワードと同じである必要があります。
次の行は、MySQLが正しく実行され、スレーブとしてマスター(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
次に、次のステートメントを使用してレプリケーションステータスを確認できます。
$ 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
...
この時点で、5分、1時間、および6時間の遅延スレーブコンテナは正しく複製されており、アーキテクチャは次のようになっています。
災害復旧シナリオ
ユーザーが誤って大きなテーブルに間違った列をドロップしたとしましょう。次のステートメントがマスターで実行されたと考えてください。
mysql> USE shop;
mysql> ALTER TABLE settings DROP COLUMN status;
運が良ければすぐに気付くことができれば、15分の遅延スレーブを使用して、災害が発生する直前に追いつき、マスターになるように昇格させるか、不足しているデータをエクスポートしてマスターに復元することができます。
まず、災害が発生する前にバイナリログの位置を見つける必要があります。マスターでnow()の時間を取得します:
mysql> SELECT now();
+---------------------+
| now() |
+---------------------+
| 2018-12-04 14:55:41 |
+---------------------+
次に、マスターでアクティブなバイナリログファイルを取得します。
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 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
同じ日付形式を使用して、バイナリログbinlog.000004から必要な情報を抽出します。約20分前(2018-12-04 14:35:00)にbinlogから読み取る開始時間を見積もり、「dropcolumn」ステートメントの前に25行を表示するように出力をフィルタリングします。
$ 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
mysqlbinlog出力の下部の数行に、位置19379556で実行された誤ったコマンドがあるはずです。復元する位置は、この1ステップ前の位置19379491です。これは、スレーブが最大になるのを遅らせました。
次に、選択した遅延スレーブで、遅延レプリケーションスレーブを停止し、上記で計算した固定の終了位置までスレーブを再開します。
$ 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;
レプリケーションステータスを監視し、Exec_Master_Log_PosがUntil_Log_Pos値と等しくなるまで待ちます。これには時間がかかる場合があります。追いつくと、次のように表示されます。
$ 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
...
最後に、探していた欠落データが存在するかどうかを確認します(「ステータス」列がまだ存在します):
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 | |
+--------+------------------+------+-----+---------+----------------+
次に、テーブルをスレーブコンテナからエクスポートし、マスターサーバーに転送します。
$ docker exec -it mysql-slave-1h mysqldump -uroot -ppassword --single-transaction shop settings > shop_settings.sql
問題のあるテーブルを削除して、マスターに復元します:
$ mysql -uroot -p -e 'DROP TABLE shop.settings'
$ mysqldump -uroot -p -e shop < shop_setttings.sql
悲惨な出来事が発生する前の元の状態にテーブルを復元しました。要約すると、遅延レプリケーションはいくつかの目的に使用できます。
- マスターでのユーザーのミスから保護するため。 DBAは、遅延したスレーブを災害直前の時間にロールバックできます。
- 遅延がある場合のシステムの動作をテストするため。たとえば、アプリケーションでは、スレーブの重い負荷が原因でラグが発生する場合があります。ただし、この負荷レベルを生成するのは難しい場合があります。遅延レプリケーションでは、負荷をシミュレートしなくてもラグをシミュレートできます。また、遅れているスレーブに関連する状態をデバッグするためにも使用できます。
- バックアップをリロードせずに、データベースが過去にどのように表示されたかを検査するため。たとえば、遅延が1週間で、DBAが過去数日間の開発に相当する前にデータベースがどのように見えるかを確認する必要がある場合、遅延したスレーブを検査できます。
最終的な考え
Dockerを使用すると、同じ物理ホスト上で複数のMySQLインスタンスを効率的に実行できます。このブログ投稿に示されている手順とは対照的に、Docker ComposeやSwarmなどのDockerオーケストレーションツールを使用して、マルチコンテナーのデプロイを簡素化できます。