DynamoDBのキャパシティユニットを時間帯で削るのじゃ

# はじめに
季節はもう春。
お花さんたちも花粉をそよ風に乗せ、私のお鼻さんも壊滅的な状況に見舞われております。

今回はDynamoDBのコストカットのために、キャパシティユニットを時間帯で見直してみました。
１日の時間帯で変化をつけていく方法です。
備忘録のために手法だけ残しておきます。
もっといい方法あるよ！って方お待ちしています。
# 前提
### キャパシティユニットについて
DynamoDBにはキャパシティユニットという概念があります。

オンデマンドキャパシティとプロビジョニンドキャパシティについては下の記事でわかりやすく説明されています。
それぞれの特徴やメリットデメリット、ユースケースとてもわかりやすかったです。

https://blog.serverworks.co.jp/DynamoDB-Capacity_Mode

ConsumedWriteCapacityUnitsやConsumedReadCapacityUnitsから消費ユニットを計算し、料金比較をすると、運用しているテーブルで「あ、これオンデマンドの方が安いんやないか？」ってものもありました。
ただし、基本的にある程度使用しているCUが多くなれば、ある程度高めの設定をしていてもプロビジョンドを選択している方がコストを抑えられます。

今回はプロビジョンドキャパシティユニットを選択しているケースです。

# 課題
### コスト削減
当初プロビジョンドキャパシティユニットを一定の値に設定していたため、余剰コストが発生していました。
1日単位で、下のイメージのような波形を繰り返します。
日中帯の取引が多いですが、夜間は極端に少なくなります。
1日の中で時間帯によって、キャパシティユニットを操作できればいいなと思いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1431393/e49bcc7a-ca66-b497-f662-f5402ea58868.png)


# 解決
### 設定値の決定
まず、設定値となる値を算定しました。
算定の仕方はそれぞれあるかと思いますが、本ケースの場合、すでに運用しているのでは数ヶ月単位で消費を監視し、最大の値から算出しました。

### スケジューリング設定
実際の値とは違いますが、スケジューリングの方法を備忘録として残します。

（対象テーブル）qiita-update-fight-table

時間で細かくプロビジョンドキャパシティユニットを設定したい場合は、前提としてオートスケールを有効化しておかなかればいけません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1431393/28678bda-ea46-a3b7-f79e-2e4c80086951.png)

デフォルトの設定値をどうするか、オートスケールの割合をどうするかを考えておく必要があります。

Cloudshell上で次のコマンドを実行します。
##### オートスケール機能オン
- 対象のテーブルをオートスケールの対象に登録し、書き込みキャパシティユニットのみ最小値と最大値を指定します。
```shell
aws application-autoscaling register-scalable-target \
    --service-namespace dynamodb \
    --resource-id "table/qiita-update-fight-table" \
    --scalable-dimension dynamodb:table:WriteCapacityUnits \
    --min-capacity 5 \
    --max-capacity 100
```
実行結果として次が表示されれば成功です。
```
{
    "ScalableTargetARN": "arn:aws:application-autoscaling:us-east-1:************:scalable-target/0d26f2576e5e1bba4b80a4ee3ae1e659f234"
}
```
- スケーリングの率などのスケーリングポリシーを指定します（コンソール上ではここでオートスケールが有効化される）
```shell
aws application-autoscaling put-scaling-policy \
    --policy-name "qiita-update-fight-table-policy" \
    --service-namespace dynamodb \
    --resource-id "table/qiita-update-fight-table" \
    --scalable-dimension dynamodb:table:WriteCapacityUnits \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{"TargetValue": 70.0, "PredefinedMetricSpecification": {"PredefinedMetricType": "DynamoDBWriteCapacityUtilization"}}'
```
コマンドを実行すると下記のように表示されCloudWatchアラームが作成されます。
このアラームを利用して、スケールの有無を監視することもできます。
```
{
    "PolicyARN": "arn:aws:autoscaling:us-east-1:************:scalingPolicy:f2576e5e-1bba-4b80-a4ee-3ae1e659f234:resource/dynamodb/table/qiita-update-fight-table:policyName/qiita-update-fight-table-policy",
    "Alarms": [
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-AlarmHigh-fee8c08b-2996-4723-9a20-27821fb7a732",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-AlarmHigh-fee8c08b-2996-4723-9a20-27821fb7a732"
        },
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-AlarmLow-32ef1533-d7d4-4edb-8c22-2863a3776b9c",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-AlarmLow-32ef1533-d7d4-4edb-8c22-2863a3776b9c"
        },
:...skipping...
{
    "PolicyARN": "arn:aws:autoscaling:us-east-1:************:scalingPolicy:f2576e5e-1bba-4b80-a4ee-3ae1e659f234:resource/dynamodb/table/qiita-update-fight-table:policyName/qiita-update-fight-table-policy",
    "Alarms": [
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-AlarmHigh-fee8c08b-2996-4723-9a20-27821fb7a732",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-AlarmHigh-fee8c08b-2996-4723-9a20-27821fb7a732"
        },
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-AlarmLow-32ef1533-d7d4-4edb-8c22-2863a3776b9c",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-AlarmLow-32ef1533-d7d4-4edb-8c22-2863a3776b9c"
        },
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-ProvisionedCapacityHigh-58a10342-87f1-4f12-8cda-fd63aad38a04",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-ProvisionedCapacityHigh-58a10342-87f1-4f12-8cda-fd63aad38a04"
        },
        {
            "AlarmName": "TargetTracking-table/qiita-update-fight-table-ProvisionedCapacityLow-ab4259a7-c349-4e9f-9738-707f94066c9f",
            "AlarmARN": "arn:aws:cloudwatch:us-east-1:************:alarm:TargetTracking-table/qiita-update-fight-table-ProvisionedCapacityLow-ab4259a7-c349-4e9f-9738-707f94066c9f"
        }
    ]
}
~
```

##### 事前・事後確認コマンド
今現在スケジューリングがされていることを確認する場合は下記コマンドを実行します。
必要な情報だけを抜き出して一覧にしています。
- 現在のスケジューリング状況確認
```
aws application-autoscaling describe-scheduled-actions --service-namespace dynamodb | jq -r '.ScheduledActions[]|[.ScheduledActionName, .ServiceNamespace, .Schedule, .ResourceId, .ScalableDimension, .ScalableTargetAction.MinCapacity, .ScalableTargetAction.MaxCapacity]|@csv' | sort -t, -k4,4 -k1,1
```
まだ未登録であれば何も表示されません。
スケジュールが登録されていれば次のように出力されます。
```
"qiita-update-fight-table-1st-plan","dynamodb","cron(10 21 * * ? *)","table/qiita-update-fight-table","dynamodb:table:WriteCapacityUnits",30,100

```
##### スケジュールを作成する
aws application-autoscaling put-scheduled-actionコマンドを用いて、MinCapacityの値を調整します。

以下のコマンドは
毎日6:10(JST)にqiita-update-fight-table-1st-planというスケジュール名でqiita-update-fight-tableの　WriteCapacityUnitsをMinCapacity=30,MaxCapacity=100に設定するコマンドです。
```
aws application-autoscaling put-scheduled-action \
  --service-namespace dynamodb \
  --schedule "cron(10 21 * * ? *)" \
  --scheduled-action-name "qiita-update-fight-table-1st-plan" \
  --resource-id "table/qiita-update-fight-table" \
  --scalable-dimension dynamodb:table:WriteCapacityUnits \
  --scalable-target-action MinCapacity=30,MaxCapacity=100 
```

AWSのcronは少しハマるので次の記事あたりを参考にしてください。

https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-cron-expressions.html

https://dev.classmethod.jp/articles/aws-cron-expression-syntax/

- 削除したい場合
```
aws application-autoscaling delete-scheduled-action \
  --service-namespace dynamodb \
  --resource-id "table/qiita-update-fight-table" \
  --scheduled-action-name "qiita-update-fight-table-1st-plan" \
  --scalable-dimension "dynamodb:table:WriteCapacityUnits"
```

### 動作確認
確認コマンド
```
aws application-autoscaling describe-scheduled-actions --service-namespace dynamodb | jq -r '.ScheduledActions[]|[.ScheduledActionName, .ServiceNamespace, .Schedule, .ResourceId, .ScalableDimension, .ScalableTargetAction.MinCapacity, .ScalableTargetAction.MaxCapacity]|@csv' | sort -t, -k4,4 -k1,1
```
スケジュールが登録されていれば次のように出力されます。
```
"qiita-update-fight-table-1st-plan","dynamodb","cron(10 21 * * ? *)","table/qiita-update-fight-table","dynamodb:table:WriteCapacityUnits",30,100
```
アクティビティを確認すると6:10にキャパシティユニットを30に変更できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1431393/f1b7153f-5960-3535-3b4f-7b5962f39c9f.png)
メトリクス上でもうまくいっていようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1431393/7f17f0c4-a247-a966-6359-b1763119bc02.png)

このスケジューリング設定を組み合わせることで、時間帯や曜日でコストを削減することができます。

# さいごに
見直しの中で大事にはこの設定自体よりもこの設定値の算出方法です。
設定値の根拠をどう計算するか、スパイクが起きた場合の処理、オートスケールは何%が適しているか・・・
などなど考慮すべき事項はたくさんあります。
小さなことから一歩ずつの精神で頑張っていきます！