はじめに
データベース移行というのは非常にセンシティブな作業であり、この使命を背負ってしまったエンジニアの皆様におかれましては、さぞ胃に優しくない日々を送っていることかと存じます。そんな私たちの心強い味方がAWS DMSです。
AWS Database Migration Service (以下DMS) は、AWS が提供する フルマネージドのデータベース移行サービス であり、オンプレミスやクラウド環境間のデータ移行を可能にします。MySQL、PostgreSQL、Oracle など多様なデータベースをサポートし、移行元と移行先の異なるエンジン間の変換も自動化。フルロード、CDC (変更データキャプチャ) による継続的レプリケーションも可能で、最小限のダウンタイムでデータベースをスムーズに移行できるのが大きな魅力です!
そんな便利なDMSですが、当然使用する際に気をつけなければいけないことはあります。 本記事では、DMSを使用して60回以上のデータベース移行を行なったMNTSQ SREチームから、「MySQLの移行をする際に気をつけたいこと7選!」をお届けしたいと思います。
※ なお、本記事はDMS自体の説明や利用方法の解説記事ではございません
DMSを使ってMySQLの移行をする際に気をつけたいこと7選!
その1. DMSのログを出力するには決まった名前のIAMロールが必要である
まずはじめにDMSの移行タスクなどを作成すると思いますが、ここでTerraformの公式サンプルコードを見てみましょう。IAMロールの定義として、このような記述があるかと思います。
resource "aws_iam_role" "dms-cloudwatch-logs-role" { assume_role_policy = data.aws_iam_policy_document.dms_assume_role.json name = "dms-cloudwatch-logs-role" }
他のリソースと命名規則を揃えたかったとしても、このロールのnameは変更してはいけません。
aws_dms_replication_instanceのリソースの記述を見るとわかりますが、DMSインスタンスにこのロールをアタッチするわけではないのです。DMSインスタンスは暗黙的に特定の命名のロールを使用します。何故なのかは知りません。とにかく、他のリソースのような感覚で名前を変更してしまうと、後々動作確認の際に、エラーになっても原因調査が進まないといったことになります。(一応サンプルコードのコメントアウトに注意書きがありますが......)
ちなみにdms-access-for-endpoint, dms-vpc-roleも同様の理由でnameを変更してはいけません。
その2. CDCを有効にするにはソースDBでバイナリログを出す必要がある
DMSの強力な機能であるCDC (変更データキャプチャ) は、レプリケーション開始後にINSERTされたレコードも移行先DBに反映させることができる機能です。これがあるため、稼働中の環境でも無停止でレプリケーションを進め、最小のダウンタイムで新データベースに移行することができます。ただし、CDCはソースDBでバイナリログを有効化していないと利用できません。
Aurora MySQLの場合は、パラメータグループでbinlog_format=ROW, binlog_row_image=fullに設定しておけばOKです。ただし適用にはソースDBの再起動が必要です。
その3. GENERATEDカラムは移行対象から除外せよ
GENERATEDカラムは、他のカラムの値を基に自動計算されるカラムです。便利な機能ですが、DMSはこのカラムにも律儀に値をINSERTしようとしてしまい、そのまま移行を実行するとエラーとなってしまいます。
ソースDBにGENERATEDカラムが存在するときは、aws_dms_replication_taskのtable_mappingsに、以下のようなルールを記述して、移行対象から除外しましょう。除外しても、ターゲットDBにも適切にGENERATEDカラムの制約が設定されていれば、自動で計算された値が再び入るはずです。カラムが複数ある場合は、除外ルールも複数書きます。
resource "aws_dms_replication_task" "mysql" {
replication_task_id = "replication-mysql"
migration_type = "full-load-and-cdc"
~~ 省略 ~~
table_mappings = jsonencode(local.table_mappings_mysql)
}
locals {
table_mappings_mysql = {
rules = [
# GENERATEDカラムを移行対象から除外するルール
{
rule-type = "transformation",
rule-id = "1",
rule-name = "skip_generated_column",
rule-target = "column",
object-locator = {
schema-name = <schema_name>,
table-name = <table_name>,
column-name = <column_name> # GENERATED制約がついているカラム名
},
rule-action = "remove-column"
}
]
}
}
その4. LOB型のカラムがある場合はターゲットDBでNOT NULL制約を一時解除せよ
DMSはLOB型のカラムを含む行を移行する際、以下の2つのステップで処理を行います。
- LOB列をNULLにしたまま行を作成
- LOB列をUPDATEしてデータを挿入
このため、LOB型のカラムにNOT NULL制約がついている場合、1の処理でエラーとなってしまうようです。NOT NULL制約がついたLOB型のカラムを持つデータベースの移行を行う際には、DMSの移行タスクを実行する前にターゲットDB側の対象カラムから制約を解除し、移行後に元に戻しましょう。MySQLのLOB型にはTINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB, TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT, JSONなどがあります。
※ 参考: DMSのAWS公式ドキュメント
その5. 完全LOBモードの設定が必要か確認せよ
DMSによるLOB型カラムの移行オプションには、次の2つのモードがあります。
制限付きLOB モード
すべての LOB 値をユーザー指定のサイズ制限 (デフォルトは 32 KB) で移行します。サイズを制限を超えるLOBは移行されず、手動で移行する必要があります。完全LOB モード
サイズに関係なくテーブル内のすべての LOB データを移行します。
DMSのデフォルトは"制限付きLOB モード"なので、データベースの完全な移行を行いたい場合は明示的に"完全LOBモード"を設定する必要があります。一応AWS的には、まず"制限付きLOB モード"を試し、必要なら"完全LOBモード"に切り替えるという戦略を推奨しているみたいです。その主な理由は、"完全LOBモード"だとパフォーマンスが極端に落ちるためのようです。本来LOBはS3などを使用して管理するのがAWS的なベストプラクティスであり、そのような方法を検討して欲しいのだと思いますが、既に移行の計画に入ってからの変更は厳しい箇所かなと思います。ですので、結局ほとんどのケースで"完全LOB モード"を使用することになるのではないかと思っています。
LOBモードの設定は、terraformの場合だとaws_dms_replication_taskリソースのreplication_task_settings.TargetMetadata.FullLobModeにbooleanで定義されています。"完全LOB モード"を使用する場合にはFullLobMode=trueに設定しましょう。AWSコンソールから移行タスクを編集して設定することも可能です。
その6. AUTO_INCREMENTは手動で移行する必要がある
DMSがサポートするのはあくまでアプリケーションデータであり、INFORMATION_SCHEMAやperformance_schemaなどの移行は行えません。プライマリキーなどにAUTO_INCREMENTを使用している場合、DMSでの移行後に値がリセットされ、新たなレコードが挿入できなくなるなどのサービス障害の原因となってしまいます。
これを防ぐにはAUTO_INCREMENTの値を手動で移行する必要があります。以下の記事などを参考にし、弊社では移行用SQLを作成するスクリプトなどを用意して、移行手順に組み込みました。注意点として、AUTO_INCREMANTの値を取得する時はSHOW CREATE TABLE <table_name>;などを使用しましょう。INFORMATION_SCHEMAへのクエリでは、最新の値が取れないことがありますし、物理削除が行われるテーブルではAUTO_INREMENTと最新レコードのidにはズレが生じます。
その7. 移行後の検証の設計は慎重に
データベース移行後には必ず、移行前後で差分が出ていないかの検証を行うかと思います。当然、弊社でも検証を行なっていましたが、移行作業初期にはやはりトラブルに見舞われることはありました。原因は様々でしたが、移行後検証の完全性が保証されていれば、全てメンテナンスウインドウ中に検知できたものであり、サービスのインシデントにつながることな無かったはずのものばかりでした。最終的に弊社では、以下のチェックを行うスクリプトを導入した結果、データベース移行に関するトラブルは起きなくなりました。
- 全テーブルの
SELECT COUNT(*) FROM <table>;の結果を移行前後で突き合わせるスクリプト - 全テーブルの
SHOW CREATE TABLE <table_name>;の結果を移行前後で突き合わせるスクリプト
これにより、レコード数に差分がないこと、テーブル構造に差分がないこと、AUTO_INCREMENTの差分がないことが保証できました。
なお、これは弊社の事例に基づく例であり、いかなるケースにおいても上記の確認が移行の完全性を保証するものではございません!
弊社の場合は事前にCDCでレプリケーションを行い、移行日にはサービスのメンテナンス時間をとってこれらを確認しましたが、無停止での切り替えなどを計画している場合は、上記の項目の確認は難しくなります。また、データベースの設計によっては、確認項目が不足しているケースもあるかもしれません。加えて、制限付きLOBモードを使用していた場合、移行がスキップされたLOBがあったとしても、この方法では気づけません。あくまで「最低限ここは確認したほうが良い」程度にお受け取りください。
いずれにせよ、移行後の検証は、移行作業そのもの以上に慎重に設計すべきです。
おわりに
弊社MNTSQでは、ここ2年間ほどかけて大規模なアーキテクチャ変更を行い、その一環として計67回の顧客環境のデータ移行を行いました。最初の1回目の作業を行った時は「え、この作業あと60回以上するの?」と、その"永遠"に絶望したものですが、この度、ついにその作業も完了したので、区切りとして記事を書かせていただきました。
本記事では、移行作業を設計・検証する際に、実際に私がハマった箇所を「気をつけたいこと」という形で紹介させていただきました。「ここだけ気をつけていればあらゆるトラブルを回避できる」というものではありませんが、これからDMSを使用してデータベース移行を行うという方の助けになれば幸いです。
MNTSQ株式会社 SRE 西室