DynamoDB を適切な料金で利用したい

私が現在関わっているシステムの DynamoDB では現在、予想される最大負荷に十分に耐えられるキャパシティを設定しておけばいいだろうという設定が横行しています。
もちろん、システムのアクティブユーザー数や毎日の負荷状況などがわかっていればそれでよいのでしょうが、このシステムは Twitter キャンペーンと合わせて利用されることが多く、負荷状況はキャンペーンの内容などによって結構まちまちなので少し読みにくいです。

また、毎日〇〇時に高負荷状態になるといった感じではなく、稼働直後に最大負荷を迎えて以降はそれに比べるとかなり低い負荷で安定稼働という状況がよくあります。安定稼働を始めたらキャパシティを下げるなどの対応はしていますが、どうにも札束で殴っているような状況です。

というわけで、一度 DynamoDB の料金体系を理解して安定性は損なわず、よりお得に利用できる方法を探ってみたいところ。

料金に関しては基本的に **バージニア北部 region** のものを記載します。

# 料金体系
とりあえずAWS公式の提供している料金体系に関する情報はこちらです。
https://aws.amazon.com/jp/dynamodb/pricing/

主に以下の項目が利用料金に影響してきます。

- キャパシティユニット
- データストレージ(保存容量)
- データ転送
- その他オプション

## キャパシティユニット
キャパシティーモードがプロビジョンドの場合 DynamoDB に設定されているテーブルのキャパシティー(読み込み・書き込み)の数値で利用実績に関わらず料金がかかります。

オンデマンドの場合は異なる料金計算が行われるので合わせて記載します。

今まで勘違いをしていたのですが、この数値は1秒間に行える読み込み・書き込み数ではなく、正確には1秒間に消費できる読み込み・書き込みキャパシティユニット数を指します。

どういった操作でどれだけのユニットが消費されるかは後述

### プロビジョンド
https://aws.amazon.com/jp/dynamodb/pricing/provisioned/

プロビジョンドの場合1秒間に必要と予想される読み込み・書き込みできるキャパシティユニットを事前に指定しておくことになります。

オートスケール機能もありますが即応性は高くないので急激な負荷上昇によるピークの立ち上がりに対してはスケールが行われる前に一時的にスロットリングが実施されてしまうことがあります。
この場合はオートスケールは次善策と捉え、正確な負荷予測か札束を用意して大げさなキャパシティーを設定することになるでしょう。


| プロビジョニングするスループットタイプ | 時間あたりの料金 |
|:-:|:-:|
| 書き込みキャパシティーユニット (WCU)  | 0.00065USD/WCU  |
| 読み込みキャパシティーユニット (RCU)  | 0.00013USD/RCU  |

1時間当たりの1ユニットの料金が以上の通り。
これらの料金は設定されている値で料金が発生するため、実際に消費されているキャパシティユニットは問題ではない。
要は過剰スペックのDynamoDBは料金の面でもったいない。

### オンデマンド
https://aws.amazon.com/jp/dynamodb/pricing/on-demand/

こちらはプロビジョンドと違いキャパシティユニットの設定は必要ない。
実際に消費されたユニット数に応じた料金が発生する従量課金制。

| 料金タイプ | 料金 |
|:-:|:-:|
| 書き込み要求単位	書き込み要求ユニット 100 万あたり   |  1.25USD |
| 読み出し要求単位	読み出し要求ユニット 100 万あたり  |  0.25USD |

使った分しか料金がかかりませんが
反面、使えば使うだけ料金がかかるのでコストが青天井になってしまうかもしれません。

とはいえ、

> テーブルのスループット容量を管理することなく、必要に応じて API コールを実行できます。DynamoDB では、ワークロードで一貫性と低レイテンシーを実現できるよう、ハードウェアリソースが自動的に管理されます。	

とあるので、ある程度の負荷の変化にも対応できるようですが、スロットルが一切発生しないわけでもない。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/on-demand-table-throttling-dynamodb/

また、スループットのデフォルトクォータ という項目も存在します。

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

 
### ユニットの消費について
プロビジョン・オンデマンドとも同じ計算で消費されているようで以下の通り

- 書き込み(1KBまで)

| 処理 | 消費ユニット |
|:-:|:-:|
| 書き込み | 1 |
| トランザクション書き込み | 2 |

- 読み込み(4KBまで)

| 処理 | 消費ユニット |
|:-:|:-:|
| 強力な整合性のある読み込み | 1 |
| トランザクション読み込み | 2 |
| 結果整合性のある読み込み | 0.5 |

アクションによって消費されるユニット量が違います。

それぞれのアクションの利用方法はここでは置いておいて、デフォルトでは

- 書き込み
- 結果整合性のある読み込み

がそれぞれ利用されるようです。

## データストレージ(保存容量)

キャパシティユニットに比べると他の項目は非常にシンプルな内容です。
データ保存容量に関しては以下の通り


- 毎月最初の 25 GB の保管は無料*'
- それ以降、毎月 GB あたり 0.25USD

ちなみに S3 の場合は

- 最初の 50 TB/月	0.023USD/GB
- 次の 450 TB/月	0.022USD/GB
- 500 TB/月以上	0.021USD/GB

RDS(PostgreSQL)の場合でも

- 汎用 (SSD) ストレージ	毎月 0.115USD/GB


となっているので大容量のデータを保存することには向いていません。
データ実体はS3など他のストレージに持ち、DynamoDBにはそのパスを保存するなどの設計を検討すると料金を抑えられるかもしれません。

## データ転送

他のAWSのサービス同様アウトバウンドのデータに料金が発生します。
また、同一 region 内のAWSサービス間でのデータ転送に料金は発生しません。

データ転送量はキャパシティユニットにも影響するので、get_item や scan などを行う際には `attributes_to_get` や `projection-expression` などのオプションを設定して取得する項目を削ることが推奨されます。

**データ受信**

- すべてのデータ受信	0.00USD/GB

**データ送信**

- 1 GB/月まで	0.00USD/GB
- 次の 9.999 TB/月	0.09USD/GB
- 次の 40 TB/月	0.085USD/GB
- 次の 100 TB/月	0.07USD/GB
- 150 TB/月以上	0.05USD/GB

# 料金比較
というわけで、データ量の削減などコスト削減のポイントはいくつかありそうですが、やはり一番気になるのはプロビジョンドとオンデマンド
どちらのキャパシティモードを利用した方が自前のサービスはお得なのかというところ。

- [DynamoDBのオンデマンドとプロビジョニングの料金を比較をしてみた #reinvent](https://dev.classmethod.jp/articles/reinvent2018-compare-dynamodb-on-demand-price-with-provisioned-price/)

上記の記事で 2018-12-07 時点における東京 region での料金比較が行われています。
そちらによると 14.4% がプロビジョンとオンデマンドの料金が一致するラインの様です。

オンデマンドはプロビジョンに比べてかなりユニット当たりの金額が割高なので可能ならばプロビジョンを適切なラインまで引き下げるのが当然良いです。

今回は、ちょっとしたアクセス集中に対応したのでこれ以上プロビジョンの値を減らしたくは無いが、基本的には設定値に全然届いていないという場合にオンデマンドに切り替えてしまった方が経済的かどうかを確認したいと思います。

```ruby:plan.rb
# 定数
READ_PROVISIOND_COST_PER_HOUR = 0.00013
WRITE_PROVISIOND_COST_PER_HOUR = 0.00065
READ_ON_DEMAND_COST_PER_MILLION = 1.25
WRITE_ON_DEMAND_COST_PER_MILLION = 0.25
# 変数
now_read_provisioned_unit = 100
now_write_provisioned_unit = 100
used_read_provisioned_unit = 7.08
used_write_provisioned_unit = 14.63

# 現在のプロビジョンドコスト(USD/h)
# (USD/unit)/h * 設定中のunit
now_provisioned_read_cost = (READ_PROVISIOND_COST_PER_HOUR * now_read_provisioned_unit)
now_provisioned_write_cost = (WRITE_PROVISIOND_COST_PER_HOUR * now_write_provisioned_unit)
now_provisioned_cost = now_provisioned_read_cost + now_provisioned_write_cost
p sprintf("%f", now_provisioned_cost)

# 現在のオンデマンドコスト(USD/h)
# USD/unit * 秒間消費unit * 1時間
now_on_demand_read_cost = (READ_ON_DEMAND_COST_PER_MILLION/10**6 * used_read_provisioned_unit)*60*60
now_on_demand_write_cost = (WRITE_ON_DEMAND_COST_PER_MILLION/10**6 * used_write_provisioned_unit)*60*60
now_on_demand_cost = now_on_demand_read_cost + now_on_demand_write_cost
p sprintf("%f", now_on_demand_cost)

plan = now_provisioned_cost < now_on_demand_cost ? 'プロビジョンド' : 'オンデマンド'

p "#{plan}の方が得"
```

使用中の region での料金と現在の DynamoDB モニタリング情報を入れると、どちらの方がコストが低いか報告します。
とりあえずバージニア北部の金額を設定。
読み込み・書き込みを異なる料金プランに設定することはできないので、両方の状況を勘案してお得な方を計算するようにしてみました。

このテーブルの場合、あくまでコストのことだけを考えるならオンデマンドの方が得らしいです。

オンデマンドの可用性も検討して、プランの変更を検討したいなというのが個人の感想。

# 参考資料
[Amazon DynamoDB 料金](https://aws.amazon.com/jp/dynamodb/pricing/)
[DynamoDBの料金を最適化しよう【初級編】](https://dev.classmethod.jp/articles/optimize-costs-of-dynamodb/)
[DynamoDBのオンデマンドとプロビジョニングの料金を比較をしてみた #reinvent](https://dev.classmethod.jp/articles/reinvent2018-compare-dynamodb-on-demand-price-with-provisioned-price/)
[オンデマンド DynamoDB テーブルがスロットルされるのはなぜですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/on-demand-table-throttling-dynamodb/)
[Amazon DynamoDB のサービス、アカウント、およびテーブルのクォータ](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html)