AWS Database Migration Serviceを使ったデータベースの縮退
Tags: aws, webdev, programming, development
Published: 2018-10-02 08:08:42

---
title: AWS Database Migration Serviceを使ったデータベースの縮退
published: true
description: 
tags: #aws, #webdev, #programming, #development
---

本稿は「[AWS Database Migration Serviceを使って水平分割されたDBの縮退に挑戦した話](https://qiita.com/migimigi_/items/76ae6ef8b75df11a41e2)」をもとに加筆・補正し、文章を整えました。

# 概要
[AWS Database Migration Service](https://aws.amazon.com/jp/dms/) (DMS)を使って、水平分割されたデータベース(DB)インスタンスを縮退しようと計画したところ、検証中に不具合らしきものを見つけたので**中断**しました。この記事はDMSを検証中に引っかかったトラブルや対策について書いていたメモをまとめたものです。今回の記事中で説明する内容は、実環境からぼかしている箇所がありますがご容赦ください。

\* このあとの説明で用いる「メンテナンス」という単語は、ゲームシステムのメンテナンスを指しており、AWS側のメンテナンスとは異なります。

## 動機
通常DBは基本的にはスペックが不足したらスケールアップを行い対応します。株式会社gumiでは加えてより大規模な負荷でも耐えられるよう水平分割して、スケールアウトも併用する構成になっております。規模の拡大時には問題ないのですが、必要とされる負荷が下がった場合や、サービスの使われ方が変わって別のサブシステム群に負荷がかかるようになった場合などに、適切に規模を縮小する必要があります。

今回の縮退はRDS MySQL5.6インスタンス4台を1つのAurora MySQL5.6クラスターへまとめる計画です(図001)。

#### 図001■RDS MySQL5.6インスタンス4台を1つのAurora MySQL5.6クラスターへ
![aws_dms_001.png](https://thepracticaldev.s3.amazonaws.com/i/ooa8p0gbcoqe0avntr11.png)

負荷のかかる箇所が別のサブシステムになったため、このインスタンス群は使用率が劇的に下がったのでスケールダウンを行ってきました。今回は、適切なスケールダウン先のインスタンスサイズがなくなったため、スケールインの計画が立案されました。DBのスケールダウンは短時間で終わるため、通常のメンテナンス時間中に問題なく行えるのですが、スケールインの場合には課題があります。

スケールインする場合は通常、メンテナンス開始後に削減対象インスタンスから`mysqldump`し、残すインスタンスへimportする作業を行います。ですが今回の場合、元の台数も4台と多く、容量もDB1台につき150GBほど使用しておりDB内のレコード数もなかなかの規模がありました。`mysqldump`を使ったスケールイン方法では十数時間かかる見込みとなり、許容できるメンテナンス時間内に終わらないという課題がありました。そのときにDMSの存在を知り、このサービスを使って縮退できないかと思い、調査と計画を行いました。

## DMSとは
DBをオンラインのまま、ソースからターゲットへ移行できるサービスです。当初はオンプレミスのDBからRDSへの移行するためのサービスと思っていたのですが、いつの間にかどんどん機能が増えていました。たとえば選択できるソースには、現在ではつぎのようなものがあります(「[データ移行のソース](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.html)」から抜粋)。

- オンプレミスおよびEC2インスタンス上のDB
    - Oracle バージョン10.2以降、11g、12.2まで
    - Microsoft SQL Server バージョン 2005、2008、2008R2、2012、    2014、および 2016
    - MySQL バージョン5.5、5.6、5.7
    - MariaDB (MySQL互換データソースとしてサポートされています)
    - PostgreSQL 9.4 以降
    - SAP Adaptive Server Enterprise (ASE) バージョン12.5、15、15.5、15.7、16 以降
    - MongoDB バージョン2.6.xおよび3.x以降
- Microsoft Azure
    - Azure SQL Database（一部制限あり）
- Amazon RDS
    - Oracle バージョン11gおよび12c
    - Microsoft SQL Server バージョン2008 R2、2012、2014（一部制限あり）
    - MySQL バージョン5.5、5.6、5.7 (5.5は一部制限あり)
    - PostgreSQL 9.4 以降 (9.4.9未満と9.5.4は一部制限あり)
    - MariaDB (MySQL互換データソースとしてサポートされています)
    - Aurora MySQL
- Amazon S3

このように大抵のDBはソースにできます。MongoDBやS3までソースにできるようになっていたのは驚きです。ターゲットは上記リストと大体同じなのですが、つぎのような差分があります(「[データ移行のターゲット](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.html)」から抜粋)。

- ソースは可能だがターゲットは不可能
    - MongoDB
    - MS Azure上のAzure SQL Database
- ソースは不可能だがターゲットは可能
    - Aurora PostgreSQL
    - Redshift
    - DynamoDB

[MongoDBからDynamoDBへの移行](https://aws.amazon.com/jp/about-aws/whats-new/2017/04/aws-database-migration-service-adds-support-for-mongodb-and-amazon-dynamodb)という用途にもDMSは使えますし、マッピングを頑張れば[MySQLからDynamoDBへの移行](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.DynamoDB.html)も可能です。

### DMSで重要な概念
DMSのドキュメント「[AWS Database Migration Service](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/Welcome.html)」はボリューム満点です。全部読むのは辛いのでここだけは抑えておきたい、という概念や箇所を説明します。

- [レプリケーションインスタンス](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_ReplicationInstance.html)
    - ソースから読み取り、ターゲットへ書き出すインスタンス
        - 正確にはレプリケーションタスクを動かすためのインスタンス
    - DMSで移行をするためには必ず必要となります。
    - ソースとターゲット両方にアクセスできるセキュリティーグループを設定する必要があります。

- [タスク](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.Creating.html)
    - レプリケーションインスタンス上で動作する移行の定義
    - ソース, レプリケーションインスタンス, ターゲットを3つ組で指定します。
    - タスクには3種類の移行方法が選択できます。
        - データをターゲットデータベースに移行します(Full Load)。
            - タスク開始時のソースの中身をターゲットへ移行すします。これはフルロード(FL)と呼ばれます。
        - 移行中に変更をキャプチャします(full-load-and-cdc)。
            - フルロード後、継続的なレプリケーションを開始すします。このレプリケーションをドキュメントでは変更データキャプチャ(CDC)と呼んでいます
            - オンラインで移行している場合、フルロード中に起きたソース側の変更は当然ターゲットには移行されません。
                - その変更をレプリケーションインスタンスはキャッシュしており、フルロード後キャッシュされた変更を適用後レプリケーションを開始します。
        - データ変更のみソースデータベースにレプリケーションします。
            - タスク開始後からのソース側の変更をターゲットにレプリケーションします(移行用途では使えないので今回は説明しません)。
- フルロード(FL)
    - ターゲットがMySQLもしくはAurora MySQLの場合、ソースからのデータをCSVで取得し、[`LOAD DATA INFILE`](https://dev.mysql.com/doc/refman/5.6/ja/load-data.html)している挙動を示しています。
- 変更データキャプチャ(CDC)
    - 進行状況はCloudWatchのメトリクスである程度把握することができます。

> たとえば、`CDCLatencySource`値と`CDCLatencyTarget`値を組み合わせることで、タスクの合計レイテンシー (レプリカラグ) を調べることができます。
\*「[データ移行サービスメトリクス](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Monitoring.html#CHAP_Monitoring.Metrics)」より

- [タスクの検証](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Validating.html)
    - タスク単位で有効か無効かを選択できるオプション、デフォルトでは無効。
    - 有効にするとFL後、ソースデータとターゲットデータの検証を開始し、行単位で比較し不一致を検出します。
        - CDC時の増分変更も検証します。
    - 有効にするとソースとターゲットに対してクエリを発行しタスク側で比較するので、ソース, レプリケーションインスタンス, ターゲットの負荷は無効に比べて上昇します。

- [テーブルマッピング](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TableMapping.html)
    - タスク単位で有効か無効かを選択できるオプション、デフォルトでは無効。
    - 通常DMSではデフォルトではソースの(MySQLでいう)データベースやテーブルを全てそのまま移行します。
        1. そのまま移行するとデータベース名やテーブル名が衝突してしまいます。
        2. 移行の必要がないデータベースやテーブルがあります。
            - このような場合に指定します。
    - 今回の移行のケースでは移行する必要があるデータベースのみ対象になるテーブルマッピングを設定しました。


## DMSの制約
ここまで読むと、DMSはつぎのように短時間のメンテナンスで済むような素晴らしい機能を備えているようにみえます。

1. 「移行中に変更をキャプチャする」移行方法でソース4台からターゲット1台へ移行します。
2. FL完了しCDCでレプリケーションに入りソースにターゲットが追いついたら、メンテナンスに突入、ソースへの更新が止まります。
3. 「タスクの検証」で正しく移行できたこと、CDCのレプリカラグが0であることを確認します。
4. ソースの`AUTO_INCREMENT`値をターゲットへ移行します。
5. アプリケーション側のDBへのエンドポイントをソースからターゲットへ書き換えます。
6. 動作確認後、メンテナンス終了。

しかし、DMS自体の制約が多々あるのでそこまで簡単ではありません。

> AWS DMS は、移行の実行に必要なターゲットスキーマオブジェクトを作成します。ただし、AWS DMS は最小限のアプローチを採用するため、データの効率的な移行に必要なオブジェクトのみ作成します。つまり、AWS DMS は、テーブル、プライマリキー、場合によっては一意のインデックスを作成しますが、効率的にソースからデータを移行するために必要ではない他のオブジェクトは作成されません。たとえば、セカンダリインデックス、非プライマリキーの制約、データデフォルトは作成されません。
\*「[AWS DMS の概要](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Introduction.html#CHAP_Introduction.HighLevelView)」より

つまり、DMSが用意してくれるのは、中身は統合済みでも、プライマリキー以外のインデックスがまったくない、デフォルト値も設定されていないデータベースなのです。そのまま移行したらあっという間にシステムダウンです。

私たちが把握しているかぎり、MySQL5.6からAurora MySQL5.6へDMSで移行されないものはつぎのとおりです(まだ漏れはあるかもしれません)。

1. セカンダリインデックス(普段はインデックスとだけ呼ぶことが多いです)
2. デフォルト値
3. 列の`AUTO_INCREMENT`属性(「[MySQL データベースを AWS DMS のソースとして使用する場合の制限](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.MySQL.html#CHAP_Source.MySQL.Limitations)」参照)
4. `AUTO_INCREMENT`値
5. パーティショニング
6. ビュー
7. トリガー
8. ストアドプロシージャ

私たちのシステムで使っていない属性も多いので、今回は使用している上記1から4までを自分たちで移行することにしました。

### DMSで移行されないものの移行
まず、移行されないこれら4つはDDL操作に属するので、「MySQL 5.6 リファレンスマニュアル」14.11.1「[オンライン DDL の概要](https://dev.mysql.com/doc/refman/5.6/ja/innodb-create-index-overview.html)」に目をとおしましょう。まずつぎのふたつは一瞬で終わります。

- デフォルト値
    - カラムのデフォルト値を設定します。
        - データファイルではなく、`.frm`ファイルのみを変更します。
- `AUTO_INCREMENT`値
    - カラムの自動インクリメント値を変更します。
        - データファイルではなく、メモリーに格納された値を変更します。

`AUTO_INCREMENT`値は整合性を保つため、メンテナンスに入ってソース側に更新がなくなったことを確認してからターゲットへ設定した方がよいでしょう。

- 列の`AUTO_INCREMENT`属性

これはプライマリキーのカラムに`AUTO_INCREMENT`属性を付与するタスクです。DMSでもプライマリキーは移行してくれます。これも正直一瞬で終わるものだと思っていたのですが、行っていることは`ALTER TABLE tbl MODIFY col type AUTO_INCREMENT`ですので、先程のオンラインDDLの概要の中の「カラムのデータ型を変更する」にあたり、強制的にテーブルコピーになります。以下が`SHOW PROCESSLIST`の結果です

```terminal
362 dms 192.0.2.1:4397  shard-A-0   Query   3   copy to tmp table   ALTER TABLE `example1` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT
```

対策としてあらかじめソースのスキーマを`mysqldump --no-data`で取得し、ターゲットに流し込んでおいてから、タスク定義で「既にターゲットにテーブルが存在したら`TRUNCATE`してから実行する」を選択することにより、列の`AUTO_INCREMENT`属性を後から時間を掛けて`ALTER TABLE`する必要がなくなりました[^1]。

残りはセカンダリインデックスだけなのですが、これについてはつぎで詳しく説明します。

### ADD INDEXをどうすればメンテナンス時間を短くできるか
そもそもインデックス付与は重いタスクです。MySQL5.6(および互換のAurora MySQL)ではインプレースで付与が可能なのですが、それはサイズが小さいテーブルの場合だけで巨大なテーブルにはテーブルコピーが行われます[^2]。そしてなによりCPUパワーを必要とします。どのタイミングで`ADD INDEX`すれば最適なのか検証しました。

1. FL→CDC→メンテナンス→`ADD INDEX`
    - メリット
        - 確実性が高い
            - 当初はこれで移行する予定でした
    - デメリット
        - `ADD INDEX`に長時間かかる
            - 当初見込みで20時間
                - CPUがボトルネックだったので現在最高の16xlargeを使用したが思ったより短縮されなかった
        - `ADD INDEX`に時間がかかるとメンテナンス時間が長引くので不採用となった
2. スキーマ流し込み（index定義付き）→FL→CDC→レプリカラグ確認→メンテナンス
    - メリット
        - 最初からスキーマを流し込んでおけばあとはDMSが全て移行してくれる
        - メンテナンス時間がエンドポイント書き換えだけで済む
    - デメリット
        - フルロードが遅すぎて途中で止まる
            - 1番の通常方法のFLで10時間程度だったのが、この方法では100時間やっても6割移行できたかどうかという進捗
            - `show engine innodb status`を見ると大量のトランザクション待ち
            - インデックス付きのテーブルに大量に挿入するのは無理があった
3. FL→`ADD INDEX`→CDC→レプリカラグ確認→メンテナンス[^3]
    - メリット
        - `ADD INDEX`が長引いてもシステムはオンラインなので問題ない
        - メンテナンス時間がエンドポイント書き換えだけで済む
    - デメリット
        - 列の `AUTO_INCREMENT`属性付与が思ったより遅かった
4. スキーマ流し込み（index定義無し）→FL→`ADD INDEX`→CDC→レプリカラグ確認→メンテナンス
    - メリット
        - `ADD INDEX`が長引いてもシステムはオンラインなので問題ない
        - メンテナンス時間がエンドポイント書き換えだけで済む
        - 列の`AUTO_INCREMENT`属性時間の短縮
    - デメリット
        - とくになし


# 結果
ここまでの検証で前項の4の手順で行えば、短いメンテナンス時間で縮退できそうだと計画を立て、本番前提の環境を構築していました。ですが構築途中に、ソースに本番の負荷がかかった状態でDMS移行すると検証エラーが大量に出ることが判明し、移行は一旦中断しました。AWSのサポートへ問い合わせしつつDMSを使わない縮退方法について検討中です。

## 備考
### 気をつけるべきこと
- レプリケーションインスタンスにソースやターゲットへ接続できる適切なセキュリティグループが指定されてないと接続テストで失敗します。
- RDS MySQL5.6をソースにするときの注意点
    - 自動バックアップを0日にすると、バイナリログがそもそも作成されなくなるのでソースとして指定しても動かなくなります。
    - CDCを使用するには、バイナリログ保持時間を伸ばす必要があります。
        - `call mysql.rds_set_configuration('binlog retention hours', 24);`
            - このクエリを投げるのには特権ユーザである必要があります。
            - バイナリログを通常より保持するようになるので、ストレージをより消費するようになります。
    - `binlog_format`をROWにする必要があります。
        - 設定はRDSのパラメーターグループから可能です。
            - DynamicなのでRDSの再起動なしで設定変更することは可能です。
        - デフォルトの`MIXED`よりパフォーマンスが下がる可能性があります。


### 行っておいてよかったこと
- DMSタスクのログを有効にすること。
    - 複数タスク必要な場合ならひとつでもいいから有効にしておくこと。
        - タスクが開始されない、タスクエラーが起こったときに大変役立ちます。
        - 付けていないと何も情報を得られないことが多いです。
- タスクの検証を有効にすること。
    - 遅くはなるが、データ移行が正しく行われたことが誰の目にも分かりやすく確認できます。


### 今後検討すべきこと
- 負荷が適切に掛かっているソースを使用して検証環境をつくること。
    - 本番インスタンスにリードレプリカを作り、そこをソースにするのがよいと思われます。
        - 本番と別のパラメーターグループを設定することもできます。
    - RDS MySQLでは簡単にAuroraリードレプリカを作成できるので、Aurora to Auroraも試してみる価値はあります。
- `AUTO_INCREMENT`属性の付与は遅いので避けたいと思っていましたが、そもそも`ALTER TABLE`文は1テーブルに対して複数の変更を書けます(13.1.7「[ALTER TABLE 構文](https://dev.mysql.com/doc/refman/5.6/ja/alter-table.html)」)。
    - `ADD INDEX`や`ADD UNIQUE KEY`は1文にまとめていましたが、`MODIFY col type AUTO_INCREMENT`もまとめてよかったのではないか。
- DMSタスクの検証機能を使わず、自前実装でデータの検証をすること。
    - 2018年8月3日開催の「[FGOなど大規模ゲームの課題から学ぶゲームサーバ・インフラ勉強会](https://connpass.com/event/91736/)」で発表されたディライトワークスの甲氏のスライドに興味深い内容がありました。
    - FGOではデータベースの水平分割のためにDMSを検証、利用していましたが、要件上DMSタスクの検証機能が使用できませんでした。
    - そのため[データの整合性はチェックサムテーブルを用意しそこで検証した](https://www.slideshare.net/hideakikabuto/db-108716671/65)とのことです。
    - 今回の私たちのデータベース縮退タスクはDMSタスクの検証機能で引っかかっていたので、それを回避するためにを自前実装するという方法もあるのだなと思いました。
        - しかし当然のことながら実装の工数と、その検証結果が正しいかどうかの検証が必要になるので簡単には選択できません。


### DMSでよく読むことになるドキュメント
- 「[AWS Database Migration Service の仕組み](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Introduction.html)」
- 「[AWS Database Migration Service のコンポーネント](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Introduction.Components.html)」
- 「[AWS Database Migration Service のベストプラクティス](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_BestPractices.html)」
- 「[タスクの作成](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.Creating.html)」
- 「[AWS DMS タスクの検証](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Validating.html)」
- 「[MySQL 互換データベースの AWS DMS のソースとしての使用](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.MySQL.html)」
- 「[AWS BlackBelt Online Seminar 2017 AWS Database Migration Service](https://www.slideshare.net/AmazonWebServicesJapan/20170919-aws-blackbelt2017dms)」

[^1]: `TRUNCATE`にしたのは`AUTO_INCREMENT`の値をリセットした方がよさそうだと思ったからで、もしかしたらDo nothingでも大丈夫かもしれません。DROPではせっかく投入したスキーマが消されるので無意味です。

[^2]: 小さいテーブルではインプレースの`ADD INDEX`が可能で200MB程度のテーブルに`ADD INDEX`をデフォルト(`ALGORITHM=INPLACE`)と`ALGORITHM=COPY`をつけて実行してみたところ、デフォルトの方が7倍ほど速かったです。

[^3]: FL後CDC前のタイミングでインデックス付与して大丈夫なのか、と思いました。ですが、公式のタスクの作成の中に「[全ロードの完了後にタスクを停止する](https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.Creating.html#w255aac23c15c11b6c11)」という設定項目で「キャッシュされた変更を適用する前に停止する - キャッシュされた変更を適用する前に、タスクを停止します。 これにより、変更の適用を高速化できるセカンダリインデックスを追加できます。」と書かれており、これはAWSの意図に沿う動作だと認識しました。