外部のMySQLをAWS Auroraに移行するTips

何度か外から AWS の Aurora MySQL へサービス移行をして知見が溜まっていたので、吐き出しておきます。DBA 専門的な人から見たら別におもろくない内容だと思いますので、初中級者の学習用教材です。

最初は全体の流れを全て書こうとしましたが、全く同じ流れでやることはないだろうと思い、部分ごとにこういうことを検討すればえぇんちゃうんくらいの Tips にしてみました。



目次

そこまで長くないですが Tips なので目次を置いておきます。



データ転送の方法

まずはデータベースのデータをどのように AWS へ持っていくかを考えます。

当日にバックアップ/リストア

サービスを移行メンテに入れる当日に、旧環境でバックアップをし、データをAWSへ転送し、Aurora へリストアする、という流れです。

正直、全然メリットがないので推奨はしませんが、まだサービス規模が小さい状態かつ、移行作業者があまりDBの取り扱いに慣れていないとかなら、ありえなくはない選択肢です。

数十分から数時間程度でデータ移行が完了し、それが許容できるメンテ時間内に収まるのであれば、ある意味 Simple is Best. ではありますが、後学のために他の手法も見て行ってくださいな。

事前にレプリケーションする

当日にそれをやらないなら、どうするかというと、事前に Aurora から外部へレプリケーション接続をしておき、最新データを新環境に用意してしまいます。多くの知識を必要としますが、それを除けばメリットしかありません。

数週間前に旧環境でバックアップを採取し、新環境でリストアして、レプリケーションを張っておくことで、当日のバックアップ/リストアの作業と時間を省くことができます。通常は数時間、下手したら数日かかる部分が、メンテ開始後にレプリケーションを停止するだけに変わります。

他のメリットとしては、特に新環境のMySQLバージョンが異なる場合、日々流れる本番の更新内容が正常に動作することを確認できます。また、本番相当の更新負荷がかかるので、更新だけとはいえ、そのメトリクスを確認しておけるのも安心・安全につなげることができます。

まぁでもなんといっても当日のメンテ時間を大幅に短縮できることと、作業ミスや予期せぬできごとにより移行中止になるようなリスクも大幅に軽減できるところが大半を占めています。

ということで以下に、2つのレプリケーション方法を紹介しておきます。


通常レプリケーション

Aurora も他のDBとレプリケーションできるので、新旧の MySQL のバージョンやら諸々が許せば、これが最も単純な手段です。


この時にまず考えるのがネットワーク経路です。

直にグローバルアドレスへ接続

新環境の Aurora は普通 Private Subnet に存在すると思いますので、そこから外部へ接続する場合は NAT Gateway が必要になります。旧環境DBでは Global Addr で受け入れる状態にする必要があるので、NAT G/W が持つ EIP を使って、接続元のIPアドレス制限をするとよいです。

また、アドレス制限をしても平文でレプリケーションデータが垂れ流されるのは好ましくないため、SSL 必須にすることになります。証明書を用意し、旧環境 my.cnf でこの設定を有効にします。

次にレプリケーション用MySQLユーザーを作成し、GRANT で REQUIRE SSL を付与します。作成後は実際に接続して、SSL が有効であることを確認しておきます。


Aurora 側では、ストアドプロシージャでSSL情報をインポートします。相変わらずSSLファイル名はわかりづらいですが、間違えると接続できないので失敗する時は特にここに注意してください。

  • “ssl_ca” : “認証局証明書” = my.cnf の ssl-cert と同じ
  • “ssl_cert” : “クライアント証明書” = my.cnf の ssl-ca と同じ
  • “ssl_key” : “クライアントキー” = my.cnf では使わないクライアント用の秘密鍵


VPN経由で接続

もう1つのネットワーク構成としては、新旧環境を VPN で接続し、Private Addr だけで DB 同士を接続できるようにすることです。話が逸れちゃうので詳細は省きますが、OpenVPN や IPSec で VPN を通して、双方の Routing を設定すれば Private だけで往復でき、しかも VPN 部分は暗号化されるから、レプリケーションとしての暗号化を省けるっちゃ省けます。

旧環境のDBに対して Global Addr での接続を用意できないとか、他の用事でそもそも VPN 接続を用意するとかなら、こちらを採用する可能性も高そうです。

他にも方法はあるかもですが、自分の環境においてできるだけシンプルかつ安全にするよう心がければ大丈夫です。間違っても無駄に Aurora のパブリックアクセスを有効にしたり、平文で binlog 飛ばしたりしないようにしましょう:-)

レプリケーション開始

経路を確保できたら、mysqldump で言うところのこの部分の binlog 位置情報を使って

リストア後に Aurora のストアドプロシージャでレプリケーションを開始するだけです。

確認は昔通り、この2つが Yes になっていればOKです。

No になったり、エラーログが出た場合、疎通確認・SSL情報・ユーザー情報、あたりを丁寧に再確認してみましょう。もし解決できないなら、Aurora と同じ Subnet に EC2 を起動し、そこから mysql コマンドを使ってレプリケーション用ユーザーで接続したり、旧環境DB上で tcpdump して通信自体が来てるかなどを確認して、原因を絞り込んでいきましょう。


DMS経由レプリケーション

2つ目のレプリケーション方法として、DMS (Database Migration Service) を使うことができます。社内には自分用の完璧なドキュメントがあるのですが、長くてここに写してくるにはクッ!!ガッツがたりない!!のでリンク集と要所解説で勘弁してちょ。

リンク集


メリット

DMSは多機能なレプリケーションという感じで、慣れるまで使い勝手は難しめだけど、わかってしまえば色んな局面や要望に応えてもらえるサービスです。

最も大きな特徴は、多数の RDBMS 種類からの移行を想定して作られているところで、旧DB → DMS → 新Aurora のように DMS を経由することで、いったんDMS内での統一規格にデータが変換されます。これは MySQL to MySQL でも変わりません。

そのためドキュメントのあちこちに、型についての注意が散見されます。例えば、float が勝手に丸められるとか、string系 が勝手に短くなるとか、そういう可能性を示唆してくるので最初は結構怖いです。

それに対しては、移行前評価という機能を使って、DMSから見て気に食わない・怪しい箇所を指摘してもらうことができます。ただ、それでも不安な大事なところなので、別途独自のデータ整合性チェックのスクリプトを書いたりもしました。

他のメリットとしては、データベース単位・テーブル単位で、更新処理を通す通さないとか、編集を加えるといった、少々の自由度がある機能が備わっています。

例えばこれを使うことで、本来 Master:Slave で1:1の関係のところを、複数のクラスタ間でレプリケーションをたすき掛けしつつ、DMSタスクごとに通すデータベースを調整することで、移行後のクラスタ・データベース構成を変更した状態でレプリケーションすることができます。それで旧環境の煩雑になったDB構成という、技術的負債を返す絶妙な機会とすることも可能です。

大まかな流れ

IAM Role作成 ポリシー:AmazonDMSVPCManagementRole移行前評価の保存用ポリシー をアタッチした Principal→Service = dms.amazonaws.com の「dms-vpc-role」と、
ポリシー:AmazonDMSCloudWatchLogsRole をアタッチした同Principalの「dms-cloudwatch-logs-role」を作成しました
サブネットグループを作成 DMSインスタンス用の Subnet を作成します。Public にするとインスタンスが直接 Global Addr へ接続できますが、DMSインスタンスの PublicIP が変わると、旧環境でのアドレス制限も変えることになるので、Private Subnet にして NAT G/W を通す方が無難と思われます。
ただし、binlog が INPUT でくるため NAT G/W の方が転送費用がかかるため、長期間や大容量を予定している場合は日々の binlog 容量から費用を計算し、Public/Private の都合の良い方を選ぶとよいです。
証明書作成 旧環境で用意した “ssl_cert” : “クライアント証明書” をインポートし、ソース・エンドポイントの作成で利用します。
DNSレコード作成 旧環境のDBアドレスを、SSL証明書のワイルドカード部分を使って、Route53 などで登録して DMS 側で使います。
例:db-01.ssl.gedowfather.example.com : 1.2.3.4
ソース・エンドポイントの作成 旧環境DBへのレプリケーション・ユーザーの接続情報を登録します。
必須ではないですが、エンドポイント接続属性を使用 にチェック、追加の接続属性に
afterConnectScript=SET SQL_BIG_SELECTS=1;
などを設定することができます。
DMSインスタンスが無い時点では接続テストはいったん失敗してOKです。
ターゲット・エンドポイントの作成 作成済みのAuroraクラスタを選択し、接続情報を登録します。
レプリケーションインスタンスの作成 binlogを受け取って、データ処理をゴニョゴニョし、Auroraに更新クエリを実行する処理をしてくれます。
更新内容次第なのでどのくらいのインスタンスクラスを必要とするかは一概に言えませんが、あまり大きい必要はありませんし、
サーバーレスがリリースされているので、今はそれを使えば十分と思われます。
データベース移行タスクを作成 ここが最大の難所で、1つ1つの項目の意味を理解して設定しないと落とし穴にハマる可能性があります。

私の場合、リストアデータからbinlog適用をしたかったので、『移行タイプ』は”データ変更のみ”とし、『ソーストランザクションのカスタム CDC 開始点』を”mysql-bin.000001:1073”のように開始しました。
全データを参照してくる選択もありますが、容量が大きいとどう考えても現実的ではないので、要件に合うものを検討します。

『最大LOBサイズ』はデフォルトで 32KB なので、TEXT,BLOG型などでその容量を超えるカラムデータがあると、当然のように 32KB でカットされて転送されます。これはDMSシステム側の都合で設計されたものっぽく、「完全LOBモード」を使って全データが正しく転送されるようにするのが当然と思われます。……っていうのは、中の人にこのデフォルト怖いからヤメテって伝えてあります。

『検証の有効化』や『移行前評価』は最初はやってみた方がいいと思います。作り直しする時は無駄なので省きます。

『テーブルマッピング』には1つ以上のルールを作る必要があり、「スキーマ名」が「データベース名」を表すので、1ルールに1つずつ書くなり、ワイルドカードを使うなりで登録します。
タスクの確認 レプリケーションが正常に動いているかを、タスクで確認します。更新数やエラーの有無を見れるので、問題があれば解決していきます。
また、テストであれば旧環境でデータ更新をしてみて、Auroraでその確認を。本番なら軽量クエリで最新行のDATETIMEなどを確認しておきます。

非対応 DDL

DMS は一部の DDL しかサポートしていません。

よく定期実行されるだろうパーティションの追加に対応していないため、DMSレプリケーションを開始する前には、その処理を停止しておく必要があります。

こういった非対応のクエリを流し込むと、DMSタスクがエラーで停止します。いったん停止してしまうと、スキップしたりといった手段がなく、完全に死にタスクとなってしましまうので、非対応処理の所在把握が重要になります。

binlog_format

DMS レプリケーションの接続先は binlog_format が ROW になっている必要があります。

変更するには mysqld の再起動が必要になるため、旧DBで Source 側となる DB だけでも対応しなくてはいけません。


バックアップの方法

ようやく肝心要のバックアップについてです。データ容量がTBクラスになると10時間以上とか普通にかかってくるので、できるだけ高速に済ませられないかを考えます。

とはいえ、当日作業にしない限りは、数日・数週間前に行えるので、多少長くとも許容できるところではあります。

mysqldump

古来からあるので説明不要のモノですが、こんな感じでバックアップを圧縮しつつ、そのまま S3 に送ると、余計なローカルストレージが不要なので便利です。


pbzip2 の並列数は、サーバーの vCPU 数より少し少なめにして、他のために余裕を持たせつつも、できるだけ働かせるのが早いです。

-B でデータベースを指定するのは好みですが、指定するとバックアップ内容にも use データベース名が入るし、余計なものを持っていかなくて済むので良いです。

mysqldump 独自並列

基本的にはデータの一貫性のために1回の mysqldump で採取する必要がありますが、どうしてももっと早くしたい場合は、SLAVE を余計に1つ用意して、レプリケーションを停止してから並列でバックアップを取得し、またレプリケーションを再開します。

並列の条件は例えばテーブル単位とかです。その場合、並列度にもよりますが、最もサイズの大きいテーブルで全体のバックアップ時間が決まるので、あまりに偏ったサイズでなければ、かなりの高速化を見込めます。

その分、リストアも並列にできて高速になりますが、データの取り扱いをキチンと理解した上で専用のスクリプトを書くことになります。

mysqlpump

そんな並列度が欲しい場合のために、mysqlpump というのが出ましたが、実質 MySQL version < 5.7.11 では使えないのと、次の選択肢があるためにお役御免になっているに等しいです。

MySQL Shell (mysqlsh)

今、速さを求めるならこれですが、色々検証することにはなります。

特に外部ストレージへの転送は Oracle Cloud Infrastructure (OCI) ObjectStorage しか指定できないっぽく、ローカルに残さず直接 S3 へってことはできない感じです。

あと、これに限った話ではないですが、並列度を上げられるほどに、バックアップ抽出そのもの以外のところがボトルネックになる可能性が高まるので注意が必要です。それがストレージ性能なのか、CPUなのか、ネットワーク転送速度なのか、は内容次第ですが、何が限界になってその速度に留まるのか、は推し量りつつ検証すべきです。

Percona XtraBackup

Aurora MySQL 8 の対応もされましたし、単純な速度だと、これが一番早くなる可能性が高いです。クエリベースではなく、データファイルベースでのバックアップだからです。

10年以上前にこれ Percona XtraBackupの基本的な使い方 | 外道父の匠 を書きましたが、さすがに古いので新しい情報を見た方がいいですね。


リストアの方法

バックアップ時間よりも、リストア時間のほうが長くなることが多く、下手したら数日単位になることもあります。

容量が大きい場合は、最初は丸っと全てを試すのではなく、一部のデータでバックアップとリストアを行い、全体時間の概算を得てから方法論を考えることも大事です。

mysqldump の通常

基本的には、mysqldump なら得た内容をそのまま MySQL に渡すことでリストアします。


ただ、これは非常に遅いです。バックアップに10時間かかったとしたら、リストアには3~5日かかってもおかしくありません。

mysqldump の分割

mysqldump のリストアが遅い理由は、その全ての処理が単列で実行されるからです。中身は普通の CREATE TABLE や INSERT INTO なので、それを上から下まで往復しながら実行されると考えたら、遅いのは当たり前だと思えるでしょう。

INSERT INTO は今では BULK INSERT がデフォルトですし、1クエリあたりのサイズを大きくしても、そう速くなりません。ボトルネックはクエリの単列往復であるため、クライアント/サーバーのインスタンスクラスをスケールアップしても、ほぼ何も変わりません(並列解凍は速くなるけど、それより遥かにクエリ往復が遅い)。

そのため、私の場合は独自スクリプトにて、INSERT 文を 1 GB ずつに分解して保存し直し、それ以外の DDL もいい感じに分けて保存することで、リストアを並列で実行できるようにしたことがあります。

素直に全てのクエリを残してしまうと LOCK TABLES が邪魔なので取り除いたり、分割のついでに新環境用に CREATE TABLE を編集したり、と色々盛り込みました。

データを分割するとクライアントも複数にできるので、EC2 を複数起動しつつ、その中でも複数のプロセスで処理することで、何十並列で INSERT リストアをします。Aurora は 16xlarge などリソースを大きくすることで、Aurora の限界性能までリストア速度を上げることができます。

また、分割や解凍処理において高速にするために、作業場のストレージ EBS を gp3, Size 2TB, IOPS 4000, Throughput 1000 MB などにもしました。

これにより、推算5日間くらいかかる単列リストアを、分割+並列リストア合わせて1日未満に短縮できたことがあります。バカでかいインスタンスを使っても、リストアが終わればすぐに終了 or スケールダウンすれば、費用は最小に留められるので、クラウドならではの手法と言えます。

そして当然、可能な限りの処理をコードに落とし込んで自動化し、クラスタ数分をポチるだけにしてあります。

その他の方法

こういった独自の工夫が必要な環境や時代もありますが、XtraBackup などのリストアはその手順に従うだけなので、特に言うことはありません。

どの選択肢にせよ、まず大事なのはデータの保証と正常なレプリケーションの開始で、次に速度や使いやすさです。検証において、そのバックアップとリストア、レプリケーションの正常性をキッチリ確認して、それから所要時間と工夫について考えていくようにしましょう。


バージョンの選定

移行時点で、新旧環境のバージョンを完全に揃えられるかはわかりませんし、移行の機会に新しめにしたいということもあります。まずはできるだけ同じバージョンで、と考えたくなるのもわかりますが、移行後の環境での動作確認をガッツリやるのもこのタイミングなので、最新にする絶好の機会ともいえます。

MySQL のバージョンは大きくは 5.5, 5.6, 5.7, 8.0 とあり、今だとできれば最新の Aurora 3 (MySQL 8.0.28)にすると便利機能が増えていてオススメですし、よほど難しい処理でもしていない限りは、どのバージョンからでもわりと現実的範囲だと思われます。

この辺は自分の所よりよっぽど参考になるナイス記事があるので貼っておきます。

文字コード

1つの例として、5.6 -> 8.0 へレプリケーションをした時、やはり文字コードでエラーが出たことがあります。


この場合、小さい容量から大きい容量なので、データ欠損が出るわけじゃないため、以下の設定をすることで回避しました。

  • replica_type_conversions = ALL_NON_LOSSY

  • これは直にレプリケーションしたから出ましたが、DMS 経由にするとこういうエラーに引っかかりづらくなるはずなので、何を選ぶにしても事前の検証がなにより大事ってことになります。


    旧環境の事前準備

    レプリケーション用のネットワーク経路の確保、レプリケーション・ユーザーの作成、SSL情報の準備、あたりは既に紹介しました。まだあったので補足しておきます。

    expire_logs_days

    旧DB の binlog の保存期間は、バックアップ開始からリストア完了、そしてレプリケーションを開始し終わるまでの分は絶対に必要になります。じゃないと、バックアップに記載されている binlog のファイル名・位置 からレプリケーションを開始できなくなるからです。

    容量がパツパツの環境だと、binlog 容量すらケチっているところもあるかもですが、ここだけは確保せざるを得ない箇所なので、まずはバックアップ~レプリケーション開始までの所要時間を見積もり、それより余裕をもたせる形で expire_logs_days を設定します。

    現在の空き容量、1日あたりの binlog 蓄積容量を確認し、足りないなら腎臓を売るなり外部ストレージをマウントするなり、なんとしてでも確保しましょう。


    当日の作業

    レプリケーションさえあれば9割以上勝ったも同然ですが、残り1割の当日作業についても詰めておきましょう。サービスをメンテに入れて、さぁデータベース周りの作業開始です、ってところからです。

    処理中クエリの確認

    メンテインしても、重い更新クエリがまだ走っていたり、ユーザーのアクセスと無関係なジョブ関連の処理など思わぬ場所から実行されている可能性はあります。

    そのため、旧環境のデータベースで実行中のクエリがないかを確認します。例えばこのように確認します。


    あらかじめ、DBのリストと、このクエリを全DBに投げるスクリプトでも用意しておき、それを実行するだけで全体の確認をできるようにしておくと、スムーズに進行できます。

    新環境のデータとの整合性

    レプリケーション自体は信じないと話にならない部分ではあるのですが、特に初めて DMS を使う時は、型やサイズによるデータ自動変換の心配がゼロにはならないため、新旧DBのデータが一致するかの確認を入れてもよいでしょう。

    とはいえ、容量が大きいと全データの一致を確認することは現実的ではありません。そのため、移行作業進行に支障が出ない程度の処理時間に抑えた内容にする必要があります。

    例えば、各テーブルの最新から何行だけを比較する処理を、全クラスタ・全DBに自動的に行うスクリプトを書いておき、事前にほぼ大丈夫なことを確認しつつも、本番では不一致が出ないことを祈りながらポチります。

    レプリケーション停止

    一通りの確認が終わったら、AWS 側で DMSタスク または Aurora レプリケーションを停止します。

    DMSタスクの場合は管理画面でポチるだけですし、Aurora 直の場合は以下の処理を実行することになります。


    どちらも停止しただけだと残骸が残るので、一通りの移行作業が落ち着いたあとに、レプリケーション情報やネットワーク経路など、不要になる部分は後でお掃除しておきます。

    スナップショット採取

    レプリケーション停止後は、Aurora のスナップショットを取得しておきます。

    ここさえ決めてしまえば、その後にアプリケーション作業で何か起きても、スナップショットからクラスタを再作成すればリトライできるので、心休まるセーブポイントとなります。

    インスタンスクラス変更

    レプリケーション開始は、移行日の数週間前には準備しておくと思いますので、更新クエリが流れるだけなら大きなインスタンスクラスを必要としません。

    そのため、それまでは小さめのクラスで稼働させ、無駄なコスト流出を防ぐことも大事です。その場合の本番クラスへの変更タイミングは、移行直前もしくはスナップショット採取後になるでしょうが、当日時間短縮のために移行直前にする場合は注意が必要です。

    普通、DBを再起動してもレプリケーションはまたそのまま再開できると思うでしょうが、Aurora は再起動(インスタンスクラス変更?)をすると SSL 情報が消えるので、そのまま CALL mysql.rds_start_replication をするとエラーになって肝が冷えます。

    ダメならバックアップからやり直しかよ!と思いつつも
    CALL mysql.rds_import_binlog_ssl_material
    で再登録したら、再開できるので大丈夫です。その辺も含めて事前に手順をテストしておき、何が起きるか把握しておけば、クラス変更のタイミングは問題にはなりません。


    さいごに

    サービスの環境移行というタスクは、かなり重い部類です。

    データベースだけでも、最低限これくらいの検討事項がありますし、実際には他のミドルウェアの移行や、新環境でのアプリケーション動作確認、DNSレコード変更など、やることが盛りだくさんです。

    いざ移行当日本番になって、やっぱりこの部分がダメでした、なんてことも全然ありえる話で、切り戻す可能性も視野に入れつつも、できるだけそれが起こらないように丁寧に計画を立てる必要があります。

    基本的には事前の作業項目の洗い出し精度がモノを言いますが、時には当日限りのトラブルシューティング・パワーが必要になることもあり、エンジニアとしての底力を試されるかもしれないと覚悟をキメておくのも、わりと大事だったりします。

    またこのブログではたまに『本番を超えるテストは存在しない』と表現していますが、100%安全な移行など存在しません。移行作業が無事完了したあとは、おそらく負荷試験を元に用意したであろうリソース量で、問題なくトラフィックを捌けることを確認する必要があり、緊急課題が見つかれば即座に対応します。

    そこまで無事に終わって、ようやく風呂に入って寝ることができます。


    あらゆる移行のトラブル・ゼロを願っていますが、昨今の複雑なシステム構成では、些細な出来事も含めれば完全なゼロになる方が珍しいかもしれません。

    それでも、データベースさえ無事なら、ある意味なんとでもなるところがありますので、その計画・進行に少しでも役立てていただければ幸いでございます:-)