DynamoDB料金理解のはじめの一歩

## 概要

DynamoDBはAWSで提供されているマネージドなNoSQLデータベースサービスですが、DynamoDBの料金についてどれぐらい正確に把握していますか。

私はなんとなくキャパシティが料金の大部分を占めていることやオンデマンドモードが登場したことなどは把握していましたが詳しくは理解していないという状態でした。

そこで今回はDynamoDBの料金について勉強した内容をまとめます。

## DynamoDBの課金の仕組み

AWSの公式ドキュメントを見るとDynamoDBでは**テーブル内のデータの保存、読み取り、書き込み**に課金対象になるとの記載があります[^1]。

[^1]: これ以外にも有効化したオプション機能も課金の対象です。

### データの保存

データの保存に対する課金はテーブルのクラスによって異なります。

|テーブルクラス|料金(東京リージョン)|
|:-:|:-:|
|DynamoDB Standard|1GBあたり0.285USDUSD/月|
|DynamoDB Standard-IA[^2]|1GBあたり0.114USD/月|

通常はStandardクラスを利用することが多いかと思いますが、アクセス頻度の低いデータを大量に保存するといったユースケースではStandard-IAクラスを使用すると**Standardクラスの40%の料金**に抑えることができます。

> DynamoDB Standard-IA テーブルクラスは、アプリケーションログ、古いソーシャルメディアの投稿、e コマースの注文履歴、過去のゲームの実績など、アクセス頻度の低いデータを長期間保存する必要があるユースケースに最適です。

(引用元 : [Amazon DynamoDB が、DynamoDB コストを最大 60% 削減するのに役立つ新しい Amazon DynamoDB Standard-Infrequent Access テーブルクラスを発表](https://aws.amazon.com/jp/about-aws/whats-new/2021/12/amazon-dynamodb-standard-infrequent-access-table-class/))

[^2]: 正式にはAmazon DynamoDB Standard-Infrequent Access

また、Standardクラスには無料枠25GB/月があります。

### データの読み取り／書き込み

DynamoDBに保存されているデータの読み取りやデータ書き込みを行う場合も課金の対象です。現時点では大きく2つのキャパシティーモードが用意されていて、それぞれ料金計算が異なります。

|キャパシティーモード|特徴|
|:-:|:-:|
|オンデマンドキャパシティーモード|**実行したデータの読み取り/書き込みリクエストに対して課金**。読み取りと書き込みのスループット予測値を指定する必要がない。|
|プロビジョニング済みキャパシティーモード|**1秒あたりの読み込みと書き込みの回数**を指定。Auto Scaling可能。|

**読み取り/書き込みリクエスト**、**読み込みと書き込みの回数**とあるようにリクエストや回数といったAWSで規定されている**単位**が存在するのですが、そこを理解せず漠然とコンソール上でモニタリングしていたので感覚がつかめませんでした。まずはこのあたりを正確に理解する必要があるようです。

#### 読み込みリクエスト単位

テーブルからデータを読み込む際はテーブルに対してAPIコールを行う必要があります[^3]。このとき、読み込みリクエストという単位で課金が発生します。ややこしいポイントはこのリクエストには3種類（ここでは2種類のみ紹介）あることです。

|読み込みリクエスト|特徴|必要な単位数|
|:-:|:-:|:-:|
|強力な整合性のある読み込み|書き込み更新が反映された最新データの応答を返す。|1単位(4KBまで)|
|結果整合性のある読み込み|最新の書き込み結果が反映されていないことがある。|0.5単位(4KBまで)|

通常は`結果整合性のある読み込み`を使用しています。強力な整合性のある読み込みを使用するには読み込みオペレーション(`GetItem/Query/Scan`) にあるConsistentReadパラメータをtrueに設定する必要があります。

上の表を見ればわかる通り`強力な整合性のある読み込み`は`結果整合性のある読み込み`よりも多くのスループット容量が使用されることがわかります[^4]。例えば8KBの項目の場合はそれぞれ次のリクエスト単位が必要になります。

|読み込みリクエスト|必要な単位数|
|:-:|:-:|
|強力な整合性のある読み込み|2単位|
|結果整合性のある読み込み|1単位|

[^3]: テーブルに限らずAWSのすべての操作はAPIコールをすることで実現しています。例えばIAMユーザに一覧をコンソール上から見るといったものもAPIコールしています。

[^4]: その他にもネットワークの遅延または停止があった場合には利用できなくなり500エラーを返す場合があったり、レイテンシーが高くなるといったデメリットに加えてグローバルセカンダリインデックス (GSI)ではサポートされていないなどデメリットがあります。

#### 読み取り容量ユニット (RCU)

読み取り容量ユニット(RCU)1単位で1秒あたり強力な整合性のある読み取りリクエストを1回実行できるものです。結果整合性のある読み込みの場合は1秒あたり2回実行できます。例えば8KBの項目の場合はそれぞれ次のユニットが必要になります。

|読み取り容量ユニット (RCU)|必要なユニット|
|:-:|:-:|
|強力な整合性のある読み込み|2RCU|
|結果整合性のある読み込み|1RCU|


#### 書き込みリクエスト単位

テーブルからデータを書き込む際はテーブルに対してAPIコールを行う必要があります。このとき、書き込みリクエストという単位で課金が発生します。このリクエストには2種類(ここでは1種類のみ紹介)あります。


|書き込みリクエスト|必要な単位数|
|:-:|:-:|
|標準書き込み|1単位(1KBまで)|


例えば3KBの項目の書き込みリクエストには次のリクエスト単位が必要になります。

|書き込みリクエスト|必要な単位数|
|:-:|:-:|
|標準書き込み|3単位|

#### 書き込み容量ユニット (WCU)

書き込み容量ユニット(WCU)1単位で1KBまでであれば1秒あたり標準の書き込みリクエストを1回実行できるものです。例えば3KBの項目の場合はWCU3個必要になります。


#### その他のリクエスト単位

上記で挙げたリクエスト単位以外に次の単位があります。

* レプリケート書き込みリクエスト単位
* ストリーム読み込みリクエスト単位
* トランザクション読み込み/書き込みリクエスト


### オンデマンドキャパシティーモード

オンデマンドキャパシティーモードでは実際にリクエストした分だけ料金が発生します。

DynamoDB Standard テーブルクラスの料金
|プロビジョン済みスループットタイプ|時間あたりの料金(東京リージョン)|
|:-:|:-:|
|書き込み要求単位 (WRU)|書き込み要求ユニット 100 万あたり 1.4269USD|
|読み出し要求単位 (RRU)|読み出し要求ユニット 100 万あたり 0.285USD|

DynamoDB Standard-IA テーブルクラスの料金
|プロビジョン済みスループットタイプ|時間あたりの料金(東京リージョン)|
|:-:|:-:|
|書き込み要求単位 (WRU)|書き込み要求単位 100 万あたり 1.7836USD|
|読み出し要求単位 (RRU)|読み出し要求単位 100 万あたり 0.356USD|


### プロビジョニング済みキャパシティーモード

プロビジョニング済みキャパシティーモードではテーブル作成時にユニット数を指定します。そのユニット数に従って下記の料金が適用されます。そのため実際にリクエストが発生してなくても料金が発生します。

DynamoDB Standard テーブルクラスの料金
|プロビジョン済みスループットタイプ|時間あたりの料金(東京リージョン)|
|:-:|:-:|
|書き込みキャパシティーユニット (WCU)|0.000742USD/WCU|
|読み込みキャパシティーユニット (RCU)|0.0001484USD/RCU|

DynamoDB Standard-IA テーブルクラスの料金
|プロビジョン済みスループットタイプ|時間あたりの料金(東京リージョン)|
|:-:|:-:|
|書き込みキャパシティーユニット (WCU)|0.0009275USD/WCU|
|読み込みキャパシティーユニット (RCU)|0.0001855USD/RCU|

DynamoDB Standardに比べるとDynamoDB Standard-IAの料金が2割増しになっています。他にもリザーブドキャパシティもあります。

また、Standardクラスには25WCUおよび25RCUのプロビジョン済み容量が無料枠です。

### Auto Scalingの仕組み

プロビジョニング済みキャパシティーモードを使うと事前にプロビジョニングしたユニットを大きく上回ったり（下回ったり）します。その場合、テーブルへのリクエストに待ちが発生してしまいます。例えばRCUを1ユニットプロビジョニングしていた場合に16KBのリクエストを行った場合最初の8KBは1秒以内に実行されますが、後半の8KBには待ちが発生してしまうので次の1秒まで待たないといけません。

そのため、プロビジョニング済みキャパシティーモードを使用する場合は原則Auto Scalingを利用します。Auto Scalingではあらかじめ目標使用率（デフォルト70%）を設定します。

（使用キャパシティー）÷（プロビジョニングされたキャパシティー）が設定した目標使用率を超えた場合、分母のプロビジョニングされたキャパシティーを増やすことで目標使用率を維持しようとします。

例えばプロビジョニングされたキャパシティーが100、使用キャパシティが90だった場合、（使用キャパシティー）÷（プロビジョニングされたキャパシティー）は90%になるので70%に調整するため、プロビジョニングされたキャパシティーを129に増やします。ここで増えた29ユニットは課金対象です。

![スクリーンショット 2022-09-30 19.53.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1654639/4511d4f1-23d8-7075-5466-2c94836454c2.png)

上図の10:20~10:30のようにまったく使用していないエリアが続いたあとではプロビジョニング量が低下します。また、10:35頃のようにプロビジョニング量を超えたあとでプロビジョニング量が増えていることがわかります。

このメトリクスをみると分かる通りAuto Scalingには多少の時間がかかります。Auto Scalingアクティビティを見てみると10:40にAuto Scalingが開始されていることがわかります。また、10:50の消費に対するAuto Scalingも10:55開始になっています。そのため、Auto Scalingにはおおよそ5分ほど時間がかかると考えられます。

![スクリーンショット 2022-09-30 20.07.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1654639/505c44ab-54af-e5b3-76fa-00f9a4db6e91.png)

本番稼働しているようなケースではあまり考慮する必要はないかもしれませんが、Auto Scalingはリクエストが0のときにはうまく機能しない点に注意です。例えばAuto Scalingでキャパシティが100まで増加したあとでリクエストが1件も送られてこなければAuto Scalingは機能せず高止まりしたままになります。

## バーストキャパシティ

公式にもあるようにプロビジョニングされているキャパシティを超えるリクエストがくるとスロットリングが発生してしまいます。

> テーブルがサポートできるよりも高いレートでアプリケーションが読み込みや書き込みを行うと、DynamoDB はこれらのリクエストのスロットリングを開始します。読み込みや書き込みをスロットリングした DynamoDB は、発信者に ProvisionedThroughputExceededException を返します。その後、アプリケーションは、リクエストの再試行前に短時間待機するなど、適切なアクションを実行できます。

（引用元：[リクエストのスロットリングとバーストキャパシティ](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ProvisionedThroughput.Throttling))


スロットリングが発生すると次のようなメッセージを返します。

```:(ProvisionedThroughputExceededExceptionのエラー例）
[ERROR] ProvisionedThroughputExceededException: An error occurred 
(ProvisionedThroughputExceededException) when calling the PutItem operation
(reached max retries: 9): 
The level of configured provisioned throughput for the table was exceeded. 
Consider increasing your provisioning level with the UpdateTable API.
```

先ほどの図をみると10:35で消費がプロビジョニングを超えているので`ProvisionedThroughputExceededException`が発生しそうですが、バーストキャパシティという仕組みがあるため発生していません。

![スクリーンショット 2022-09-30 19.53.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1654639/4511d4f1-23d8-7075-5466-2c94836454c2.png)



プロビジョニングモードでは未使用の読み込みおよび書き込み容量を最大5分(300 秒)間保持します。例えば1WCUプロビジョニングされていた場合は最大で300WCU保持していることになります。こうして保持しておいたキャパシティをスパイク時に使用することができるのがバーストキャパシティです。さらに、このキャパシティはテーブルに対して定義した1秒あたりにプロビジョンされるスループットキャパシティよりも高速です。

とはいっても、バックグラウンドメンテナンスやその他のタスクでバーストキャパシティが消費されることがあったり将来変更される可能性があるためあまり期待しすぎない方がよさそうです。

## さいごに

最低限の料金について調べた内容をまとめましたが、時間があれば細かい部分の料金についてもまとめたいと思います。


## 使用したスクリプト

今回のDynamoDBへの書き込みテストに使用したスクリプトです。Lambdaで動かしています。

```python
import boto3
import json

def lambda_handler(event, context):    

    for i in range(100):
        num_str = str(i)
        pad_num_str = num_str.zfill(8)
        item = {
            "id": pad_num_str,
            "value":"12345"
        }
        dynamo = boto3.resource('dynamodb')
        dynamo_table = dynamo.Table('ddb_fee_check_table')
        dynamo_table.put_item(Item=item)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

## 参考文献

https://aws.amazon.com/jp/dynamodb/pricing/

https://aws.amazon.com/jp/about-aws/whats-new/2021/12/amazon-dynamodb-standard-infrequent-access-table-class/

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

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-partition-key-design.html#bp-partition-key-throughput-bursting

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ProvisionedThroughput.html#ProvisionedThroughput.Throttling