Amazon DynamoDBにデータをインポートするときに知っておきたいこと

オンプレミスで稼働してるSQL ServerのとあるテーブルをAmazon DynamoDB (以下、DynamoDBと称す)に移行したときに得た知見です。
今回、DynamoDBの説明は省略します。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html


## どんな方が読むと良いのか
* これからDynamoDBにデータをインポートしようとしている
* データ量がちょっと多め（1000万件以上）
* ”試してみた”テックブログを読んでみたけど参考にならなかった
* DynamoDBの概要は抑えているけど運用経験はない

などなど。データのインポートがメインになりますので、同じようなことをやろうとしている方の参考になれば幸いです。


## 概要
SQL ServerのテーブルのデータをCSVファイルとして出力し、DynamoDBにインポートしました。
AWSのブログでLambdaを使った方法が公開されていますので、まずはこちらを読んでいただいてこれでいいやってなった人はブラウザバックしてください。こちらを試しているテックブログも多いので簡単にできると思います。

https://aws.amazon.com/jp/blogs/news/implementing-bulk-csv-ingestion-to-amazon-dynamodb/

### Lambdaのタイムアウトが気になる
>100,000 行のファイルでの実行には、約 80 秒かかります。この Lambda 関数のタイムアウトは約 100 万行のデータに対応できますが、CSV ファイルはより小規模なチャンクに分割する必要があります。

CSVファイルは1400万行以上ありました。Lambdaにはタイムアウトが15分という制約がありますので、もちろん分散してやる必要があって、計算上では15並列です。

今回は ~~CSVを15分割するのはめんどうなので~~ ハイスペックな複数台のEC2を用意して並列でスクリプトを実行することにしました。

## 要件
* 1400万行以上のCSVファイルをDynamoDBにインポートする
* 移行時間にシビアではないけど1時間以内には終わらせたい
* CSVを300万行ずつに分割して5並列で実行する

## 課題
### キャパシティ不足
インポート中に度々500エラーが発生してしまいました。

原因はオンデマンドモードの初期の書き込みキャパシティに到達してしまい、スループットの拡張が走っていたようでした。拡張が走っている間は一時的なスロットリングが発生し、500エラーを返します。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html

解決策としては**リトライを増やすこと**です。
Boto3でスクリプトを実装したのですが、デフォルトのリトライは3回でしたのでそれを10回に増やしました。

``` python
config = Config (
   retries = {
      'mode': 'standard',
      'max_attempts': 10
 
   }
)
```

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html


他の手段としては、**プロビジョニングモードにしてあらかじめキャパシティを上げておくこと**もあります。
リトライ回数を増やしてもスループットの拡張は回避できないので、500エラーは発生しますし、リトライする分処理時間も長引きます。

DynamoDBでは24時間に一度だけ、オンデマンドとプロビジョニングのモードを切り替えることができます。インポート時にはプロビジョニグモードでキャパシティを上げておいた状態にしておき、後からオンデマンドモードに戻すようにしました。

:::note info
書き込みキャパシティの目安
* 300万行のCSVファイルを [batch_writer()](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/dynamodb.html#batch-writing) でPutItemした場合、必要な書き込みキャパシティは約1700ほど
* 5並列では計算上 1700×5で 8500（実際にはそこまでいかなかった）
* オンデマンドモードのデフォルトの書き込みキャパシティが2000なので2並列以上で拡張が走ってしまう

<img width="300" alt="image1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222808/b42fe630-56dd-bff6-c258-87dc82920396.png">

<img width="300" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222808/b50c0006-19bf-196b-2b70-e52f0ba6bf43.png">


:::

### キャパシティ不足 Part2
インポート後にDynamoDBのItem件数を見て、CSVファイルと差分がないかを確認します。
AWSコンソール上では6時間ごとに件数が更新されるため、インポート後はしばらく件数が表示されません（詳しく調べていないですが6時間後になるのか...？）。

AWS CLIでItem数を調べることができます。
```
$ aws dynamodb scan --table-name <table-name> --select COUNT --return-consumed-capacity TOTAL
```


ここで気をつけたいのが、読み込みキャパシティを使うということです。

:::note info
読み込みキャパシティの目安
* 500万件で約1170が必要

<img width="300" alt="image2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222808/4c5a2df7-30fb-dcfb-a019-33abfaba06c3.png">

:::

## インポートにかかった時間
1400万行以上のCSVファイルを5分割した場合、以下の通りでした。
AWSのブログには10万件で80秒とあり、100万件だと13分ちょっとなのでそれよりは気持ち早い結果になっています。

| CSVファイル | 件数 | インポート時間 |
|:---------|----:|:------------:|
| 1 | 287万件 | 35分              |
| 2 | 287万件 | 29分              |
| 3 | 287万件 | 32分              |
| 4 | 288万件 | 34分              |
| 5 | 280万件 | 32分              |

## 整合性の確認
AWS CLIでDynamoDBのItem数は確認しましたが、CSVファイル（および移行元のテーブル）の中身と一致しているかはスクリプトで確認しました。もちろんインポートのスクリプトでエラーハンドリングやログの出力は行っていますが、インポート時点でデータに不整合はなかったことを明確にするためです。

あまり得策ではないのかもしれませんが、CSVファイルの1行1行をDynamoDBのItemと付き合わせていきました。
こちらもCSVファイルを5分割して並列で実施しました（4時間弱かかりました...）。

### PITR
インポート後は、定期バッチによってオンプレミスのSQL Serverと同期してデータが更新されていきます。移行元のテーブルからCSVファイルを出力する処理が始まった時点から、時間が経つほどバッチの処理時間も伸びていきます（オンプレミスと差分が増えて更新件数も増えるため）。
今回はあまりシビアに時間を決めていませんでしたが、インポートにかかる時間に加えて整合性の確認をし、4, 5時間かかってしまうのは許容範囲外でした。

そこで本来はバックアップの用途であるPITRを活用しました。
具体的にはインポート時点のデータを別テーブルにリストアし、リストア先のテーブルで整合性の確認を行うというものです。リストア元は定期バッチで更新されていきますが、リストア先は更新されないのでインポート時点のデータを保持できます。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery_Howitworks.html

## 最後に
### プロビジョニングモードの罠
罠というか、怖いお話です。

インポート時は一時的にプロビジョニングモードでキャパシティを書き込み9000、読み込みを4000まで引き上げ、インポート後にオンデマンドモードに戻すとお話しました。
上の通りにキャパシティを上げているDynamoDBにかかる費用はとてもとてもお高いです。

検証環境で使用したプロビジョニングモードのテーブルをそのまま放置していたり、またPITRでリストアしたテーブルもそのままだったり、さらには何度も検証を繰り返してその分のテーブルが放置されてしまっていたら。。

本記事のタイトルが「DynamoDBで xxx万円溶かしてみた」にならなくて本当に良かったです。
実際にどれくらいの費用がかかってしまうのか気になる方は調べてみてください。

検証後のお片付けは大事。