AWS DMSでMySQLからRedshiftへのマイグレーションを試してみる

# はじめに

- MySQLのデータをRedshiftへマイグレーションしたいというニーズがあり、手順について検証してみました。
要件としてはマイナーなのか、あまり情報がなかったので書き起こしておきます。

- なお、**テストは小さなテーブルで実施しました**ので、負荷テストの参考にはなりません。
また、他の方への参考のために「エラーのトラブルシュート履歴」を意識的に記載しているため、途中の設定値は正しいものではないことをご了承ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/62417231-d8a8-7a1c-c4a6-07258211d9f6.png)


# DMSの特徴

 - 様々なデータソース（移行元DB）、データターゲット（移行先DB）に対応したマイグレーションサービスです。
 - 一度きりの移行だけでなく、継続的にデータを転送することができます。
 - 移行元と移行先の間に、「レプリケーションインスタンス」という専用のインスタンスを起動しておく必要があります。
    - レプリケーションインスタンスはインスタンスの一時停止ができないためコスト面で注意が必要です。


- DMSの新機能の一つに[Fleet Advisor]というものがあります。[Fleet Advisor]は今回のように移行元DBが定まっている場合には役立ちませんので、今回は利用しません。以下に調査記事のリンクを貼り付けておきます。
  - https://qiita.com/jnit/items/7ace8d013db7969cc8b7

- DMSの連携対象
  - **DMSで連携できるのはテーブルと主キーなどの一部の制約のみです。**
    - 基本的にインデックスやプロシージャ、ビューはターゲット側に手動で反映する必要があります。
    - 出典：https://d1.awsstatic.com/webinars/jp/pdf/services/20210216_Blackbelt_DatabaseMigrationService.pdf
  - ただしビューについては、移行タイプを「フルロード（既存のデータを移行する）」に設定した場合のみ、オプションの追加によりビューをテーブルとして移行できます。（これも今回検証してみます）
      -  ※「フルロードのみ」場合は差分転送が行われませんので、実行頻度の検討が必要になります。
      - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Task.CDC.html

- DMSのサポートDBエンジンは以下の情報源を参照ください。
  - ソースDB
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Introduction.Sources.html  
  - ターゲットDB
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Introduction.Targets.html

# 今回の構成


| 対象 | DB Engine | DB Version | Location | spec | 
|:-----------|:------------|------------:| - | - |
| 移行元DB       | MySQL on EC2| 5.7.38 | AWS VPCのEC2    |t3.small|
| レプリケーションインスタンス       | - | 3.4.7 | AWS VPC |dms.t3.micro, 50GB|
| 移行先DB     | Redshift     | 1.0.41881 | AWS VPC      |dc2.large|


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/d37b351c-a73c-b985-db1f-d5cc9ad41a71.png)



# 諸条件

- **ソースがMySQLの場合、Redshiftへの変換にSCTが利用できない**
  - DMSにはSCT(SchemaConversionTool)という、ローカルPCにインストールしてスキーマやストアドプロシージャ等を自動変換・診断してくれるツールがあります。このSCTはソースDBとターゲットDBの組み合わせごとに使用可否が決まっている点に注意が必要です。
  - 以下のURLの通り、ソースがMySQLの場合、ターゲットにRedshiftを選択することができません。
    - https://docs.aws.amazon.com/SchemaConversionTool/latest/userguide/CHAP_Welcome.html
    - そもそもターゲットがRedshiftの場合は、ソースの対応DBエンジンにOLTP系のものは少なく、多くがOLAP系です。
       - OLTP系：SQL Server, Oracle
       - OLAP系：BigQuery, Snowflake,Azure Synapse Analytics等
  - ためしにSCTでソースをMySQLにしてみたところ、ターゲットにはRedshiftが表示されませんでした。仕様通りですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ea826912-1eb1-4e16-7627-9d47e31e39f7.png)
  - **DMSでのマイグレーションは、SCTが必須ということではなく、SCTを使わない場合は必要に応じて自力の対応が必要になります。** 今回は変換元がシンプルなテーブル構造であるという想定のもと、DMSのみで検証をしてみます。



- MySQLをソースとした場合の留意点
  - **MySQLでバイナリログを有効にしておく必要があります。** また、タイムゾーンを気にしておく必要があります。このへんは後ほど触れます。その他の諸条件は以下のページに記載されています。
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.MySQL.html

- Redshiftをターゲットとした場合の留意点
  - Redshiftは基本的にバルクロードでデータを取得する設計になっているため、ターゲットをRedshiftに設定した場合、DMSはいったんS3にcsvデータを吐き、自動でそれをRedshiftにCOPYします。それ用のリソースが自動で作成されます。
    - S3バケット：dms-(乱数)
    - IAMロール：dms-access-for-endpoint
    - > Amazon Redshift ターゲットエンドポイントにデータを移行する際、AWS DMS はデフォルトの Amazon S3 バケットを中間タスクストレージとして使用し、その後移行されたデータを Amazon Redshift にコピーします
    - > AWS DMS は Amazon S3 バケットを使用してデータを Amazon Redshift データベースに転送します。AWS DMS がバケットを作成できるようにするため、コンソールは IAM ロール dms-access-for-endpoint を使用します。
  - そのほか、ターゲットがRedshiftの場合、後述する **「移行前評価」が対応していない** などの制限もあります。その他の諸条件は以下のページに記載されています。
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.Redshift.html


# 検証環境の構築

今回は何もないところから検証を行いますので、ソースDBとターゲットDBを作成し、ソースDBにはサンプルデータを入れていきます。

## 【ソースDB】MySQL on EC2

Amazon Linux2に、ちょっと古いバージョンのMySQL 5.7系をインストールします。
このへんは本題ではないのでさらっと書いておきます。

### MySQLインストール

古いバージョンのリポジトリは以下です。
https://dev.mysql.com/downloads/repo/yum/

対象のバージョン選択後、ログインせずに以下のリンク右クリックでURLを取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/fb7ba679-d573-fd04-4392-a7e9b788c08e.png)


```
- MySQL7系のリポジトリをインストール
# yum localinstall https://dev.mysql.com/get/mysql80-community-release-el7-6.noarch.rpm

- MySQL 8.0をインストールしないように設定
# yum-config-manager --disable mysql80-community

- MySQL 5.7系をインストール対象に設定
# yum-config-manager --enable mysql57-community

- AmazonLinux2にデフォルトでインストールされているMariaDBライブラリの削除
# yum remove mariadb-libs

- MySQLのインストール
# yum install mysql-community-server --nogpgcheck

```

### OS周りの設定

MySQLのタイムゾーンはシステム環境変数によって表示が制御されます。
とりあえずOSレベルでJSTに設定しておきます。
```
- タイムゾーンを日本時間に設定
# vi /etc/sysconfig/clock
# cat /etc/sysconfig/clock
ZONE="Asia/Tokyo"
UTC=true
# ln -sf  /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

### MySQLの設定

**MySQlのDMSの利用条件として、[binlog_format=row]にしておく必要があります。**
また、バイナリログを有効化するには、[server-id]パラメータなどの設定も必要なので最低限の設定だけしておきます。

```
# cp -p /etc/my.cnf{,.org}
# vi /etc/my.cnf
# diff  /etc/my.cnf{,.org}
28,32d27
<
< server-id=1
< log-bin=mysql-bin
< binlog_format=row
< binlog_checksum=none

# service mysqld restart
```

上記のようにバイナリログを設定しておかないと、後述のデータベース移行タスクにおいて、タスク実行時に以下のようなエラーが出ます。
```Binary Logging must be enabled for MySQL server```

#### 初期パスワードの確認と変更

```
- 出力されるログから初期パスワードを確認する
# grep -e 'A temporary password is generated for root@localhost' /var/log/mysqld.log

- rootユーザでログインする。パスワードは先程確認したものを入力。
# mysql -u root -p

- ログイン後、パスワードを変更する
mysql> set password = '**********';
```

### ダミーデータを作成しMySQLに挿入

以下のサイトからダミーデータを生成します。

http://filldb.info/

スキーマはサンプルのものを利用します。
[Test Sample Database Schema]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/1c231c0d-8c48-37cd-b4f8-4c9d3e005b71.png)

とりあえずプルダウンから[authors]テーブルを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/04d58f42-2132-1126-6c81-852d83d80621.png)

データ入力規則はとくに変えずに、件数として 1000 を入力して[Generate data]を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/fe1843a9-8244-8bef-49f2-365255e8650d.png)



生成されたデータを、[Export authors]ボタンからダウンロードします。これでsql文のファイルがローカルにダウンロードされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/0aafa452-9243-d01a-e0ef-8adadfabb5ec.png)

同じようにpostsテーブルの行を、こちらは10000件で作成します。
それぞれの.sqlファイルをSSHセッションにアップし、sourceコマンドで読み込ませます。
※私の場合、エラーが起きたのでコピペで実行しました。

件数は正しく入っているようです。
```sql
mysql> select count(*) from authors;
+----------+
| count(*) |
+----------+
|     1000 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from posts;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.02 sec)

```

:::note info
なお、後述しますがDMS専用のMySQLユーザも作っておいたほうがよいです。ここでは作っていない体で進めます。
:::


## 【ターゲットDB】Redshift

EC2と同じVPC内に最小サイズのdc2.largeで作成します。
IAMロールは、とりあえず無しで作成します。
それ以外は特筆すべきものがないので割愛します。

:::note info
なお、後述しますがIAMロールは付与する必要があります。ここでは付与していない体で進めます。
:::

# 移行の手順検証

ソースDBとターゲットDBが作成できたので、DMSを触って移行手順を検証していきます。
以下のような順番で進めます。

- レプリケーションインスタンスの作成
- エンドポイントの作成
- データベース移行タスクの作成

## DMS
### レプリケーションインスタンスの作成

レプリケーションインスタンスを作成します。
検証用ですので最小サイズのインスタンス、ストレージとし、パブリックアクセスはよくわかりませんが検証データなのでとりあえず有りにしておきます。

| 項目 | 設定値 | 備考|
|:-----------|:------------|------------|
| インスタンスクラス| dms.t3.micro| 小さいテーブルなので最小サイズ|
| ストレージ     | 50GB |（デフォルト） |
| VPC |  Redshiftと同じVPC     | | 
| マルチAZ | シングル     |  | 
| パブリックアクセス可能 | チェックあり  |検証なのであまり気にせず  | 
| セキュリティとネットワークの詳細設定 | デフォルト     |  | 
| メンテナンス | デフォルト     |  | 

- 移行に適したスペックについては以下のページを参照してください。
  - https://docs.aws.amazon.com/dms/latest/userguide/CHAP_ReplicationInstance.Types.html
    - 以下のようなちょっとした助言がかいてあります。
    - > AWS DMS は、特に Oracle から PostgreSQL への移行など、異種の移行やレプリケーションを実行する場合に、CPU を集中的に使用する可能性があります。C4 インスタンスは、このような状況に適しています。

### エンドポイントの作成

- ソースエンドポイント
  - MySQL on EC2用のエンドポイントを作成
  - ソースエンジン：MySQL
  - エンドポイントデータベースへのアクセス：アクセス情報を手動で提供する
  - 接続情報のユーザー名を、最初MySQLのrootユーザにしていましたが、接続テストにおいて以下のようなfailedエラーが出ました。
  - ```Test Endpoint failed: Application-Status: 1020912, Application-Message: Cannot connect to ODBC provider ODBC general error., Application-Detailed-Message: RetCode: SQL_ERROR SqlState: HY000 NativeError: 1130 Message: [unixODBC][MySQL][ODBC 8.0(w) Driver]Host 'ip-10-0-1-125.ap-northeast-1.compute.internal' is not allowed to connect to this MySQL server```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/0f9f3596-e8e3-a738-e707-c752b3e07657.png)
  - おそらくMySQLのデフォルト設定だとリモートからのrootユーザアクセスが不可になっていると思われます。
  - **そこでDMS専用のMySQLユーザを以下のように作成し、ソースエンドポイントのユーザに指定したところ接続テストが成功しました。** 
    - 以下は全テーブルへの管理者権限かつ、%で全てのIPからのアクセスを許可していますのでご注意ください。
```sql
CREATE USER 'dms-admin'@'%' IDENTIFIED BY '*********';
show grants for 'dms-admin'@'%';
grant all on *.* to 'dms-admin'@'%';
```


- ターゲットエンドポイント
  - Redshift用のエンドポイントを作成
  - ターゲットエンジン：Amazon Redshift
  - エンドポイントデータベースへのアクセス：アクセス情報を手動で提供する
  - サーバ名、ポート、データベース名はRedshiftのエンドポイントから切り出したものを指定

上記のような感じで作り、Redshiftは接続テストも問題なくクリアしました。

### データベース移行タスクの作成

データベース移行タスクを作っていきます。
何回か失敗しているので、同じような方向けに、失敗内容も記載しておきます。

#### トライ1

- データ移行タスク
  - タスクの設定
    - (名前、ソース、ターゲット、等は割愛)
    - 移行タイプ
      - 既存のデータを移行して、継続的な変更をレプリケートする
  - タスク設定
    - (全てデフォルトのまま)
  - 移行前評価
    - **移行前評価の実行の有効化：チェックあり※**
      - 評価レポートのストレージ：(新規作成したバケットを指定)
      - IAM ロール：(S3フルアクセスを持つIAMロールを新規作成し指定)
  - 移行タスクのスタートアップ設定
    - 後で手動で行う

※移行前評価にチェックを入れると、評価用のS3バケットやIAMロールの作成が必要になります。

以上のような設定でタスクを作成しようとしたところ、以下のようなエラーが出てタスク作成に失敗しました。
```システムエラーメッセージ:VALIDATION_REDSHIFT feature is not available```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/99aebad9-b6a6-ef46-4d1b-fadfdae34745.png)

どうやら「移行前評価の実行の有効化」でチェックをつけたことで動く「DMSのデータ検証機能」がRedshiftターゲットをサポートしていないようです。
https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Validating.html

> データ検証は、AWS DMS がソースとターゲットのエンドポイントでサポートしている次のデータベースで動作します。
> Oracle
> PostgreSQL 互換データベース（PostgreSQL または Aurora PostgreSQL、Aurora Serverless for PostgreSQL）
> MySQL 互換データベース (MySQL または MariaDB、Aurora MySQL、Aurora Serverless for MySQL)
> Microsoft SQL Server
> IBM Db2 LUW

Redshiftターゲットは何かとDMSの制約が多いですね。移行前評価が使えないのであればどうにもしようがないので、チェックをオフにします。
先程作成した移行評価用のS3バケットやIAMロールは、ターゲットRedshiftの場合はすなわち不要ということになります。
気を取り直して、**「移行前評価の実行の有効化：チェックなし」で再作成してみます。**

#### トライ2

- データ移行タスク
  - タスクの設定
    - (名前、ソース、ターゲット、等は割愛)
    - 移行タイプ
      - 既存のデータを移行して、継続的な変更をレプリケートする
  - タスク設定
    - (全てデフォルトのまま)
  - テーブルマッピング
    - **(全てデフォルトのまま)**
  - 移行前評価
    - **移行前評価の実行の有効化：チェックなし**
  - 移行タスクのスタートアップ設定
    - 後で手動で行う

今度は以下のようなエラーが出て作成に失敗しました。

```システムエラーメッセージ:Error in mapping rules. At least one selection rule is required.```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/7ee28668-f466-2344-302c-ae44224cafb9.png)

**テーブルマッピングの[selection rule]設定を少なくとも１つ入れておかないとだめなようです。**

#### トライ3

私にとっては「テーブルマッピングって何ぞ？」という状態なので、とりあえずワイルドカードの%をいれて、全てのテーブルを移行対象に設定してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/5c0535b0-5552-ded1-0e0e-3c47ecef1d44.png)

  - テーブルマッピング
    - ウィザード
    - 選択ルール
      - スキーマ名：%
      - テーブル名：%
      - アクション：含む
    - 変換ルール
      - (なし)

ウィザードで上記のような設定をすると、裏では以下のようなJSON設定が生成されます。

```
{
    "rules": [
        {
            "rule-type": "selection",
            "rule-id": "803082689",
            "rule-name": "803082689",
            "object-locator": {
                "schema-name": "%",
                "table-name": "%"
            },
            "rule-action": "include",
            "filters": []
        }
    ]
}
```
※[rule-id]や[rule-name]は環境によって異なるのでご注意ください。

上記のようにマッピングを設定したところ、データベース移行タスクの作成ができ、移行タスクの作成中に遷移しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9b92d7f9-018d-66ea-c98e-b45e3a15a4d4.png)

少し待つとステータスが「準備完了」になるので、「アクション」から「開始/再開」を選択して移行をスタートさせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/2f54d3a2-a1cb-df82-4020-cd6f3461d005.png)

で、ちょっと待っていると、うーん、失敗しましたね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/467b3bb2-a2a7-6f9c-210f-a25f71d42ac1.png)


タスクをクリックして詳細画面を見てみると、FATALエラーが6個以上でたので停止したようです。
```Last Error Task 'OZM2Z6R4N6WQUYGJGHP2POIOI5EO27SAFWHJFRY' was suspended due to 6 successive unexpected failures Stop Reason FATAL_ERROR Error Level FATAL```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/6d7f4a4d-823d-b5cb-93f5-70819288e60e.png)

CloudWatchログも出力するようにしていたのですが、ログをみてもさっぱり原因がつかめず。

ちなみにCloudWatchlogsでDMSログからエラー系の行のみに絞るには、以下のような文字列でフィルタすると良いと思います。
```"]E"```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/13a070d1-d062-699c-8ebf-2e82a07ca98d.png)
※このエラー画像は参考用です。

ログにはめぼしいものがなかったので、Redshiftのクエリエディタを見たところ、postsテーブルやauthorsテーブルはRedshiftにマイグレーションされていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ef3df9df-f7dd-33ae-027d-e859baff0d44.png)

- DBのスキーマ構造やCloudWatchLogsなどを眺めていて、ふと、MySQLのシステムスキーマのことを思い出しました。
  - 以下はソースDBの「db01」が今回の検証用スキーマであり、「awsdms_control」はDMSが作成したもの。**それ以外はシステム系スキーマです。これらを全て移行対象としてしまったようです。**

```sql
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| awsdms_control     |
| db01               |
| innodb             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
7 rows in set (0.01 sec)
```

- たとえばsysスキーマは、多くのストアドプロシージャが含まれているはずです。DMSではストアドプロシージャが移行できないのでエラーになりそうなものです。

#### トライ4

- ということで、Redshiftに移行されたデータを全てドロップしたうえで、移行タスクを編集してテーブルマッピングの「選択ルール」より、**スキーマ名を検証用DBの「db01」だけにフィルタリングしてみます。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/8565f7d9-088d-f531-74a3-79cf09ebb4af.png)

  - テーブルマッピング
    - ウィザード
    - 選択ルール
      - スキーマ名：%
      - テーブル名：db01
      - アクション：含む
    - 変換ルール
      - (なし)

上記の選択ルールで生成されるjsonは以下の通りです。

```
{
    "rules": [
        {
            "rule-type": "selection",
            "rule-id": "803082689",
            "rule-name": "803082689",
            "object-locator": {
                "schema-name": "db01",
                "table-name": "%"
            },
            "rule-action": "include",
            "filters": []
        }
    ]
}
```

マッピングルールを編集後、移行タスクを再開してみると、今度はマイグレーションが動きはしたものの、以下のように「エラーを伴って実行中」となっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/2ee0a025-c463-9f6c-ea9a-412031d65904.png)

CloudWatchLogsを見てみると、以下のようにクラスタにDMSが自動作成したIAMロールが付与されていない、というエラーと、それに続くS3バケットのcsvファイルが読み取れないというエラーが出ていました。
```dms-access-for-endpoint is not associated to cluster```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/d13ec4db-5ae6-5858-fe37-e565dfa0a0b3.png)

最初に記載したとおりDMSは自動生成したIAMロールを使って、一度S3に吐き出したCSVをRedshiftにコピーさせる仕様です。DMSがそのIAMロールを使うものだと思っていましたが、このエラー文字列の「クラスタ」という言葉はRedshiftを指しているように見えます（DMSのレプリケーションインスタンスは「クラスタ」とは呼ばない気がします）。

「dms-access-for-endpoint」というIAMロールの名称から考えると、DMSの「ターゲットエンドポイント」に付与すべきロールのように思えるのですが、マネジメントコンソール上「ターゲットエンドポイント」でIAMロールを指定できる箇所がありませんでした。

参考までに、IAMロール「dms-access-for-endpoint」にくっついているIAMポリシー「AmazonDMSRedshiftS3Role」は以下の通りです。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "s3:ListBucket",
                "s3:DeleteBucket",
                "s3:GetBucketLocation",
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:GetObjectVersion",
                "s3:GetBucketPolicy",
                "s3:PutBucketPolicy",
                "s3:GetBucketAcl",
                "s3:PutBucketVersioning",
                "s3:GetBucketVersioning",
                "s3:PutLifecycleConfiguration",
                "s3:GetLifecycleConfiguration",
                "s3:DeleteBucketPolicy"
            ],
            "Resource": "arn:aws:s3:::dms-*"
        }
    ]
}
```

このポリシーは単純に考えると、「dms-*」という名前のバケットにたいする読み書き権限です。
Redshiftがロードの際にS3バケットにアクセスできればいいだけなので、上記のIAMポリシー/IAMロールではなくカスタマイズしたものでもよいかと思いますが、**エラー文言に従って、RedshiftクラスタにIAMロール「dms-access-for-endpoint」を付与してみました。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/3b898608-567c-9f26-0d0d-e44e41c5e661.png)

Redshiftなので設定反映は数分かかります。

#### トライ5

上記の通りRedshiftにS3バケットへのアクセス用IAMロールを付与し、移行タスクを再開してみると、**正常に動いているようです！**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/5ab4676b-be20-1b96-d35e-a400322fdefc.png)

- 所要時間
  - 今回のようなデータ量(テーブル合計で11,000件)だと、1分程度で移行処理が完了しました。

- ログについて
  - CloudWatchlogsで以下のような文字列で検索すると、各テーブルのロード完了時間と件数が確認できます。
```Loading finished for table```
各行に出力されているTime takenは、DMS全体の所要時間ではないことをご注意ください。
```
2022-10-11T07:22:07 [TASK_MANAGER    ]I:  Loading finished for table 'db01'.'authors' (Id = 1) by subtask 1. 1000 records transferred. Time taken: 6669 micro seconds  (replicationtask.c:2447)
2022-10-11T07:22:25 [TASK_MANAGER    ]I:  Loading finished for table 'db01'.'posts' (Id = 2) by subtask 2. 10000 records transferred. Time taken: 112368 micro seconds  (replicationtask.c:2447)
```
  - 以下のページで、ログの参考情報がちょろっと記載されています。
    -  https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Monitoring.html#CHAP_Tasks.Status

継続的なレプリケーション状態になると、以下のログが10秒ごとで出力されるようです。
```Task is running```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/fe3f9308-82c2-6fd4-1539-67029bf81c23.png)



### データベース移行タスクのトライまとめ

- ターゲットをRedshiftとした場合のこれまでのトライをまとめてみます
  - [トライ1]：「移行前評価の実行の有効化」はRedshiftでは利用できないためチェックを外す
  - [トライ2]：テーブルマッピングの選択ルールを少なくとも１つ設定しておく必要がある
  - [トライ3]：テーブルマッピングの選択ルールで、必要なテーブルのみに絞る必要がある
  - [トライ4]：RedshiftにはDMS用のS3バケットへのアクセス用IAMロールを付与する必要がある
  - [トライ5]：移行成功！

# 移行データの中身の検証

移行手順は確立できたので、今度はデータの中身などを見て行こうと思います。

## データ型の変遷について

### 基礎知識

- DMSを利用したマイグレーションで意識すべきポイントとして、ソースとターゲットのDBエンジンが異なる場合、データ型がそれぞれのDBエンジンに適した形に変換されるという点です。
  - このとき中間にDMSを挟む形になります。**DMS自体にもデータ型が存在します。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/97c45377-718a-a958-8860-da32ff367518.png)


- 上記の番号にあわせた、各情報源を載せておきます。
  - (1) MySQL をソースとした場合の、DMSにおけるデータ型のマッピングは以下に記載されています。
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.MySQL.html#CHAP_Source.MySQL.DataTypes
  - (2) DMSのデータ型の詳細は以下に記載されています。
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Reference.DataTypes.html
  - (3) Redshiftをターゲットとした場合の、DMSにおけるデータ型のマッピングは以下に記載されています。
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.Redshift.html#CHAP_Target.Redshift.DataTypes

- 基本的にこれらを見ながら、ソースのデータを適切に移行できるか確認していくことが必要になるかと思います。(私はデータベースの型については浅学非才ですのでこれ以上深掘りしません(できません))
  - たとえばBLOBを例にとると以下のとおり、DMSまでは問題ないように見えますが、Redshiftでは収まらなくなります。

| MySQL | DMS | Redshift |
|:-----------:|:------------:|:------------:|
| BLOB(最大65535) | BYTES(65535)|VARCHAR (最大 LOB サイズ ※)| 

> ※ 最大 LOB サイズが 31 KB を超えることはできません。Amazon Redshift は 64 KB より大きい VARCHAR をサポートしていません。 

- 上記のように、とくにLOB(ラージバイナリオブジェクト)については異なるDBエンジン間では移行が困難なことが多いためか、「移行タスク」の作成時においてもLOBの扱いが設定できるようになっています。
  - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.LOBSupport.html
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/f7f6f674-ee29-b1b2-104d-51110829d3d1.png)

- それぞれ以下のようなオプションになっているようです。

> 「LOB 設定を含めない」を選択した場合、最大 LOB サイズが無視されます。
> 「完全 LOB モード」を選択した場合、AWS DMS は各 LOB を最大 LOB よりも小さなサイズに分割します。
> 「制限付き LOB モード」を選択した場合、AWS DMS は各 LOB が最大 LOB のサイズよりも大きくならないように切り捨てます。

### 今回移行したデータ型について

- 今回のテストデータを移行してみたところ、下表のような感じになりました。
  - **ソース側がシンプルなデータ型であるため、全体的に問題なくデータが移行できており、データ型も問題ないように見えます。**
  - 赤字のところは気になるポイントです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/ec7b85a0-3215-dd40-d3a1-32b29da8fa91.png)

- いくつかポイントを挙げてみます。
  - **MySQLのvarcharがRedshiftでは3倍のサイズのVARCHARになっている。**
    - UTF8のバイト数を考慮してのことでしょうか。
      - 厳密にいえば日本語のUTF-8は、特殊漢字に4バイトのUTF-8が割り当てられているのですが、対象文字はわずかな数なので、全体として3倍あればなんとかなる気がします。
  - **MySQLのAUTO_INCREMENTがRedshiftでは削除されている**
    - Redshiftは分析で用いるデータベースなので、インクリメントは不要なはずで、ある意味当然なような気もします。
    - 以下のページにソースがMySQLの条件として記載されています。
      - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Source.MySQL.html#CHAP_Source.MySQL.Limitations
      - 「列の AUTO_INCREMENT 属性は、ターゲットデータベース列に移行されません。」
  - **MySQLのUNIQUE KEYがRedshiftでは消えている**
    - Redshiftだと一意制約違反をしてもエラーにならないので、そういうことでしょうか。
  - **MySQLの照合順序(collation)がRedshiftでは消えている**
    - [COLLATE utf8_unicode_ci]
      - この「ci」はCaseInsensitiveの略で、「大文字小文字を区別しない」という意味です。
      - Redshiftでもテーブル作成時に同様の指定(COLLATE CASE_INSENSITIVE)ができるようですが、「DMSは一部の制約しか移行できない」という仕様どおりということでしょうか。

## タイムゾーンについて

今回のテストデータでは日付型・時刻型は以下のように変換されていました。

| MySQL | DMS | Redshift |
|:-----------:|:------------:|:------------:|
| timestamp | DATETIME|timestamp| 
| date | DATE|date| 

- DMSのDATETIME型は以下の仕様とのことです。
  - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Reference.DataTypes.html 
    - > タイムスタンプ値: 年、月、日、時間、分、秒、小数点以下の秒。小数点以下の秒のスケールは最大 9 桁です。サポートされている形式: YYYY: MM: DD HH: MM: SS.F (9)。
  - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Target.Redshift.html#CHAP_Target.Redshift.DataTypes
    - > スケールが 0 以上、6 以下の場合 (Redshift ターゲット列タイプによる)。タイムスタンプ (s) TIMESTAMPTZ (s) — ソースタイムスタンプにゾーンオフセットが含まれている場合 (SQL Server や Oracle など)、挿入/更新時に UTC に変換されます。オフセットが含まれていない場合、時間はすでに UTC で考慮されます。
    - > スケールが 7 以上、9 以下の場合：VARCHAR(37)

- MySQLではタイムゾーンの情報はカラムに持っているのではなく、システム変数で定義されるようです。つまりデータ自体はUTCで持っており、表示のときにシステム変数のタイムゾーンを参照する形のようです。
  - 今回のソースデータでは予めOSレベルでJSTに設定してありました。

```sql
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)
```

- 実際に先程のテーブルのデータをRedshiftに移行してみたところ、以下のようになっていました。
  - ソースのMySQL：JST表示(UTC+9)
  - ターゲットのRedshift：**UTC表示 (つまり元のJSTから9時間引いた時間)**

- 基本的にデータベースレイヤーでは、欧米の夏時間などの考慮をしなくてもいいように、UTCで保持するのがベストプラクティスなようですので、DMSとしても適切に扱ってくれている、と理解しました。

## ビューのマイグレーションと制約について

- ビューに関しては最初に書いた通り、以下の制約があります。
  - **移行タスクにおいて「フルロード（既存のデータを移行する）」に設定した場合のみ、ビューをテーブルとして移行できます。**

- ということで簡単なビューを作り、新しくフルロードの移行タスクを作成して検証してみようと思います。

### MySQL側
適当なビューを作っておきます。

```sql
create view id_title as select id, title from posts;
```

### DMS側
- フルロードのみの移行タスクをシンプルに作りました。
  - 既存の移行タスクを編集したかったのですが、移行タイプは変更できないようです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/6e3e8b22-df17-0e89-30a9-8b80c2b0afc6.png)

- しかし結果としてビューがRedshiftに移行できていませんでした。
  - DMSのログレベルを「すべて（詳細なデバッグ）」にしても原因がつかめず、ドキュメントを眺めていると、**「選択ルール」に「table-type」という項目があり、これでビューを含めるか定義できるようです。** 
    - https://docs.aws.amazon.com/ja_jp/dms/latest/userguide/CHAP_Tasks.CustomizingTasks.TableMapping.SelectionTransformation.Selections.html
    - > table-name がテーブルまたはビュー、テーブルとビューの両方を参照するかどうかを示します。デフォルトは table です。

ということで、以下のように```"table-type": "all"``` を指定してマッピングを作り直し、移行してみました。


```
{
  "rules": [
    {
      "rule-type": "selection",
      "rule-id": "223438029",
      "rule-name": "223438029",
      "object-locator": {
        "schema-name": "db01",
        "table-name": "%",
        "table-type": "all"★この行を追加
      },
      "rule-action": "include",
      "filters": []
    }
  ]
}
```

※なお、パラメータ "table-type" はマネジメントコンソール上のウィザードでは選択できないため、直接マネコン上のJSONを編集する必要があります。

### Redshift側

結果、フルロードのみ対応ではありますが、Redshift側にテーブルとしてビューの移行ができました。
簡単ですが、Redshift側のテーブル構造だけ記載します。

```
 dev2
   db01
     tables
       authors
       id_title (MySQL側でビューだったやつ）
       posts
       timetest
     views
       (none)
```

# まとめ

- 長くなってしまいましたが、MySQLからRedshiftへの、シンプルなテーブルの移行は問題なくできそうだ、という検証結果となりました。
- 今回はシンプルでかつ小さいデータを利用したため、本番移行では以下のようなポイントで様々な考慮が必要になると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538058/9a4f8792-5b6c-50ba-45db-af20c9763156.png)


以上です。