DynamoDBを使いこなして精神的安定を手に入れた

今私が開発している新サービスをDynamoDBを使用して開発/運用しているので、使ってみてのメリット/デメリットをSQLと比較しながら説明できればと思います。
知見が新たに溜まったらどんどん追記していく予定です。
早速、SQL(MySQL)と比較してのDynamoDBメリット/デメリットを書いていきたいと思います。
その前に、DynamoDBの用語にまだなれていない方は先にこの記事の最後の方にある[用語マッピング](https://qiita.com/walkers/items/9b39d752cc5b8df7b4b5#用語マッピング)を見てから読んでいただいたほうがすんなり理解できると思います。

また、既存のgemは検索系が弱かったので [dynamodb-api](https://github.com/walkersumida/dynamodb-api) gem を自作しました。是非、使ってみてください。
※スターいただけるとモチベーション上がるので、よろしくお願いいたします笑

# メリット
## 容量無制限という精神的安定が手に入る

私が今回DynamoDBを選定した背景の大きな理由の一つ、[容量に制限がない](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html#limits-tables)ところです。NoSQLは大量データを扱うのに向いているという理由の一つがこれですね。

> テーブルのサイズには実用的な制限はありません。テーブルは項目数やバイト数について制限がありません。

逆にSQLの容量はというとAWSのRDSでいうと、[最大16TB](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_Storage.html)です。Amazon Aurora MySQLは、[64TB](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Aurora.AuroraMySQL.html)ですがMySQLのバージョンなどで色々別途制約などがあるので、調べてから選定していくことをおすすめします。

## オートスケーリングが簡単

DynamoDBは、AWSコンソール画面からボタンをポチポチするだけで、オートスケーリングができるので、やばくなったら札束で簡単に解決できます。再起動とかないので気軽にできてしまう。詳しい説明は省きますが、以下のように使用率が何％を超えたらいくつに増やすかを決めて保存するだけです。
<img width="823" alt="スクリーンショット_2018-09-12_0_00_54.png" src="https://qiita-image-store.s3.amazonaws.com/0/104056/56d733bb-2f6c-0847-2ebb-af6f5ef843a3.png">


### リクエストごとの支払い(on-demand)に対応 [2018-12-07追記]

今までオートスケーリングの事を考えたり、急に負荷が上がったときに、スケーリング間に合わなかったりしてエラーを吐いたりした経験がありますが、それらから開放することができます！on-demandにするだけです！
on-demand modeへの変更は1日1回可能です。on-demandからprovisioned modeへの変更も可能です。
費用感は、日々データは増えかつリクエスト数もユーザ数が増えることで多くなるので正確な倍率を出すのは難しいのですが、私の場合は数ドル増えただけです。もちろん使い方にもよるので、変更直後は利用料金を監視することをおすすめします。でも使う価値はめちゃくちゃあると思います。
https://aws.amazon.com/jp/blogs/news/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing/

## サーバーレス

DynamoDB には、インストール、メンテナンス、または運用するソフトウェアが何もない。つまり、MySQLみたいにversion 5.4のサポートが終了したのでアップデートしてくださいなどが無いんですよね。楽。


## 属性(カラム)追加が容易

マイグレーションとかないので、ダウンタイムなしで追加/削除できちゃいます。

## 属性にいろいろな型が使える

数値、文字列、バイナリ、Bool、Null、JSON、配列が使えます。詳しくは[こちら](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)を。

# デメリット
## 検索に癖がある
- パーティションキーにはイコールでしか絞り込めない
- その他の属性で検索をかけたい場合は、取得したデータにフィルターをかけて取得する必要がある
    - 検索をかけた結果を5件取得した場合、その中からフィルターをかけて絞り込むので結果は、5件以下になる

## ページングが難しい & 真面目にやってはいけない
上記のように検索に癖があるので、その影響でページングもSQLのように綺麗に実現しようとすると結構ハードルが高い。ただ、NoSQLでは真面目にページングしてはいけないという結論に至りました。
その背景にあるのは、Gmailです。Gmailはサービス(CLOUD BIGTABLE)は違うもののNoSQLを使用しています。
メール検索ボックスに何も入れないで検索した結果の数は正確です。これはDynamoDBで言うフィルターを使っていないからです。
<img width="588" alt="スクリーンショット 2018-09-17 14.31.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/104056/63ec5cbc-18a0-1a06-f32f-46cdf0ab719c.png">

しかし、検索ボックスに文字を入れて検索をした場合のページングの結果はどうでしょうか。
1ページ目は以下の通り。__約__がついてますね。これは真面目に行っていない証拠笑
92行と表示されているので、次のページに移動してみます。
<img width="584" alt="スクリーンショット 2018-09-17 14.32.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/104056/05c0ff14-17e6-92b9-d4e8-dd5e52fa744e.png">

2ページ目は以下の通り。あらまびっくり、母数が増えましたね笑
<img width="584" alt="スクリーンショット 2018-09-17 14.32.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/104056/1442a823-70b7-a4ce-adc3-bf62186bc224.png">

そして3ページ目は以下の通り。最終的に__多数__になりますw
<img width="584" alt="スクリーンショット 2018-09-17 14.32.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/104056/37b64a11-059d-b8d5-5883-ec5cc5ea1d09.png">

Googleですらこれなので、NoSQLでページングを真面目に行うのはやめたほうがよさそうです。


## インデックスの貼り方かなり重要
グローバル/ローカル セカンダリインデックスというのが有ります。昔は追加できるインデックスに上限があったので貼り方には気をつける必要がありました。ただ、上限が緩和されたからといい、むやみに追加するとコストやパフォーマンスに影響に影響を与えることには間違いないので、引き続き貼る前にしっかりと設計する必要があります。
※緩和されたのはグローバルインデックスのみで、ローカルインデックスは引き続き最大5個までが上限です

また、ソートしたい場合インデックスを貼りソートキーに対してしかソートを掛けられないので注意です。こちらもちゃんと設計してから貼りましょう。

## ActiveRecord使えないので辛い
Railsを使用している場合の話ですが、ActiveRecord使えないのが辛いです笑
[Dynamoid gem](https://github.com/Dynamoid/dynamoid)というのを使っているのですが、完成度はいまいちで結構バグが潜んでいるっぽいです。また、検索系が弱いです。
なので、自分で[dynamodb-api](https://github.com/walkersumida/dynamodb-api)というgemを作りました。まだ完成度は低いですが検索系を充実させて作っています。

# まとめ
私の場合は、色々なデメリットもありますが、それよりもメリットのほうが大きかったので、DynamoDBを選択しましたが、そのアプリケーション内では、MySQLも使用しています。SQLとNoSQLにはそれぞれメリット、デメリットがあるのでうまく使い分けて共存させていくのがベストだと感じました。

# その他お役立ち情報

## ベスト・プラクティス
関連データはなるべく同じテーブルで管理するのが好ましい。RDBMSは、正規化などの概念がありテーブルを分け、必要に応じてjoinするという考え方ですが、DynamoDBは逆で__関連するデータは1つのテーブルにまとめる__ことがベスト・プラクティスとされています。関連するデータを近くに置くことでコストとパフォーマンスを改善するためです。またこのあとにも説明しますが、RDBMSにはない配列などを1カラムで管理できるので、RDBMSではテーブルを分けるような1対nのデータも比較的簡単に扱うことができます。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-general-nosql-design.html

## DynamoDBのクライアントアプリがプレビュー版でリリース[2019-09-17追記]
AWSからDynamoDBのクライアントアプリがリリースされました！地味に便利です。

以下からダウンロードできます。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.settingup.html

<img width="1402" alt="スクリーンショット_2019-09-17_22_26_43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104056/3f2b000d-4b31-e3a1-9698-e9ff3a78c961.png">


## 制限
### 項目のサイズ
DynamoDB の最大項目のサイズ 400 KB で、属性名バイナリの長さ (UTF-8 長) と属性値の長さ (これもバイナリ長) を含みます。属性名はサイズ制限に反映されます。

たとえば、2 つの属性を持つ項目があり、1 つの属性は名前が "shirt-color" で値が "R"、別の属性は名前が "shirt-size" で値が "M" であるとします。この項目の合計サイズは 23 バイトです。

項目のサイズ上限は__約136,533文字__です。
※1文字 = 3バイト

### セカンダリインデックス
#### 登録上限数
~~グローバルセカンダリインデックス/ローカルセカンダリインデックス毎に5個ずつ。~~
ローカルセカンダリインデックスは5個の上限数があります。

##### グローバルセカンダリインデックスの事実上登録無制限に [2019-06-21 追記]

グローバルセカンダリインデックスの登録上限数が20個に増加しました。また、増加リクエストを申請することで、更に増やすことができます！
しかし、設計を安易に行い無駄に登録して数を増やしていくのは、よくないので今までと変わらず設計を十分に行ってから登録してください。
[Amazon DynamoDB でテーブルに作成できるグローバルセカンダリインデックスと射影インデックス属性の数が増強される](https://aws.amazon.com/jp/about-aws/whats-new/2018/12/amazon-dynamodb-increases-the-number-of-global-secondary-indexes-and-projected-index-attributes-you-can-create-per-table/)

#### 追加/削除

グローバルセカンダリインデックスはテーブル作成後に追加/削除が可能。ローカルセカンダリインデックスはテーブル作成後は、追加/削除を行うことはできません。

ちなみに、あとからグローバルセカンダリインデックスを追加する場合、データ量によって結構時間かかるので注意してください。以下のデータ量でも、追加するのに3時間半ぐらいかかった。DynamoDBのGUIから登録を行う場合想定時間が出るので追加する前に確認してみてください。

| ストレージ容量（バイト単位） | 項目数 |
|:--|:--|
| 64.25 MB | 1,579 |


### ローカルセカンダリインデックス 項目コレクションサイズ制限
同じパーティションキーを持つすべての項目が10 GB を超えることができません。
なので特に理由がない場合は、グローバルセカンダリインデックスを利用しましょう。以下の公式サイトにもそう記載されています。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-indexes-general.html#bp-indexes-general-efficiency

## 属性名のサイズ

属性名は 1 文字以上、64 KB 以下。

## 用語マッピング
| 一般的なSQL | DynamoDB |
|:-----------------|:------------------|
| テーブル | テーブル |
| プライマリーキー | 呼び名はプライマリキーと同じだが構成が以下の2通り存在する<br><br>パーティションキー(ハッシュキー)<br>または<br>パーティションキーとソートキー(レンジキー)の複合キー |
| 行 | 項目 |
| 列 | 属性　|

※カッコで囲んでいるキー名は昔の呼び名です。どっかのタイミングで変えたみたいです。

## 一括更新は存在しない
1件1件、UpdateItemする必要があります。

## DynamoDBの予約語
filterを使うときに予約後とカラム名が被ると追加で処理を入れなければならないので、避けられるなら避けたほうがいいです。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html

[dynamodb-api](https://github.com/walkersumida/dynamodb-api) gem は予約語を使用しても検索時に面倒なことが増えないようになっています！
※しつこいですが、スターいただけるとモチベーション上がるので、よろしくお願いいたします笑


## トランザクション機能がネイティブサポート [2018-12-07追記]

トランザクション機能がネイティブサポート！心強い。これは使用用途増えますね。
https://aws.amazon.com/jp/blogs/news/new-amazon-dynamodb-transactions/