目次
こんにちは!moridy です。前回の記事を執筆してから 10 ヶ月ほどでしょうか、早いもので grasys に入社してから 1 年が経ちました。
今回は業務で DMS を触った際に、テーブルを分割して移行すると速度が向上するなど、いくつか大事なポイントがあったのでそれらをご紹介していきます。
この記事では Aurora MySQL から Oracle への移行を主に扱います。
AWS DMS とは?
DMS は Database Migration Service の略で、AWS が提供するデータ移行サービスのことです。異種 DB 間の移行や運用中の継続的なレプリケーション(CDC)にも対応しています。
DMS の移行は主に以下の 3 つのフェーズで構成されます。
- 既存データの移行 (フルロード)
- まずフルロードで既存のデータをターゲット DB に移行します。ここでは DB に select 文のクエリを発行してデータを取り込みます。
- キャッシュされた変更の適用
- フルロード中にソース DB に変更があった場合その変更はキャッシュされます。バイナリログを利用することで変更をキャッチしています。フルロードが完了するとキャッシュされていた変更がターゲット DB に適用されます。
- 継続的なレプリケーション(CDC)
- フルロード完了後は CDC に移り、変更が発生し次第ターゲット DB に反映が行われます。
まずは制限事項を読もう
DMS が対応している各 DB について、ソースとターゲットそれぞれの場合の注意事項がまとめてあります。日本語だと理解し難い部分が多いので、原文も読みましょう。
テーブルの分割移行
元々 DMS では複数のテーブルがタスクに含まれる場合には、それぞれのテーブルを並列で移行します。しかし、特定のテーブルの移行に時間がかかる際にはそれだと不十分です。そうした場合は時間のかかるテーブルを分割して並列移行することで速度の向上を図ることができます。Oracle への移行では以下の 2 通りの分割が可能です。
タスク内で分割
1 つのタスク内でテーブルを分割し並列して移行を行うパターンです。使用にはまずターゲットエンドポイントで以下の設定が必要です。UseDirectPathFullLoad と DirectPathParallelLoad を true に設定しましょう。
JSON にすると以下の様になります。
{
"ExtraArchivedLogDestIds": [],
"DatabaseName": "ORCL",
"Port": 1521,
"ServerName": "test-oracle.12345.ap-northeast-1.rds.amazonaws.com",
"Username": "test",
"UseDirectPathFullLoad": true,
"DirectPathParallelLoad": true
}
次に task 設定の FullLoadSettings の項目で CreatePkAfterFullLoad を true に設定しましょう。制約やインデックスがあるテーブルでは DirectPathParallelLoad を使用できないので、フルロードが完了するまで制約の作成を遅らせます。
もし手動でテーブルを作成済みであれば、制約やインデックスを一旦削除しましょう。
{
"FullLoadSettings": {
"CommitRate": 10000,
"StopTaskCachedChangesApplied": false,
"StopTaskCachedChangesNotApplied": false,
"MaxFullLoadSubTasks": 8,
"TransactionConsistencyTimeout": 600,
"CreatePkAfterFullLoad": true,
"TargetTablePrepMode": "DROP_AND_CREATE"
},
}
最後にテーブルマッピングにタスク分割のルールを設定すれば OK です。パーティションごとに分割することも可能ですが、以下の様に指定したカラムの値で分割することも可能です。boundaries の項目では分割の境界を指定するので、以下の例では record_num が 900 より大きい場合も移行の対象となります。
{
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "1",
"object-locator": {
"schema-name": "test",
"table-name": "test_table"
},
"rule-action": "include",
"filters": []
},
{
"rule-type": "table-settings",
"rule-id": "2",
"rule-name": "2",
"object-locator": {
"schema-name": "test",
"table-name": "test_table"
},
"parallel-load": {
"type": "ranges",
"columns": [
"record_num"
],
"boundaries": [
[
"300"
],
[
"600"
],
[
"900"
]
]
}
}
]
}
複数のタスクに分割
複数のタスクにテーブルを分割して並列で移行を行うパターンです。この場合ターゲットエンドポイント設定で UseDirectPathFullLoad は false に設定します。これを true にしておくと表ロックがかかり複数タスクでの並列移行ができません。DirectPathParallelLoad も使わないので false で OK です。
{
"ExtraArchivedLogDestIds": [],
"DatabaseName": "ORCL",
"Port": 1521,
"ServerName": "test-oracle.12345.ap-northeast-1.rds.amazonaws.com",
"Username": "test",
"UseDirectPathFullLoad": false,
"DirectPathParallelLoad": false
}
次に同じテーブルを移行するタスクの設定で TargetTablePrepMode を DO_NOTHING にしてください。TargetTablePrepMode はフルロード前のターゲット DB の前処理を設定する項目なので、複数タスクで並列移行しようとするとタスクごとに前処理が実施されてしまいます。前処理が必要な場合は手動で truncate や drop クエリを実行してください。
{
"FullLoadSettings": {
"CommitRate": 10000,
"StopTaskCachedChangesApplied": false,
"StopTaskCachedChangesNotApplied": false,
"MaxFullLoadSubTasks": 8,
"TransactionConsistencyTimeout": 600,
"CreatePkAfterFullLoad": false,
"TargetTablePrepMode": "DO_NOTHING"
},
}
最後にテーブルマッピングですが、フィルター機能を使って各タスクが移行する範囲が被らない様に設定を行います。
TASK1 の設定
{
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "1",
"object-locator": {
"schema-name": "test",
"table-name": "test_table"
},
"rule-action": "include",
"filters": [
{
"filter-type": "source",
"column-name": "customer_no",
"filter-conditions": [
{
"filter-operator": "between",
"start-value": 0,
"end-value": 500
}
]
}
]
}
]
}
TASK2 の設定
{
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "1",
"object-locator": {
"schema-name": "test",
"table-name": "test_table"
},
"rule-action": "include",
"filters": [
{
"filter-type": "source",
"column-name": "record_num",
"filter-conditions": [
{
"filter-operator": "gte",
"value": 501
}
]
}
]
}
]
}
変換ルール
テーブルマッピングの設定では変換ルールを設定することができます。変換ルールは異種 DB 間の差を吸収するために欠かせませんが癖があります。
“同じオブジェクト(スキーマ、テーブル、列、テーブルとテーブルスペース、またはインデックスとテーブルスペース)に対して複数の変換ルールアクションを適用することはできません。“
つまり、同じカラムへ適用できる変換ルールは 1 つだけです。Oracle への移行に際してカラム名を大文字にしたりしていると、そのカラムのデータ型を変更したりといった変換ルールは適用できません。これはオブジェクトごとに管理されている様で、特定のカラムを削除したのちに全カラム対象のルールを適用するといったことは可能です。安易な変換ルールを追加すると、後々面倒になるので、移行に必要なルールを見極めましょう。
フルロード中のメンテナンス
DMS のレプリケーションインスタンスにはメンテナンスウィンドウが設定されており、一週間に一度再起動が行われます。その際にフルロード中だと、進捗が失われてしまいます。移行に一週間以上かかる DB はフルロードを永遠に完了できないということです。そこまで巨大な DB は稀だと思いますが、例えば移行する DB 間でデータの変換を行う様な場合だと、移行時間が極端に長くなる場合があるので注意しましょう。
(裏技としてメンテナンスウィンドウを変更し続けることでメンテナンスを回避できます。サーバーレスレプリケーションではこの手は使えません。)
CDC は一時停止が可能
DMS のタスクは CDC の状態であれば停止時点から再開が可能です。ただしフルロード中のテーブルがあった場合、そのテーブルは最初からフルロードを行うことになります。
コンソールから操作する場合はポップアップにフルロード(再起動)も表示されるので、間違えない様に気をつけましょう。
制御テーブルでタスクの詳細を確認
タスク設定から制御テーブルを有効にすると、ターゲット DB 内に制御テーブルが作成され、詳細なタスクの実行状況を確認できます。分析時には有効にするのがおすすめです。以下の 4 つの制御テーブルを利用できます。
- awsdms_apply_exceptions(常に有効です)
- エラー情報
- awsdms_status
- 現在実行中のタスクの情報
- awsdms_suspended_tables
- 移行が停止しているテーブルの情報
- awsdms_history
- レプリケーションの履歴
{
"ControlTablesSettings": {
"historyTimeslotInMinutes": 5,
"HistoryTimeslotInMinutes": 5,
"StatusTableEnabled": true,
"SuspendedTablesTableEnabled": true,
"HistoryTableEnabled": true,
"ControlSchema": "",
"FullLoadExceptionTableEnabled": false
},
}
制御テーブルは小文字で登録されているので、Oracle で探す際には以下の例の様にダブルクォーテーションをつけましょう。
SELECT * FROM TEST."awsdms_status";
サーバーレスレプリケーションが便利
レプリケーションインスタンスのサイズを変更するためには、同インスタンスで動いているタスクを停止しなければいけません。フルロード中だけインスタンスサイズを大きくして・・・ということがなかなか億劫です。
その点サーバーレスレプリケーションでは負荷によって自動でリソースを拡張・縮退してくれるので手放しで OK です。プロビジョニングに 1 時間近くかかったり、自動でのリソース縮退が実施されると縮退が完了するまで設定を変更できなかったりと不便な点はあるので、そこだけ注意しましょう。
ストレージサイズでの IOPS 制限
dms のレプリケーションインスタンスはストレージとして gp2 を使用しており、最低でも 50GiB を持っています。gp2 は割り当てが 3 IOPS/GiB であるため、ベースの IOPS が 150 程度となっています。クレジットで 3000IOPS までバーストすることは可能ですが、大きいトランザクションではクレジットが足りず、ボトルネックになることがままあります。ストレージは一度拡張すると縮退できないので、CDC する場合などはあらかじめ適正なサイズを調べましょう。
まとめ
いかがだったでしょうか。DMS は便利ですが、プロビジョニングに時間がかかるためボトルネックを探るために試行錯誤していると時間を浪費してしまうこともあります。事前に要点を把握して、スムーズに移行を実施できる様に準備しましょう!