この記事はニフティグループ Advent Calendar 2019の20日目の記事です。
はじめに
弊社ではWebのインフラとしてAWSの導入を進めており、様々なサービスがAWSに移行されつつあります。
先日、私の担当サービスも無事にAWSへの移行が完了しました。その時に苦戦したDBの移行方法を共有いたします。
なお、AWS CLI
のインストールやRDSインスタンスの作成手順などは割愛します。
また、この方法では移行先DBから移行元DBへの疎通が通っていることが前提となります。技術的、セキュリティルール的に疎通を通すことが不可能である場合は、以下の手順は実行できませんのでご了承下さい。
TL;DR
今回移行したDBは以下のようなものです。
- MySQL 5.6
- データ量約200GB
- 常にデータ更新がかかっている(DBの停止=サービス停止)
データ量が多い点と、常に更新が掛かっている点がネックでした。単純にdump/restoreを実行すると約10時間ほどのサービス停止が必要になり、より短い時間での移行を検討する必要がありました(停止しないと移行中も新しいデータが追加されるため、差分が生まれてしまう)。
そこで、MySQLの外部レプリケーション機能を使って、dump/restore後に移行元/移行先のDBの差分を常に更新し続け、DBを切り替えるタイミングでレプリケーションを停止する手法を採用しました。
移行方法
今回行った移行手順を図にすると以下のようになります。NATGatewayを使ってグローバル通信をする図になっていますが、VPN経由でも問題ないと思われます。
順を追って説明いたします。
①移行元DBのバイナリログ保持期間を変更する
さて、レプリケーションを使ってDB間の同期をするためには、バイナリログ(DBへの変更操作がすべて記述されたログ)が必要になります。このログは保持期間が決められており、古いものから順に削除されてしまいます。最低でもdumpを開始してからレプリケーションをスタートさせるまでの間はログが残っていないとなりません。
dumpを開始する前にバイナリログ保持期間を変更し、ログが保持されることを確実にしておきましょう。
通常はMySQLのシステム変数であるexpire_logs_days
を以下のように変更することで、期間を変更できます。
一部のマネージドサービスではこの変数が無視され、設定画面からのみ変更することが可能な場合もあるのでお気を付け下さい。
MySQL [(none)]> SET GLOBAL expire_logs_days = 2;
②移行元DBをdumpする
まず移行元のDBをdumpします。データ量が大きいDBの場合は、dumpファイルをS3上に生成することによって作業しているサーバのディスクサイズを気にしなくてよくなるのでおすすめです。
以下のコマンドを実行します。HOSTNAME
、USERNAME
はそれぞれDBに接続するためのホスト名、ユーザ名。DATABASE1 DATABASE2 ...
はdump対象のデータベース名です。
パイプを使って標準出力をaws s3 cp
コマンドに渡すことによって、直接S3にコピーします。
$ mysqldump -h HOSTNAME -u USERNAME -p --databases DATABASE1 DATABASE2 ... --master-data=2 --single-transaction --routines | aws s3 cp - s3://your_s3_bucket/hoge.dump
ここでポイントとなるオプションが2つあります。
--master-data
このオプションが1にセットされていると、dumpを実行した瞬間のバイナリログ座標がCHANGE MASTER TO
ステートメントとしてdumpファイルに書き込まれます。
2にセットされていると、CHANGE MASTER TO
ステートメントがコメントとしてファイルに書き込まれます。0にセットされているとこれが書き込まれないので、必ず1か2を指定します。今回はCHANGE MASTER TO
ステートメントではなく、RDSのシステムストアドプロシージャ(後述)を使うので、2をセットしておきます。
参考: https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_master-data
--single-transaction
このオプションをつけないと、移行元DBへの書き込みがロックされてしまいます。障害につながる恐れがあるので必ずつけましょう。
参考: https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_single-transaction
③移行先DBにデータをrestoreする
dumpが完了したら移行先のDBにデータを流し込みます。先ほどは逆に、aws s3 cp
の標準出力をmysqlコマンドに渡してあげます。
今度はHOSTNAME
、USERNAME
に移行先DBのホスト名、ユーザ名を指定します。
aws s3 cp s3://your_s3_bucket/hoge.dump - | mysql -h HOSTNAME -u USERNAME -p -f
④レプリケーションの実行
restoreまで完了したら、ついにレプリケーションを実行します。これにより、dumpを開始してからrestoreが完了するまでの更新や、それ移行の更新も同期し続けることができます。
まずはじめに、dumpファイルに書き込まれているバイナリログ座標を確認します。
aws s3 cp s3://your_s3_bucket/hoge.dump - | grep -i "change master to"
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin-changelog.123456', MASTER_LOG_POS=1234567;
MASTER_LOG_FILE
とMASTER_LOG_POS
の値をそれぞれ控えておきます。
次に移行先DBに接続し、以下のコマンドを使用してマスターとなるDB(移行元のDB)を指定します。
RDSでは通常のSQLステートメントではなく、システムストアドプロシージャと呼ばれるコマンドを実行します。
MySQL [(none)]> CALL mysql.rds_set_external_master (
'HOSTNAME',
'PORT',
'USERNAME',
'PASSWORD',
'mysql-bin-changelog.123456',
1234567,
0
);
mysql.rds_set_external_master
のパラメータには移行元DBに接続するための情報、バイナリログ座標、最後にSSLの使用の有無を指定します。
パラメータに関する詳細は、以下の公式ドキュメントもご参照下さい。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/mysql_rds_set_external_master.html
ここまで完了したら、以下のコマンドを実行することによって、レプリケーションがスタートします。
MySQL [(none)]> CALL mysql.rds_start_replication;
DBの切り替え
正しくレプリケーションが行われてるかは以下のコマンドによって確認できます。
MySQL [(none)]> show slave status\G
...
Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it
...
Slave has read all relay log
とあるので、レプリケーションによるコピーは最新の状態に追いついているようです。
サービスを一時的にメンテモードにしてから1以下のコマンドによってレプリケーションを停止と設定の削除をしましょう。
MySQL [(none)]> CALL mysql.rds_stop_replication;
MySQL [(none)]> CALL mysql.rds_reset_external_master;
最後に、Route53のプライベートホストゾーンや、アプリケーションのconfを新しいDBに書き換えるなどして、切り替え完了です。
最後に
ダウンタイムは最後の切替時に行うメンテモードの間だけなので、レプリケーション停止から切り替えまでのわずかな時間のみとなります。切り替えの手順をしっかり確認しておけば、数分間の停止で移行を完了させられるかと思います。
移行元/移行先の疎通を通さないといけないというハードルはありますが、条件さえ揃えばメリットの多い移行方法だと思います。
明日は@shotawsさんが何か書いてくれるそうです。
-
レプリを停止してからDB切り替えまでの数分間の間にDBへの書き込みが無い場合には、メンテモードにする必要はないです。その場合は完全に無停止で移行することができます。 ↩