repl.info

AWS DMSを使ってMySQLからPostgreSQLに移行した

先日、現在所属しているフライルでメインデータベースをMySQLからPostgreSQLに移行しました。主にデータ移行に関してあれこれやったので、それについて書いていきます。筆者はアプリケーション側の対応はほとんど行っていないため、そこに関してはこの記事では触れません。

なお、移行の主な理由はPostgreSQLのRLS (Row Level Security)利用です。データが増えれば増えるほど移行が大変になるのは間違いなく、やるなら今だろうと判断し実施に踏み切りました。

スケジュール

本格的に検証を開始してから1ヶ月強でデータ移行を完了しました。まず、2〜3週間で調査を行いました。主にDMSを用いたデータ移行の検証とアプリケーションのPostgreSQL対応の調査で、他のタスクと並行して実施しました。

その後、約1週間でアプリケーションのPostgreSQL対応をチーム全体で行いました。この時は機能開発を止め、チーム全体でPostgreSQL対応に集中しています。最後にデータ移行・アプリケーションデプロイですが、これは祝日を1日使って実施しました。1日で移行しきれるデータ量だからよかったですが、1年後だと1日でできたか分かりませんね。

AWS DMS

今回、MySQLからPostgreSQLへのデータ移行にはAWS DMSを用いました。DMSはRDBやNoSQL、S3などを対象としてデータ移行を行うためのサービスです。

以下画像は、DMSのドキュメントから引用したDMSのレプリケーションプロセスです。

image-20240308102920025

引用:https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/Welcome.html より。

DMSはレプリケーションタスクという呼ばれるプロセスをレプリケーションインスタンスの中で動かします。レプリケーションタスクはソースデータベースからターゲットデータベースにデータを移行したり、移行したデータのバリデーションを行います。レプリケーションインスタンスがソース・ターゲットに接続するための情報はエンドポイントリソースに格納されます。

データ移行

まず、DMSを用いたデータ移行について述べます。基本的にはポチポチするだけで移行してくれるのですが、DMSだけで全ていい感じに移行できるわけではありませんでした。そこで、工夫やハマりポイントをいくつか紹介します。なお、調査不足で実はDMSの設定などで実現できた…ということもあるかもしれません。

データ以外の移行

DMSはデータ移行時にテーブルを自動作成してくれますが、意図通りの型で作ってくれるとは限りません。そこで、事前にテーブルを作成しておくのがおすすめです。 また、DMSはインデックスや外部キーなどの移行はサポートしていません(AWS Database Migration Service での移行タスクのトラブルシューティング)。

そのため、これらについてはデータ移行事前・事後に作成処理を行いました。なお、外部キー制約は事前に作成するとデータ移行時に制約に引っかかってエラーになることがありました。そのため、データ移行前・移行後にわけて作成できるように準備するのがよいと思います。

LOBカラムの扱い

DMSのLOB(Large Object)に関してはいくつか処理方法があります。例えば、Full LOB modeはLOBのサイズに関わらず全てのLOBが移行されます。Limited LOB modeだと設定した最大許容LOBサイズを超えるLOBは切り捨てられます。その代わり、パフォーマンスが大幅に向上するようです。

今回はLOBも完全に移行したかったため full LOB mode を使う必要がありました。しかし、その場合ターゲットのLOBカラムがNULLBLEでなければなりませんでした。 そこで、NOT NULLなカラムについてはテーブル作成後に一時的にNULLABLEに変更し、データ移行が終わった後再度NOT NULLに変更するようにしました。

マイグレーションとバリデーションは直列で実行する

マイグレーションとバリデーションは同時に実行することができるのですが、今回のケースだと同時に実行すると安定せず、エラーになることがありました ( duplicate key value violates unique constraint エラーが発生)。 レプリケーションインスタンスのスペックやレプリケーションタスクのパラメータ調整で安定するのかもしれませんが、順番に実行して確実に完了することを優先し直列に実行することにしました。

テーブルマッピング

テーブルマッピングを設定しておくことで、データ移行対象の絞り込みや様々な変換を行うことができます。ただ、ドキュメントがあまり多くなく難しい機能だと感じました。

  1. MySQLのデータベース名をPostgreSQLのスキーマ名として別名に変換

MySQLのデータベース名をPostgreSQLのスキーマ名としてそのまま使うのは不都合がありました。このような場合、transformationルールを使ってリネームすることができます。

  1. 移行するテーブルの絞り込み

移行の必要がないテーブルが数個ありました。このように移行対象を絞り込む場合、selectionルールを使って絞り込むことができます。今回はexclude ルールアクションで移行しないテーブルを指定しました。

  1. バリデーション時の文字置換(未使用)

バリデーション節でも述べますが、MySQL側のデータにヌル文字が含まれている場合PostgreSQL側にはスペースとなって移行されるようです。そうするとバリデーションで差分として検出されるのですが、validationルールのoverride-validation-function ルールアクションを使うとバリデーション時にヌル文字をスペースに変換してくれるようです。しかし、文字の置換ルールを設定すると [MySQL][ODBC 8.0(w) Driver][mysqld-8.0.35]No database selected というエラーになってしまい、うまく動きませんでした。全く原因はわからず、ルールの適用は断念しました。

レプリケーションは未使用

DMSにはCDCと呼ばれる継続的なレプリケーションを可能とする機能を持っています。データ量が多くメンテナンスウィンドウ内でマイグレーションが不可能な場合はこれを使うことになると思います。一部テーブルのレコード数が非常に多くレプリケーションが必要かと思われたが、実際にはデータ移行不要なテーブルだったため今回は使用しませんでした。

データバリデーション

次に、データのバリデーションについてです。データ移行後、DMSのバリデーション機能と自前で実装したバリデーションツールの2つを用いてMySQLとPostgreSQLのデータ比較を行い、問題がないかをチェックしました。2種類の方法でバリデーションした理由ですが、今回のデータ移行によってデータの欠損が発生することは絶対に避けなければならず、欠損の可能性を可能な限り小さくするためです。最終的にどちらのバリデーションでも問題ないという結果となり、安心してアプリケーションの切り替えに進むことが出来ました。

DMSのバリデーション機能は比較的簡単に使えて便利ですが、いくつかハマった点を紹介します。

COLLATIONに要注意

COLLATIONの設定によってはMISSING_SORUCEやMISSING_TARGETといった、片方にレコードはあるがもう片方にはないというバリデーションエラーが多発することがあります。 バリデーションの検証をしていた時はPostgreSQLの設定をあまり詰めておらず、照合順序が異なる設定になっていました。その結果MISSING系のバリデーションエラーが多発し、原因の特定と対応に時間がかかりました。 おそらくソースとターゲットで並び順が異なることにより、DMSから見て存在しないように見えてしまうのだと思われます。 レコード自体はソースにもターゲットにも存在するので最初は全く原因がわかりませんでした。RDBに詳しければすぐ気づくのかもしれません。

データ差分

基本的にデータ差分は発生しないのですが、いくつか差分が検出されました。

  1. 絵文字など一部の文字の比較がうまくいかない

DMSから見て片方は絵文字に見えているが、片方からは「?」として見えてしまい差分として検出されるケースがありました。 解消する方法がわかりませんでしたが、後述するバリデーション結果のチェックや自前のバリデーションツールによって問題なしと判断できました。

  1. MySQL側のデータににヌル文字が混ざっている場合

MySQL側のデータにヌル文字が混ざっているケースがあり、これが差分として検出されました。 ヌル文字自体はPostgreSQLには書き込めないのですが、データ移行時にDMSによってスペースに変換されていたようで、これがバリデーションから見て差分となりました。 こちらも後述するバリデーション結果のチェックや自前のバリデーションツールによって問題なしと判断しました。 テーブルマッピングでヌル文字をスペースに置換する設定もできるようですが、うまく動きませんでした。

バリデーション結果のチェック

バリデーションが進むにつれ、差分が検出されるとAWS Console上からどのテーブルで何件検出されたかの情報は見えるようになります。しかし、どのレコードのどのカラムにおいてどのような差分が検出されたかはわかりません。 それらの情報はターゲットとなるPostgreSQLに awsdms_validation_failures_v1 というテーブルが作られ、ここに格納されています。なので、バリデーション完了後にこのテーブルを確認し、本当に問題があるものなのかどうかを確認しました。

以下はDMSのドキュメントから引用したawsdms_validation_failures_v1のレコード例です。

select * from awsdms_validation_failures_v1 where TASK_NAME = 'VFPFKH4FJR3FTYKK2RYSI'

TASK_NAME       VFPFKH4FJR3FTYKK2RYSI
TABLE_OWNER     DB2PERF
TABLE_NAME      PERFTEST
FAILURE_TIME    2020-06-11 21:58:44
KEY_TYPE        Row
KEY             {"key":  ["3451491"]}
FAILURE_TYPE    RECORD_DIFF
DETAILS         [[{'MYREAL': '+1.10106036e-01'}, {'MYREAL': '+1.10106044e-01'}],]

引用:https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Validating.html より

主に見たのは FAILURE_TYPEDETAILS です。FAILURE_TYPE RECORD_DIFFMISSING_SOURCE、または MISSING_TARGET の3タイプがあります。MISSING_SOURCEMISSING_TARGET はマイグレーションタスクやバリデーションタスク実行中にソースDBへのアクセスがあると発生しやすいと思われますが、今回のデータ移行ではDBへのアクセスが発生しない状況で行ったため発生しませんでした。主にチェックしたのは RECORD_DIFF です。 awsdms_validation_failures_v1 テーブルのレコードについて、KEY にはプライマリキーが格納されています。また、DETAILS にはどのカラムでどのような差があったかが格納されています。 これらを用いて、ソースとターゲットから該当のデータを取得・比較するスクリプトを作り、バリデーション結果のチェックを行いました。 なお、 DETAILS にはJSON形式の文字列が格納されているはずなのですが、素朴にパースできないことがありやや難儀しました。

自前のバリデーション

DMSのバリデーションは使ったのですが、それとは別に自前でツールを用意してバリデーションを行いました。二重でデータの問題がないことを確認できるので、おすすめです。

DMSリソースの管理

DMSにはレプリケーションタスク、レプリケーションインスタンス、エンドポイント、シークレットなど関連するAWSリソースが多くあります。手動で管理すると大変なので、TerraformやCloudFormationなどで管理するのがおすすめです。筆者はTerraformで管理しました。

一点管理する上で難しかったのはテーブルマッピングのルールです。jsonで設定を書く必要があるのでルールが増えたりレプリケーションタスク毎に設定を変えたい場合やや煩雑でした。ルール毎にユニークなIDを割り振る必要があるようで、terraformのrandom_integerリソースを使って生成したのですがこれも少し面倒ですね。

その他

  • floatの精度がソースとターゲットでずれていたらバリデーションに気づけるので便利。
  • 実際の移行時に、特定のテーブルのデータ量がかなり増えていて想定以上に時間がかかった。
    • 今回はできなかったが、毎日スナップショットから復元してマイグレーション・バリデーションする仕組みを作っておくと気づけたかもしれない。

終わりに

今回DMSを使ってMySQLからPostgreSQLへデータ移行してみて、躓きポイントはあるもののDMSはかなり便利でした。とはいえMySQLからPostgreSQLへの移行は今後現環境ではまず発生しないと思われます。得た知見を生かしにくい…が、何かの役に立つこともあるはずです。DMSに関する情報はあまり多くなく、この記事が少しでも誰かの役に立てばいいなと思います。