AWS DMSを使ってみた（ノウハウ）

# はじめに
ちょっと前に仕事でAWS DMSを使ったので、その時のノウハウです。
DMSはDatabase Migration Serviceの略で、（基本的には）DBからDBへ移行するためのツールです。

[AWS DMS](https://aws.amazon.com/jp/dms/)

自分が実際に使ったのはOracle→Aurora MySQLの移行です。

# ターゲットDBのテーブルは予め作っておく

DMSは、ソースDBからデータを抜き取り、一度DMSのデータ型に変換して、
更にターゲットDBのデータ型に直してデータを入れていきます。

この過程で意図しないデータ型に変換されてしまうことがあるので、
ターゲットDBには予め意図した型のテーブル・カラムを作成しておいてから、データ移行を行ったほうが良いです。

データの変換テーブルは以下のページなどを参照。
[AWS Database Migration Service のデータ型](
https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Reference.DataTypes.html)
[OracleがソースDBの時のデータ型](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.Oracle.html#CHAP_Source.Oracle.DataTypes)
[Aurora MySQLがターゲットDBの時のデータ型](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.MySQL.html#CHAP_Target.MySQL.DataTypes)

またテーブルの移行にはSCT（Schema Conversion Tool）を使うこともできます。
[AWS Schema Conversion Tool](https://aws.amazon.com/jp/dms/schema-conversion-tool/)
※これで変換されたDDLをそのまま使うのではなく、意図した型になっていることを確認してからターゲットDBに適用した方が良いです。

# トリガーなどは無効化
大量のデータがインサートされる時はできるならトリガーは無効化しておきます。
（もしトリガーの挙動が別テーブルへのデータインサートなら、そのテーブルも同期しておけばOK）
でないと、いちいちトリガーが動いてデータ同期に時間がかかります。
（フルロードのときはload data infileでロードするからトリガは動かない気もする。）

もっと言うと、インデックスもフルロード時には一時的に外しておいた方が効率的です。

# タスク作成はスクリプトなどで自動化
AWSコンソールからタスクを作成できますが、テーブルが多いときには現実的ではありません。
API/SDKなどを使って自動化できるようにしましょう。
最低限はタスクですが、DMSインスタンスなども簡単に作れるようにスクリプトを用意しておいても良いです。
[CreateReplicationTask](https://docs.aws.amazon.com/ja_jp/dms/latest/APIReference/API_CreateReplicationTask.html)
[CreateReplicationInstance](https://docs.aws.amazon.com/ja_jp/dms/latest/APIReference/API_CreateReplicationInstance.html)
あたりを参照。

# CreateReplicationTask についてもうちょっと深堀り
CreateReplicationTaskと、タスク作成に関するポイントをいくつか。

## 移行のタイプ
FullLoadとCDC(change data capture)とFullLoad+CDCから選択できます。
要件にも寄りますが、ほとんどの場合はFullLoad+CDCを選ぶことになります。
FullLoadだけを選ぶのは、もう更新されないDB(静止断面)をソースDBとするとき。
CDCを選ぶのは、DMS以外でFullLoadをするとき。同期開始地点を時間やSCN(Oracle)で指定可能(cdc-start-position/cdc-start-time)だけど、
ちゃんと環境を整えてやらないと、整合性を保つのは難しいと思われます。

※データ移行はFullLoadとCDCの2フェーズに見えますが、実はこの間にはキャッシュ適用というタイミングもあります。
これはFullLoad実施中に発生した変更をキャッシュしておき、FullLoadが終わったらそれを適用します。
キャッシュ適用が終わったら、CDCに移行します。

## タスク設定(ReplicationTaskSettings)
APIのページではさらっと書いてありますが、結構たくさんのことをJSON形式で指定する必要があります。
大きくは、FullLoad設定、CDC設定、ログ設定、エラー時の挙動など。
詳細はこちら→[AWS Database Migration Service タスク設定の指定](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TaskSettings.html)
個別のパラメータについては、更に以下にて。

### TargetMetadata.ParallelLoadThreads
ターゲットDBへのロード処理を何多重で実行するかです。(1テーブルを複数スレッドで処理します。)
Aurora MySQLの場合、auto_commitをonにしておかないとロードされないレコードが発生します。(他のDBも同様？)
（最近のDMSでは強制的にonにするようになっているとも聞きました。）

### TargetMetadata.BatchApplyEnabled
BatchApplyを有効にするかどうか。有効にすると結果整合性が取られる。
無効にするとTransactionModeになって、トランザクションの順序性が保証されます。
ChangeProcessingTuning内のBatchApply...パラメータで細かいチューニングができます。

### FullLoadSettings.TargetTablePrepMode
FullLoad開始前に対象テーブルをどうするかを設定します。
何もしない(DO_NOTHING)、Dropする(DROP_AND_CREATE)、Truncateする(TRUNCATE_BEFORE_LOAD)
の3択です。
手動でtruncateしてからDO_NOTHINGをするか、TRUNCATE_BEFORE_LOADにしておきます。
Dropすると、DMSがよしなにデータ型を決めてしまうのでおすすめしません。

### FullLoadSettings.MaxFullLoadSubTasks
FullLoadを何多重(何テーブル)同時にロードするかを設定します。
先のParallelLoadThreadsとは別の話です。

### FullLoadSettings.TransactionConsistencyTimeout
タスク開始時、残存トランザクションがあれば何秒待つか、という設定です。
デフォルトは10分(600秒)なので、その場合は最長10分待ちます。
その間にトランザクションが完了しなければ、そのトランザクションの完了を待たずにフルロードが開始されます。
タスク開始以降に開始したトランザクションについては、キャッシュで変更が反映されますが、
タイムアウトしてしまったトランザクションは・・・・。

### ChangeProcessingDdlHandlingPolicy.*
それぞれDrop、Truncate、Alterを同期するかどうかです。
データ同期中にソースDBにDDL適用するのはおすすめしません。。。
カラム追加などすると、うまくデータ同期が動かなくなったりします。
ただ、大量にDeleteするよりTruncateした方が色々効率は良いので、これだけ有効化しても良いと思います。
また、ソースDB、ターゲットDBそれぞれのDBによって、同期されるDDLに制約があります。
特にPatition系は注意が必要です。ドキュメントを良く確認する必要があります。

### ErrorBehavior.*
数が多いので割愛しますが、エラー時の挙動はちゃんと考えておいたほうが良いです。

## テーブルマッピング(TableMappings)
対象テーブルや対象外テーブルや、フィルタを設定したり、変換ルールなどをJSON形式で指定します。
APIのページの例ではJSONファイルを指定していますが、Python SDK(Boto)ではできなかったので、
JSON形式でコマンドに埋め込みました。

詳細はこちら→[テーブルマッピングを使用して、タスクの設定を指定する](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TableMapping.html)

# OracleのCapture（Extract）するツール
ソースDBがOracleの場合は、CaptureするツールとしてLogMinerとBinary Reader があります。
LogMinerの方が機能的には優れていますが、スピードはBinary Readerの方が早いです。
またLogMinerはソースDBに負荷がかかりますが、Binary Readerはネットワーク帯域とDMSインスタンスに負荷がかかります。
それぞれの挙動の概要は以下のとおりです。
詳細はこちらのページで→[AWS DMS のソースとして Oracle データベースを使用する](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.Oracle.html)

## LogMiner
LogMiner自体はOracleDBに付随するツールです。LogMiner経由でREDOを読み込み必要な変更をDMSに転送します。
そのためソースDBに負荷がかかります。

## Binary Reader
Binary ReaderはAWS独自(?)のツールです。
REDOログ(アーカイブREDOログも含む)をDMSに転送して、Binary Readerで変更を読み取ります。
REDOログをすべて転送しDMSで変更を読み取るため、ネットワークとDMSインスタンスに負荷がかかります。
確かタスク分だけ同じREDOログを転送することになるので、ネットワーク負荷はタスク分だけ倍増します。
またREDOログがASMにあったりすると、それはそれは大変な苦労を見ることになります。

# Active Data GuardのスタンバイDBをソースDBにする
スタンバイDBは読み取り専用ですが、ソースDBに指定することができます。
ただスタンバイDB自体もデータ同期していて遅延が発生するので、ターゲットDBへのデータ同期は更に遅延が発生します。
またADGとDMSを長い間停止しておくと、同期の再開時にDMSがタスクを止めていた間の変更を
移行しないことがあります。（standbyDelayTimeを参照）

# parallelLoadThreads(Aurora MySQLの追加の接続属性)
Aurora MySQLをターゲットDBとした時の、追加の接続属性に「parallelLoadThreads」があります。
タスクのTargetMetadata.ParallelLoadThreadsが優先だった気がするのですが、
もしタスクで設定しても有効にならなければ、こちらでも設定しておきます。

詳細はこちら→[AWS Database Migration Service のターゲットとしての MySQL 互換データベースの使用](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.MySQL.html)

# おわりに
リアルタイムデータ同期はかなり難しかったです。
DB移行時だけ利用するという用途であれば良いと思います。
どうしても恒久的にデータ同期する必要があるのであれば、
同期するテーブルの選定、タイミング（リアルタイム、バッチ）を良く精査しなければなりません。
またバッチであれば、ファイル連携などDMS以外の手段でも良いと思います。
（リアルタイムもDMS以外の手段がありますが。）

大量のテーブル・データを全てリアルタイムで常にデータ同期するなんて、
運用が大変（というか無理）なので、絶対にお勧めしません。