DynamoDBのコスト削減のためにやったこと

AWS

はじめに

DynamoDBはサーバレスでJSONオブジェクトを属性として直接保存することができ、便利な反面、思わぬところでコストが嵩んでいることもあります。少しの工夫で大幅にコスト削減が可能であることが分かりました。

もちろん、システムの稼働状況やDBの負荷状況によって最適な施策は変わるので、あくまで参考程度に捉えていただければ幸いです。以下の状況に当てはまれば、対応の余地ありの可能性があるので、一度確認してみると良いかもしれません。

  • データライフサイクルが無いテーブルがある
  • テーブルへの書込み・読み込みがほとんどバッチで、オンデマンドキャパシティモードで稼働しているテーブルがある
  • レコード内にやたら容量の大きい属性値を持つ属性がある
  • アクセス頻度が低いテーブルにStandard テーブルクラスを採用している

今回実施した施策は以下の通りです。

  • 保存容量の縮小・テーブルクラスの変更
  • キャパシティモードの変更
  • 格納データの見直し

保存容量の縮小・テーブルクラスの変更

ログ関連データを保存していたテーブルに適用したコスト削減です。

本記事執筆時点(2024.4)では、東京リージョンにおけるキャパシティに関する料金体系は以下の通りでした。

Standard テーブルクラスStandard-Infrequent Access (Standard-IA) テーブルクラス
0.285USD/GB-月 ※10.114USD/GB-月
※1 最初に保存される 25 GB (1 か月あたり) は無料

今回、この施策を対応したテーブルはログを保存しているテーブルでした。古いもので3年ぐらい前のログが、細かい粒度で保存されていたので、約200GBのデータがこのテーブルに溜まりっぱなしになっていました。GSIも設定されていたため、約$100近く月々このテーブルにコストがかかっている状態でした。

そこで、新規レコードと、既存レコードの2つの面で以下の対応を取ることにしました。

新規レコード

  • アプリ側のコードに、PUT時のレコードにTTLとしての列(expiredAt)を追加
  • DynamoDB側で、1年を有効期限としたTTLの設定の有効化し、期限切れのデータはS3 Glacierにアーカイブ

1つ目は、秒単位のUNIX時間をレコードに追加するイメージです。ミリ秒単位ではTTL設定できないので、秒単位で設定します。

2つ目については、AWS公式ドキュメント内のモダナイゼーションとして以下が紹介されていたので、こちらを参考にして設定を行いました。

ERROR: The request could not be satisfied

既存レコード

  • 既存レコードのバックアップ(必要がなければ、その後バックアップは消去)
  • 既存テーブルの削除および新規作成
  • Standard-Infrequent Access (Standard-IA) テーブルクラスの切り替え

最初は、UpdateItemやDeleteItemを使って、どうにかしようと考えましたが、開発環境で検証を進め、キャパシティユニットの消費がかかり結局コストが嵩みそうだったので、バックアップを取り、必要がなければその後、バックアップを削除し、新たに新規のテーブルを作成することにしました。

そのままDynamoDBに貯め続けるよりかは安価ですが、バックアップする容量に対しても以下のように料金がかかるので、必要がなければバックアップも消去するのが良いかと思います。

ウォームバックアップストレージコールドバックアップストレージ※1
0.114USD/GB-月0.0342USD/GB-月
※1 コールドバックアップストレージは、AWS Backup によってのみ管理されるオンデマンドバックアップ向けにサポートされています。AWS マネジメントコンソールから AWS Backup の使用をオプトインできます。 コールドストレージに移行されたバックアップの保存期間は最低で 90 日間です。90 日間が経過する前にバックアップが削除された場合、その 90 日間の残りの日数についてのストレージ料金に等しい按分金額が請求されます。

あとは、低頻度アクセスのデータを保存するのに適したテーブルクラス(Standard-Infrequent Access (Standard-IA) テーブルクラス)が提供されているので、そちらを新規作成したテーブルに適用しました。保存容量に対する料金は上の表で紹介した通りです。

キャパシティモードの変更

バッチ処理が主に書込み・読み込みをしていたテーブルに適用したコスト削減です。

DynamoDBのキャパシティモードには

  • オンデマンドキャパシティモード
  • プロビジョンドキャパシティモード

があります。各キャパシティモードがどのようなものであるかは省略します。本記事執筆時点(2024.4)では、東京リージョンにおけるキャパシティに関する料金体系は以下の通りでした。

Standard テーブルクラス
キャパシティの種類100万消費キャパシティあたりの料金
書込みキャパシティ(WRU)1.4269 USD
読出しキャパシティ(RRU)0.285 USD
Standard-Infrequent Access (Standard-IA) テーブルクラス
キャパシティの種類100万消費キャパシティあたりの料金
書込みキャパシティ(WRU)1.7836 USD
読出しキャパシティ(RRU)0.356 USD

テーブル作成時の設定がデフォルトの場合、プロビジョンドキャパシティモードですが、今回の場合、バッチ処理ぐらいでしか書込み・読み込みしていないテーブルについても、すべてのテーブルがオンデマンドキャパシティモードになっていました。

バッチ処理に合わせて、書込みキャパシティ・読み込みキャパシティの使用量がメトリクスからある程度分かっていたので、バッチ処理の時刻に合わせてオートスケールさせるための設定をAWS CLIを用いて行いました。

// 読み込みキャパシティ ピーク時
aws application-autoscaling put-scheduled-action --service-namespace dynamodb --schedule "cron(50 0,9 * * ? *)" --scheduled-action-name RaiseReadLimit --resource-id table/[テーブル名] --scalable-dimension dynamodb:table:ReadCapacityUnits --scalable-target-action MinCapacity=50,MaxCapacity=90

// 書き込みキャパシティ ピーク時
aws application-autoscaling put-scheduled-action --service-namespace dynamodb --schedule "cron(50 0,9 * * ? *)" --scheduled-action-name RaiseWriteLimit --resource-id table/[テーブル名] --scalable-dimension dynamodb:table:WriteCapacityUnits --scalable-target-action MinCapacity=300,MaxCapacity=700

// 読み込みキャパシティ アイドル時
aws application-autoscaling put-scheduled-action --service-namespace dynamodb --schedule "cron(01 5,11 * * ? *)" --scheduled-action-name LowerReadLimit --resource-id table/[テーブル名] --scalable-dimension dynamodb:table:ReadCapacityUnits --scalable-target-action MinCapacity=10,MaxCapacity=50

// 書き込みキャパシティ アイドル時
aws application-autoscaling put-scheduled-action --service-namespace dynamodb --schedule "cron(01 5,11 * * ? *)" --scheduled-action-name LowerWriteLimit --resource-id table/[テーブル名] --scalable-dimension dynamodb:table:WriteCapacityUnits --scalable-target-action MinCapacity=5,MaxCapacity=100

格納データの見直し

これは「保存容量の縮小」と似たようなものなのですが、一言でいえば、「属性値に大きなデータ(例えば、JSONデータの配列など)を格納せず、分散できるものは分散する」を意識しました。当時そのシステムの処理としては、1レコードの1属性をUPDATEする処理がメインの処理だったのですが、その属性がJSONデータを配列として持っており、そのデータサイズがかなり大きいものとなっていました。

UPDATEするのは属性値の配列の一部だったとしても、キャパシティ消費はそのレコード全体にかかってくるので、膨大な書込みキャパシティユニットを消費していました。

そこで、テーブル構成を変えるのは少し大規模になりそうだったので、その属性値を含む1レコードを複数のレコードに分割する形で、登録し直しました。イメージとしては、属性値の配列の長さが100のものを、10にして、10レコードに分割するイメージです。

たったこれだけですが、書込みキャパシティ消費は激減しました。

最終的にどれだけ削減できたか

以上の3つの施策をもって、全体で「約$750」ほどかかっていたのが、「約$200」まで削減することができました。パーセンテージにして、「約70%」の削減です。ここで紹介した施策の実施是非は、システムの稼働状況やDBの負荷状況によって慎重に検討する必要がありますが、これだけの施策でコスト削減できるのであれば、一考の価値ありだと思います。

コメント

タイトルとURLをコピーしました