MongoDB同士のデータマイグレーションのベストプラクティスを探る ~mongodump/mongorestore編~ #mongodb #datamigration
はじめに
ITエンジニアにとって、システムの移行は避けられない作業の一つです。
利用者にサービス影響を極力感じさせずに移行を終えるのがベストですが、システム構成によっては、パフォーマンスへの影響やサービス停止(メンテナンス時間)が伴うことがあります。サービス停止を伴う原因の1つが、データベースのデータマイグレーションです。MongoDBでも他のデータベースと同じように、バージョンアップやハードウェアの更新に伴い、データマイグレーションが必要になる場面があります。
MongoDBのテクニカルサポートでも、データマイグレーションに関するお問い合わせは多くいただいています。それだけ多くの方が頭を悩ませているようです。そこで、今回はMongoDB同士のデータマイグレーションのベストプラクティスを探ってみることにしました。
MongoDBでは公式でいくつかのツールが用意されています。MongoDB同士のマイグレーションに使えそうなものを3つ見つけました。それぞれにどのような特徴があるのでしょうか。
- mongodump/mongorestore
- mongoimport/mongoexport
- MongoDB Cluster-to-Cluster Sync(mongosync)
mongodump / mongorestore とは
mongodumpはMongoDBからBSON形式(Binary JSON)でデータベースの中身を取り出すユーティリティです。MongoDBは内部ではBSONでデータを保持しており、それをそのまま取り出せるということです。(そもそもMongoDBが良く知られたJSONではなく、どうしてBSONでデータを取り扱うのかは、こちらのポストを読んでみてください)
MySQLやPostgreSQLにもダンプするためのコマンドがありますが、それのMongoDBバージョンだと思っていただければ大丈夫です。
公式ドキュメントではバックアップと復元のために使用するツールとして紹介されています。
MongoDB ツールを使用した自己管理型配置のバックアップと復元
MongoDB Server間の互換性を確認すると、以下のように紹介されています。
mongodump
バージョン100.10.0
では次のバージョンの MongoDB Server をサポートしています。
- MongoDB 7.0
- MongoDB 6.0
- MongoDB 5.0
- MongoDB 4.4
- MongoDB 4.2
mongodump
は、以前のバージョンの MongoDB サーバーでも動作する可能性がありますが、互換性は保証されません。
互換があるバージョン同士であれば、マイグレーション用途としても使えそうですね。
mongoexport / mongoimport とは
mongodumpがBSON形式でデータをエクスポートするのに対して、mongoexportはJSONやCSV,TSV形式でデータをエクスポートするユーティリティです。MongoDB Server間の互換性に関してはmongodumpと全く同じ記載ですが、どちらかと言えばMongoDBとその他のデータストア間で使うことを想定していると思いますので、MongoDB同士のデータマイグレーションにおいては(間にデータ加工をはさみたいなどの要件がない限り)mongodump/mongorestoreよりもこちらを使うべき、というシーンはほぼないかと思います。
MongoDB Cluster-to-Cluster Sync(mongosync)とは
ドキュメントを読んでみると、以下のように記述があります。
mongosyncバイナリーは、Cluster-to-Cluster Sync で使用されるプライマリ プロセスです。
mongosync
はソースクラスターから宛先クラスターにデータを移行し、同期が完了するまでクラスターを継続的に同期させます。
mongosync
を使用すると、本番環境をミラーリングした分析、開発、またはテスト専用のクラスターを作成できます。
細かいことは良いからMongoDB Server間のデータをまるっと移行したい!というときに使えそうです。
mongodump/mongorestore と mongosyncの2つについて、データマイグレーション時の使い分けを探っていきたいと思います。本当は、今回の記事ですべて書き切りたかったのですが、ブログがあまりに長くなりそうなので、今回の記事ではmongodump/mongorestoreを使って検証した結果のみを記載します。mongosyncは次の記事で。なるべく早く公開します!すみません!
検証環境
検証環境は以下の通りです。
移行元(ホスト名:freelen-mongodb)
- MongoDB Enterprise 6.0.18 (Standalone)
- IP Address: 192.168.50.194
- OS: Debian GNU/Linux 11.11
- CPU: 1(11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz)
- RAM: 20GB
- DISK: 150GB
移行先(ホスト名:himmel-mongodb)
- MongoDB Enterprise 7.0.0 (Standalone)
- IP Address: 192.168.50.30
- OS: Debian GNU/Linux 12.7
- CPU:1(11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz)
- RAM: 20GB
- DISK: 150GB
移行データ詳細
100GB前後のコレクションを含むデータベース
これは10GB程度ではあまりボトルネックが発生しなさそうというのと、私の環境でサッと用意できるものが最大100GBだったというのが主な理由です。サーバは2台とも同ホストの単一ESXi上で動いていて、同じネットワークに属しています。
前準備:検証用データを用意する
100GBあるコレクションを用意していきます。この手順は、皆さんの手元で実際に検証環境を用意するときにも役立つかと思います。手っ取り早くmongodump/mongorestoreを使った検証結果を知りたい方は読み飛ばしてください。
今回は、以前のブログでも紹介したmgodatagenと、そこで生成したデータ(コジャケの個人情報)を使います。今回生成するデータベース名はsalmon_employeeで、コレクション名はkojakeです。
MongoDBに大量のテストデータ(数百万件)を挿入する #mongodb #mgodatagen
以前生成したものだと、1ドキュメントあたりのサイズが小さいので、100GBを稼ぐのは厳しそうです。そこで、長文を追加してなるべく1ドキュメントあたりのサイズを大きくしてみます。
今回はchatGPTに「もしもコジャケがサーモンランの面接に参加したら」というテーマで文章を生成してもらいました。長いのでこちらには記載しませんが、なかなか面白い内容になりました。気になる奇特な方は、後ほど出てくるpersonalKOJAGEdata.jsonで読んでみてください。
最初はなるべく16MB(MongoDBが扱えるドキュメントの最大サイズ)まで近づけてください、と伝えたのですが、考えてみると、テキストだけで16MBってとんでもなく大きなサイズなんですよね。無理を言ってしまってすまない。(以下chatGPTとの会話)
生成してもらった文章の部分は大体5KB前後ありました。1ドキュメントあたり5KBとして、何ドキュメントあれば100GBのコレクションができるでしょうか。chatGPT君に計算してもらいましょう。
だいたい2000万件あれば、100GBに近づきそうですね!以上を踏まえて完成したmgodatagenのコンフィグはこちらです。
$jq . personalKOJAGEdata.json [ { "database": "salmon_employee", "collection": "kojake", "count": 20000000, "content": { "_id": { "type": "objectId" }, "firstname": { "type": "faker", "method": "FirstName" }, "lastname": { "type": "faker", "method": "LastName" }, "phone": { "type": "faker", "method": "Phone" }, "email": { "type": "faker", "method": "Email" }, "workplace": { "type": "enum", "values": [ "アラマキ砦", "ムニ・エール海洋発電所", "シェケナダム", "難破船ドン・ブラコ", "すじこジャンクション跡", "トキシラズいぶし工房", "どんぴこ闘技場" ], "randomOrder": true }, "future course": { "type": "enum", "values": [ "ドスコイ", "タマヒロイ", "シャケコプター", "オオモノシャケ", "カタパッド", "コウモリ", "タワー", "ダイバー", "テッキュウ", "テッパン", "ナベブタ", "バクダン", "ハシラ", "ヘビ", "モグラ", "キンシャケ" ], "randomOrder": true }, "intro": { "type": "constant", "constVal": { "intro": "どうも、こんにちはシャッ!えっと、まずは自己紹介からさせていただくシャケ。僕の名前はシャケ、サーモンランで皆さんにとってちょっと厄介な存在として登場している、いわゆる「敵キャラ」シャケ。えぇ、僕たちがミッション中にみんなの前に立ちはだかるあの鮭たちの一員なんですシャケ。今日は「もし僕たちが面接に来たら?」っていう面白い設定みたいなので、ぜひ話を聞いてほしいシャケ。まず、僕の特技といえば、なんといっても「チームプレイ」シャケ。僕たち鮭は、決して一匹だけで挑むことはしないシャケ。いつも大群で動くんだシャケ。サーモンランで見たことあるシャケ?あのうじゃうじゃと押し寄せる鮭の大群。それが僕たちの強みシャケ。相手にプレッシャーを与えるためにも、常に数で勝負するって決めてるシャケ。そして、僕たちは「しぶとさ」も自慢の一つシャケ。普通のゲームだと、敵を倒せば終わりって感じシャケが、僕たちはそんなに簡単にはやられないシャケ。しぶとく何度でも川を遡って、プレイヤーのみなさんを圧倒していくシャケ。特に、僕たちは本能的に目的地にたどり着くことを強く求めているシャケから、どんなに撃退されても、何度でも挑戦するんだシャケ。これが僕たちのしつこさの秘密シャケ。さて、次に「役割分担」についてお話しするシャケ。実は僕たち鮭にも、色々な種類のメンバーがいるシャケ。たとえば、体が大きくて、みんなをぶつかって吹っ飛ばすタイプの「デカシャケ」とか、ものすごいスピードで動いて、プレイヤーをかき乱す「バクダン」なんて仲間がいるシャケ。僕たちにはそれぞれ得意分野があって、役割を分担しながら、チーム全体でプレイヤーを翻弄することを目指してるシャケ。僕の場合は、標準的なシャケとして、どんな場面でも万能に動けるのが強みシャケ。プレイヤーが油断している隙に、じわじわと近づいていくのが得意なんだシャケ。それに、僕たち鮭には「チーム全員での協力」っていう文化が根付いてるシャケ。たとえば、ボスシャケが指揮を取ると、僕たちはその号令に従って一斉に動くシャケ。ボスが「今だ!」って言えば、みんなで一気に襲いかかるシャケ。僕たちはプレイヤー一人を相手にしてるんじゃなくて、あくまで全体を目指して攻撃しているシャケ。だから、みんなで協力して大きな波を作り出すのが、僕たちの戦法シャケ。次に、「しぶとさ」と「タフさ」についてもう少し話させてほしいシャケ。僕たちがサーモンランでやられると、プレイヤーは一時的にホッとするかもしれないシャケが、それで終わりじゃないシャケ。僕たちはすぐに補充されて、次から次へと押し寄せるシャケ。プレイヤーがどんなに強くても、僕たちの数には勝てないシャケ。数の力を利用して、みんなをじわじわと追い詰めていくのが僕たちの得意技シャケ。さて、僕たちがなぜこうして「敵キャラ」として君臨しているのか、その理由についても少し話すシャケ。僕たちの目的は、何としてでもイクラを守り抜くことシャケ。あの「金イクラ」、あれが僕たちの命とも言えるんだシャケ。だから、プレイヤーがあのイクラを集めようとすると、僕たちは何が何でも止めようとするシャケ。もし奪われたら、それは僕たちにとっての大敗北シャケ。でも、その代わりに、金イクラを守りきれたときは、最高の勝利感を味わえるんだシャケ。僕の志望動機シャケ?それはもちろん、「プレイヤーのみんなにもっと困ってもらいたい」からシャケ!僕たちが登場することで、プレイヤーのみなさんが手に汗握るようなスリルを感じてもらえるなら、それが僕たちの存在価値シャケ。サーモンランのステージで、僕たちが出現するたびに「ああ、また来たか…!」って思ってもらえるくらい、強烈な印象を残したいシャケ。将来的な目標としては、僕自身もっと強力なシャケになりたいと思ってるシャケ。いずれは、あの「グリル」とか「タツマキ」みたいな、プレイヤーにとっての脅威となるボスキャラを目指していきたいシャケ。そして、最終的には、プレイヤーに「どうやったらこんなシャケに勝てるんだ…?」って思わせたいシャケ。それが僕の最終的な夢シャケ。" } } } } ]
それでは2000万件生成していきましょう。移行元サーバのスペックで、だいたい5分前後かかりました。
$./mgodatagen -f personalKOJAGEdata.json connecting to mongodb://127.0.0.1:27017 MongoDB server version 6.0.18 Using seed: 1729756121 collection kojake: done [====================================================================] 100% +------------+----------+-----------------+-----------------+ | COLLECTION | COUNT | AVG OBJECT SIZE | INDEXES | +------------+----------+-----------------+-----------------+ | kojake | 20000000 | 5333 | _id_ 215648 kB | +------------+----------+-----------------+-----------------+ run finished in 5m35.7s
本当に100GBあるのか確認してみましょう。mongoshで接続して、db.コレクション名.statsで確認できます。今回はscaleオプションでGB単位で出力させています。sizeの値を見てみると、99...となっており、ほぼ100GBとなっていることが分かります。成功です!
Enterprise salmon_employee> db.kojake.stats({scale:1073741824}) (途中省略) }, sharded: false, size: 99.35197057016194, count: 20000000, numOrphanDocs: 0, storageSize: 13.91046142578125, totalIndexSize: 0.20580291748046875, totalSize: 14.116264343261719, indexSizes: { _id_: 0.20580291748046875 }, avgObjSize: 5333, ns: 'salmon_employee.kojake', nindexes: 1, scaleFactor: 1073741824 }
少し脇道にそれると、これは実際にサーバのストレージを100GB消費しているわけではありません。このコレクションのために消費されているストレージサイズは、storageSizeの値である約13.91GB と、インデックスのデータも加算された合計であるtotalSizeの約14.11GB です。
これはMongoDBのストレージエンジンであるWiredTigerがデータ保存時に圧縮しているためです。詳しくは以下のドキュメントもご覧ください。
100GBのデータベースを生成できました。次からは実際にmongodump/mongorestoreを使ったデータマイグレーションの方法を模索していきます。
mongodumpを使ってダンプファイルを取得する
mongodumpを使って、ダンプファイルを取り出してみます。今回は移行元サーバ(ローカル)で実行しています。100GBありますが、3分12秒で完了しました。
$mongodump --db=salmon_employee --collection=kojake 2024-10-28T16:08:18.273+0900 writing salmon_employee.kojake to dump/salmon_employee/kojake.bson 2024-10-28T16:08:21.269+0900 [........................] salmon_employee.kojake 348831/20000000 (1.7%) 2024-10-28T16:08:24.270+0900 [........................] salmon_employee.kojake 672427/20000000 (3.4%) 2024-10-28T16:08:27.269+0900 [#.......................] salmon_employee.kojake 996030/20000000 (5.0%) (途中省略) 2024-10-28T16:11:24.269+0900 [#######################.] salmon_employee.kojake 19321581/20000000 (96.6%) 2024-10-28T16:11:27.270+0900 [#######################.] salmon_employee.kojake 19648318/20000000 (98.2%) 2024-10-28T16:11:30.268+0900 [#######################.] salmon_employee.kojake 19962491/20000000 (99.8%) 2024-10-28T16:11:30.608+0900 [########################] salmon_employee.kojake 20000000/20000000 (100.0%) 2024-10-28T16:11:30.609+0900 done dumping salmon_employee.kojake (20000000 documents)
ダンプしたファイルは実際にはどのような形をしているのか見てみましょう。
mongodumpを実行したディレクトリ下にdumpというディレクトリが出来ます。その下にデータベース名のディレクトリ(今回はsalmon_employee)があり、その下にbsonファイルとjsonファイルが出来ています。これがダンプファイルの実態です。
$ls -lh dump/salmon_employee/ total 100G -rw-r--r-- 1 nika nika 100G Oct 28 16:11 kojake.bson -rw-r--r-- 1 nika nika 173 Oct 28 16:08 kojake.metadata.json
bsonファイルは100Gあることが分かります。つまりWiredTigerが圧縮する前の形でダンプされるわけです。そのため、mongodump実行時にサーバのストレージ容量が足りなくなることがあり、注意が必要です。ダンプするコレクションのサイズはdb.collection.stats()などで事前に調べておきましょう。
圧縮オプション(--gzip)を使う
mongodumpには圧縮オプション(--gzip)があります。
サーバの空きストレージに不安がある方はこちらを使ったほうがいいでしょう。今回は同じデータベースに対して--gzipオプションを使うとどうなるか見ていきましょう。
$mongodump --gzip --db=salmon_employee --collection=kojake 2024-10-28T16:18:55.040+0900 writing salmon_employee.kojake to dump/salmon_employee/kojake.bson.gz 2024-10-28T16:18:58.035+0900 [........................] salmon_employee.kojake 124998/20000000 (0.6%) 2024-10-28T16:19:01.035+0900 [........................] salmon_employee.kojake 250547/20000000 (1.3%) 2024-10-28T16:19:04.035+0900 [........................] salmon_employee.kojake 369048/20000000 (1.8%) (途中省略) 2024-10-28T16:26:55.041+0900 [#######################.] salmon_employee.kojake 19795978/20000000 (99.0%) 2024-10-28T16:26:58.035+0900 [#######################.] salmon_employee.kojake 19920475/20000000 (99.6%) 2024-10-28T16:26:59.929+0900 [########################] salmon_employee.kojake 20000000/20000000 (100.0%) 2024-10-28T16:26:59.929+0900 done dumping salmon_employee.kojake (20000000 documents)
非圧縮の時は3分12秒だったのに対して、今回は8分4秒かかっています。圧縮している分処理に時間がかかるようです。サイズはどのぐらい圧縮されたのでしょうか。
$ls -lh dump/salmon_employee/ total 2.1G -rw-r--r-- 1 nika nika 2.1G Oct 28 16:26 kojake.bson.gz -rw-r--r-- 1 nika nika 153 Oct 28 16:18 kojake.metadata.json.gz
100GBが2.1GBまで小さくなりました。かなり小さくなっていますね。
mongorestoreを使ってリストアする
移行先サーバにリストアするには、mongorestore実行時に--uriオプションを指定するか、SCPなどで移行先サーバにダンプファイルを転送し、mongorestoreを実行する必要があります。
ここは、移行元と移行先のネットワークの距離に応じて対応が変わりそうです。移行元と移行先のサーバが同じネットワークセグメント内にあり、帯域に心配がない場合は--uriオプションを使って移行元サーバから移行先サーバに直接リストアすれば良いでしょうし、2サーバ間に物理的に距離があるなら、dumpディレクトリをtarで固めてUSBメモリ等で運搬するほうが良いかもしれません。
今回は--uriオプションを使用するパターンをやってみましょう。
--uriオプションを使用する
移行元サーバからmongorestoreコマンドを実行します。この時注意すべきことは、以下の2つです。
- 移行元サーバから移行先サーバへのmongod(デフォルトは27017番ポート)へのアクセスが可能であること
- mongorestoreで使用するユーザに適切な権限が付与されていること
ネットワーク越しのリストアとなりますので、移行先サーバのMongoDBでは接続できるホストを制限し、認証必須にしておきましょう。こちらは一般的なサーバセキュリティに通じる話です。詳しくは、自己管理型配置のプロダクションノートのネットワーキングの章をご確認ください。
どのぐらいのセキュリティレベルが必要かはシステムの要件によって異なります。今回の環境では、移行元と移行先のサーバはどちらも同セグメント内にいるものとし、移行先サーバは以下のように設定しています。
nika@himmel-mongodb:~$ cat /etc/mongod.conf # mongod.conf # for documentation of all options, see: # http://docs.mongodb.org/manual/reference/configuration-options/ # Where and how to store data. storage: dbPath: /var/lib/mongodb # engine: # wiredTiger: # where to write logging data. systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log # network interfaces net: port: 27017 bindIp: 127.0.0.1,192.168.50.30 # how the process runs processManagement: timeZoneInfo: /usr/share/zoneinfo security: authorization: enabled #operationProfiling: #replication: #sharding: ## Enterprise-Only Options: #auditLog:
mongorestoreでは以下のユーザを使用します。db: 'admin'に対してrestoreのロールを付与すると、移行先サーバのmongoDB内すべてのデータベースに対してリストアが可能です。
Enterprise admin> db.getUser("suletta") { _id: 'admin.suletta', userId: UUID('2756664d-4269-429f-b24c-42403a187b1b'), user: 'suletta', db: 'admin', roles: [ { role: 'restore', db: 'admin' } ], mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ] }
このユーザがリストアできるデータベースを絞りたい場合は、dbをadminから指定データベース名(今回の場合はsalmon_employee)に変更します。
それでは、実際にリストアしてみましょう。移行元サーバであるfreelen-mongodbから実行します。まずは圧縮なしバージョンから。
※mongorestoreコマンド内のパスワードは本来は平文で表示されますが、今回意図的に*******で秘匿しています。
nika@freelen-mongodb ~ $mongorestore --uri="mongodb://suletta:******@192.168.50.30:27017/?authSource=admin" dump/ 2024-10-31T14:49:37.083+0900 WARNING: On some systems, a password provided directly in a connection string or using --uri may be visible to system status programs such as `ps` that may be invoked by other users. Consider omitting the password to provide it via stdin, or using the --config option to specify a configuration file with the password. 2024-10-31T14:49:37.094+0900 preparing collections to restore from 2024-10-31T14:49:37.094+0900 reading metadata for salmon_employee.kojake from dump/salmon_employee/kojake.metadata.json 2024-10-31T14:49:37.101+0900 restoring salmon_employee.kojake from dump/salmon_employee/kojake.bson 2024-10-31T14:49:40.094+0900 [........................] salmon_employee.kojake 744MB/99.3GB (0.7%) 2024-10-31T14:49:43.094+0900 [........................] salmon_employee.kojake 1.44GB/99.3GB (1.5%) 2024-10-31T14:49:46.094+0900 [........................] salmon_employee.kojake 2.18GB/99.3GB (2.2%) (途中省略) 2024-10-31T14:56:19.095+0900 [#######################.] salmon_employee.kojake 98.5GB/99.3GB (99.2%) 2024-10-31T14:56:22.095+0900 [#######################.] salmon_employee.kojake 99.3GB/99.3GB (99.9%) 2024-10-31T14:56:22.388+0900 [########################] salmon_employee.kojake 99.3GB/99.3GB (100.0%) 2024-10-31T14:56:22.388+0900 finished restoring salmon_employee.kojake (20000000 documents, 0 failures) 2024-10-31T14:56:22.388+0900 no indexes to restore for collection salmon_employee.kojake 2024-10-31T14:56:22.389+0900 20000000 document(s) restored successfully. 0 document(s) failed to restore.
6分45秒かかりました。圧縮あり(--gzip)だとmongodumpしたときと同様にリストアに時間がかかるのでしょうか?やってみましょう。
nika@freelen-mongodb ~ $mongorestore --gzip --uri="mongodb://suletta:*******@192.168.50.30:27017/?authSource=admin" dump/ 2024-10-31T14:37:21.051+0900 preparing collections to restore from 2024-10-31T14:37:21.051+0900 reading metadata for salmon_employee.kojake from dump/salmon_employee/kojake.metadata.json.gz 2024-10-31T14:37:21.055+0900 restoring salmon_employee.kojake from dump/salmon_employee/kojake.bson.gz 2024-10-31T14:37:24.051+0900 [........................] salmon_employee.kojake 16.8MB/2.02GB (0.8%) 2024-10-31T14:37:27.051+0900 [........................] salmon_employee.kojake 33.4MB/2.02GB (1.6%) 2024-10-31T14:37:30.051+0900 [........................] salmon_employee.kojake 49.8MB/2.02GB (2.4%) 2024-10-31T14:37:33.052+0900 [........................] salmon_employee.kojake 66.2MB/2.02GB (3.2%) (途中省略) 2024-10-31T14:43:33.051+0900 [#######################.] salmon_employee.kojake 2.00GB/2.02GB (98.6%) 2024-10-31T14:43:36.052+0900 [#######################.] salmon_employee.kojake 2.01GB/2.02GB (99.4%) 2024-10-31T14:43:38.260+0900 [########################] salmon_employee.kojake 2.02GB/2.02GB (100.0%) 2024-10-31T14:43:38.260+0900 finished restoring salmon_employee.kojake (20000000 documents, 0 failures) 2024-10-31T14:43:38.260+0900 no indexes to restore for collection salmon_employee.kojake 2024-10-31T14:43:38.260+0900 20000000 document(s) restored successfully. 0 document(s) failed to restore.
6分17秒かかりました。mongodumpの時と違い、mongorestoreの時は圧縮の有無で時間の差はあまり無いようです。
まとめ:mongodump/mongorestoreはマイグレーションに使えるのか?
以下の点に注意すれば、MongoDB同士のマイグレーションツールとしても使えそうということが分かりました。
- mongodump+(データの移動)+mongorestoreにかかる時間をシステムのメンテナンス時間として許容できる
- 移行元サーバにダンプファイルを一時的に配置することができる(圧縮オプションを使用すればかなり小さくすることが可能)
この記事を読んだ方の参考になれば幸いです。次回は、mongosyncでのマイグレーション検証と使い分けについてです。