こんにちは。アイリッジ プロダクト開発グループの川口です。主に自社サービスのインフラ・運用業務を担当しています。
VPC に所属していない、いわゆる Classic 環境に存在する RDS (MySQL 5.6) インスタンスを VPC の中へ今年の夏頃に移行させました。対象のインスタンスは弊社で最も古くから本番環境で稼働しているDBの一つで、自社サービスの中核的な役割を担っているモノリシックなアプリケーションのDBです。それ故サービス停止時間を極力短くして移行する必要がありました。
少しニッチではありますが、同様の事例をあまり見つけられなかったので、レプリケーションを活用してサービス停止時間を短縮した移行手順を紹介させていただきます。
移行前の状態
移行前はAWS アカウントが RDS インスタンスとアプリケーションサーバで別々になっていました。これは過去に脱 Classic 環境に挑戦した際にアプリケーションサーバは新アカウントの VPC 内に移設されたが、RDS インスタンスは後述の移行課題によって数年手付かずの状態であったためです。また、アプリケーションサーバから RDS インスタンスへのアクセスが NAT Gateway 経由になっています。これにより下記の課題が発生していました。
運用上の課題
NAT Gateway 経由による問題
- DBアクセスの度に余計な通信オーバーヘッドが発生するためアプリケーションのパフォーマンスに悪影響を及ぼしている。
- NAT Gateway の通信料金がDBアクセスだけでも月数十万単位で発生しており利益を圧迫している。
Classic 環境による問題
- Classic 環境は2013 年 12 月 4 日以降に作成した AWS アカウントでは作成できない
- 弊社の検証環境が上記に該当するため当該 DB に関する検証が環境を完全再現して行う事ができない状態であった。
- なお該当しない場合はインスタンスタイプに制約があるものの現在でも新規に Classic 環境のインスタンスを作成することができる。
- ネット上に Classic 環境の情報が乏しく、また AWS からも推奨されていないため思わぬ制約に当たるリスクがある。
AWS アカウントが分かれていることによる運用負荷
- 単純にウェブコンソールのログインや AWS CLI のプロファイルなどを切り替えるのが面倒。
- 切り替え忘れによるオペレーションミスの原因にもなりうる。
また、移行作業を行うにあたっては下記の課題がありました。
移行作業の課題
巨大なDBなためスナップショットの取得とリストアに数時間かかる
- 単純なリストアによる移行ではその間ずっとDB接続を停止しなければならないためサービス影響が大きい。
- 問題発生時のロールバックにおいてもリスクとなりうる。
- サービス再開後に問題が発生してロールバックを行う場合、データ損失が許容されないと再度スナップショットの取得とリストアで数時間かかる事になる。
- サービス停止を伴うメンテナンスは深夜に行う決まりとなっているので長時間に及ぶと作業者が疲弊する(重要)
Amazon RDS マネージド機能のリードレプリカは VPC を跨いで作成することができない
- 何故かリージョンを跨げば VPC も跨げる。。。
- またレプリカの昇格にインスタンスの再起動を必要とするので、インスタンスの暖気作業が事前に行えずサービス停止時間に影響がある。
- この記事でいう暖気とはインスタンスのメモリ上に参照頻度の高いデータを予めロードさせておく事で、サービス再開直後のディスク I/O 圧迫を防ぐための作業。
採用した移行手順
これらの課題を解決するために MySQL のレプリケーションを活用した移行手順を考案しました。Amazon RDS ではレプリケーションを構築する MySQL コマンドは権限が限定されているため直接実行することはできませんが、用意されているレプリケーション用のストアドプロシージャを用いれば手動でレプリケーションを構築することができます。また、基本的にスレーブからマスターへのTCPコネクションさえ確立できればAWSアカウントやVPC、NATゲートウェイ跨ぎなどでもレプリケーションが可能です。
移行手順を要約すると下記の流れになります。
- Classic 環境のインスタンスをマスターとして、スレーブをVPC内部に作成する。
- スレーブに対して暖気などサービス停止前に行える作業を実施する。
- サービスを停止する。
- マスターとスレーブを入れ替えた逆のレプリケーションを構築する(ロールバック用)
- アプリケーションの DB 参照先を VPC 内インスタンスに切り替える。
- サービスを再開する。
また、万が一ロールバックが必要となった場合はアプリケーションの DB 参照先をスレーブとなった Classic 環境のインスタンスに切り替えればデータ損失無しで復旧することが可能になります。
これらの手順の詳細は下記になります。図中の赤い線やオブジェクトが主な変更点になります。
事前準備 (サービス停止前)
- アプリケーションの DB 参照先を Route 53 のプライベートホストゾーンに設定された CNAME レコードに修正する。
- Route 53 を挟むことでアプリケーションのデプロイ無しにDNSレコード修正のみで DB 参照先を切り替えられる様にするため。
- また、TTL は60秒など短めに設定しておくと切り替え時間が短くなる。
- Classic 環境のインスタンス (Classic DB) のバイナリログ保持期間を設定する。
- mysql.rds_set_configuration ストアドプロシージャで設定する。
- VPC 内部に移行先インスタンスを作成してレプリケーションを構築するまでの所要時間から余裕を持った値を設定するのが良い。
- 当然、保持期間が長ければ長いほどディスク容量を消費するので注意が必要。
- Classic DB にレプリケーション専用の MySQL ユーザを作成する。
- 例:
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'repl_user'@'%' IDENTIFIED BY 'password';
- 例:
- RDS マネージド機能のリードレプリカを用いて Replication 1 インスタンスを作成する。
- Classic 環境同士であればウェブコンソール上をポチポチするだけでリードレプリカを作成する事ができる。
- この際、マスターとなるインスタンスが MultiAZ であれば停止時間も発生しない。
- Replication 1 のレプリケーションを停止して、バイナリログのファイルとポジションを控える。
- mysql.rds_stop_replication ストアドプロシージャを実行して停止させる。
- Replication 1 で
SHOW SLAVE STATUS
コマンドを実行してMaster_Log_File
とExec_Master_Log_Pos
の値を控える。
- Replication 1 のスナップショットを取得して、移行先アカウントに共有する。
- 共有されたスナップショットから VPC 内に移行先インスタンス (VPC DB) を作成する。
- VPC DB で Classic DB をマスターとしたレプリケーションを設定する。
- マスターの設定は mysql.rds_set_external_master ストアドプロシージャを実行する。
- パラメータの
mysql_binary_log_file_name
とmysql_binary_log_file_location
は手順5で控えた値を用いる。
- パラメータの
- レプリケーションの開始は mysql.rds_start_replication ストアドプロシージャを実行する。
- この手順が完了したら Replication 1 はもう不要なのでインスタンスを削除して良い。
- マスターの設定は mysql.rds_set_external_master ストアドプロシージャを実行する。
- VPC DB をマスターとしたリードレプリカをRDS マネージド機能で作成する。
- VPC DB がプライベートサブネットに存在するため、後述のロールバック用レプリケーションを構築する際に中継するパブリックサブネットのインスタンスが必要となる。
- サブネット跨ぎのリードレプリカはウェブコンソール上からなら作成できるが、aws cli や terraform などの API 経由だと作成できない不具合が検討当時はあったので注意。
- VPC DB で暖気を実施する。
- 当 DB では実行に30分ほど掛かる SELECT クエリを実行して暖気を行った。
DB 切替 (サービス停止)
- サービスを停止状態にする。
- 当サービスではロードバランサからアプリケーションサーバの切り離しと Classic DB に
read_only=1
となっているパラメータグループを適用した。 - 各 DB の更新が停止している状態を作らないと後述のレプリケーション構築でデータ不整合が発生してしまうので注意。
- 当サービスではロードバランサからアプリケーションサーバの切り離しと Classic DB に
- VPC DB でレプリケーションを停止して、 Replication 2 バイナリログのファイルとポジションを控える。
- mysql.rds_reset_external_master ストアドプロシージャを実行して停止させる。
- Replication 2 で
SHOW MASTER STATUS
コマンドを実行してFile
とPosition
の値を控える。
- Classic DB で Replication 2 をマスターとしたレプリケーションを設定する。
- マスターの設定は mysql.rds_set_external_master ストアドプロシージャを実行する。
- パラメータの
mysql_binary_log_file_name
とmysql_binary_log_file_location
は手順2で控えた値を用いる。
- パラメータの
- レプリケーションの開始は mysql.rds_start_replication ストアドプロシージャを実行する。
- マスターの設定は mysql.rds_set_external_master ストアドプロシージャを実行する。
- Route 53 でアプリケーションが DB 接続先として参照しているレコードを Classic DB から VPC DB に変更する。
- 正常性確認を適宜行った後にサービスを再開させる。
ここまでが移行作業の詳細な手順となります。続いて、万が一問題が発生した場合のロールバック手順が下記になります。
ロールバック
- サービスを再度停止状態にする。
- Classic DB でレプリケーションを停止する。
- mysql.rds_reset_external_master ストアドプロシージャを実行して停止させる。
- Route 53 でアプリケーションが DB 接続先として参照しているレコードを VPC DB から Classic DB に変更する。
- 正常性確認を適宜行った後にサービスを再開させて移行作業を完了させる。
このように移行元のインスタンスに対してもレプリケーションを構築してデータを更新させつづけることで、ロールバック時のデータ損失を理論上無くす事ができました。幸い、このロールバック手順を実施せずに済みました。
まとめ
この他にもスレーブが MultiAZ だと書き込み処理が遅くてレプリケーションが追いつかない問題や、準備期間のインスタンスコストの都合などがあり、実際にはスケールダウン・アップ等の追加手順を挟んでいますが、概ねの流れは紹介させていただいた通りになります。レプリケーションを構築するメンテナンスは初めてなので手作業中心で行って確認手順も手厚めに盛りこみました。その結果、本番作業ではサービス停止時間が40分程となりました。サービスによってはこれでも許容できない場合はあるとは思いますが、当初の数時間掛かる見込みより大幅に短縮する事ができました。また、この手順は ALTER TABLE 等で長時間の停止を要する DB メンテナンスにも応用する事ができ、次回以降は手順をスクリプト化するなどして更に停止時間を短縮する事ができるのでは無いかと考えています。以上、似たような事例を探している方の少しでも参考になれば幸いです。