Amazon DynamoDB に負荷をかけ、検証する方法をご紹介

2023-01-05
デベロッパーのためのクラウド活用方法

Author : 邵 正

皆さんこんにちは、Game Solutions Architect の邵 (@axot) です。

以前 AWS ブログの Amazon DynamoDB スケーリングのベストプラクティス という記事で DynamoDB 運用する際のポイントを紹介しました。

本記事では、DynamoDB に負荷をかける方法をご紹介します。さらに、それを応用してスパイクおよび不均等なアクセスパターンについてのテストおよび解説もご紹介します。最後まで是非ご覧ください !


準備

Amazon DynamoDB 作成

まず、今回のテストのための DynamoDB テーブルを作成します。

ベンチマークではなく、負荷の掛け方や性質を確認が目的なので、ReadCapacityUnits(RCU)、WriteCapacityUnits(WCU) とも最小限に 1 に設定しています。

また、ソートキー については本日のテストで利用しないため、プライマリキーのみを作成しています。

# 本記事は、aws cli バージョン 2 を利用しています。
# もしバージョン 1 をご利用している場合は、こちらの手順を参考して
# バージョン 2 をインストールしてください
# https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html
$ aws --version
aws-cli/2.9.8 Python/3.9.11 Linux/5.10.147-133.644.amzn2.x86_64 exe/x86_64.amzn.2 prompt/off

$ aws dynamodb create-table \
    --table-name my-load-test \
    --attribute-definitions \
        AttributeName=pk,AttributeType=S \
    --key-schema \
        AttributeName=pk,KeyType=HASH \
    --provisioned-throughput \
        ReadCapacityUnits=1,WriteCapacityUnits=1 \
    --table-class STANDARD

ホットスポットやスロットリングされた パーティションキー には、Amazon CloudWatch Contributor Insights for DynamoDB機能が非常に役立ちますので、有効しておきます。テストケース 2 および 3 で利用方法も紹介します。

$ aws dynamodb update-contributor-insights --table-name my-load-test \
    --contributor-insights-action=ENABLE

負荷を掛けるための Amazon EC2 を作成

負荷をかけるために Amazon EC2 を利用します。今回のテストは数 Queries Per Second (1 秒当たりのクエリ件数 / QPS) までを予定していますので、t2.micro レベルで十分です。EC2 自体の作成の方法は割愛します。

$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2"
ID="amzn"
ID_LIKE="centos rhel fedora"
VERSION_ID="2"
PRETTY_NAME="Amazon Linux 2"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2"
HOME_URL="https://amazonlinux.com/"

$ uname -a
Linux ip-10-0-12-202.ap-northeast-1.compute.internal 5.10.147-133.644.amzn2.x86_64 #1 SMP Fri Oct 14 01:16:24 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

DynamoDB のテストを行うにあたり、YCSB (Yahoo Cloud Serving Benchmark) というツールを使うことができます。YCSB は、さまざまなシステムを評価するためのツールであり、DynamoDB もサポートしています。YCSB を使うことで、必要な負荷を掛けたり、DynamoDB のスループットやレイテンシーを測定することができます。

# YCSB は JAVA で動作しますので、Amazon が開発した Corretto(OpenJDK) を入れます。
$ sudo yum install -y java-11-amazon-corretto git

# コンパイル済みの YCSB を install
$ curl -O --location https://github.com/brianfrankcooper/YCSB/releases/download/0.17.0/ycsb-0.17.0.tar.gz
$ tar xfvz ycsb-0.17.0.tar.gz

# DynamoDB テストに必要な権限を作成
# 便宜上 IAM User を利用します
$ aws iam create-user --user-name ddb-tester

# 今回対象テーブル my-load-test のフル権限を付与
$ aws iam put-user-policy --user-name ddb-tester --policy-name ddb-test-full --policy-document "$(cat <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "dynamodb:*"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:dynamodb:*:*:table/my-load-test",
        "arn:aws:dynamodb:*:*:table/my-load-test/index/*"
      ]
    }
  ]
}
EOF
)"

# IAM User のアクセスキーを発行
$ aws iam create-access-key --user-name ddb-tester
{
    "AccessKey": {
        "UserName": "ddb-tester",
        "AccessKeyId": "AKxxxx",
        "Status": "Active",
        "SecretAccessKey": "xxxx",
        "CreateDate": "2022-12-02T05:13:51+00:00"
    }
}

# YCSB が利用する AWS アクセスキーを設定
$ vim AWSCredentials.properties
accessKey =
secretKey =

テスト

ここまでで基本のセットアップが完了しましたので、テストに移ります。

今回のテストでは、3 つのテストを通して負荷の掛け方を示します。テスト 1 では、基本的な負荷を掛ける方法を示します。テスト 2 ではスパイクのトラフィックを再現し、DynamoDB の バーストキャパシティー 機能がどのようにアクセスを処理するかを確認します。テスト 3 は不均等なアクセスパターンを掛けることで、アダプティブキャパシティー がどのように動作するかを解説します。各テストの設定は、dynamodb-common.properties ファイルに共通で記述することもできます。詳細に関しては、YCSB 全体の設定DynamoDB 固有の設定 をご参考ください。

# YCSB 共通の設定を入れます
$ vim dynamodb-common.properties
dynamodb.awsCredentialsFile = ./AWSCredentials.properties
dynamodb.primaryKey = pk
dynamodb.primaryKeyType = HASH
dynamodb.region = ap-northeast-1
dynamodb.endpoint = http://dynamodb.ap-northeast-1.amazonaws.com.
requestdistribution = uniform
dynamodb.debug = false
dynamodb.connectMax = 100
dynamodb.consistentReads = false
fieldcount = 5
table=my-load-test

# ycsb コマンドを直接利用できるように
$ export PATH=$HOME/ycsb-0.17.0/bin:$PATH

ケース 1 : 基本的な負荷を掛ける方法

初めのテストケースは固定の負荷をかけるシナリオとなります。

こちらの例では 100% Write (Update) の負荷で、1 QPS をかけるものです。同じ手法を使って 1k/10k QPS も同様に掛けることは可能です。その際には EC2 インスタンスのスペックだったり、-threads にも調整が必要な場合があります。

$ cat test-1
# CRUD ベースの workload タイプ
# 基本はこちらを利用します
# 他に REST 対応の workload も存在します
# ref: https://github.com/brianfrankcooper/YCSB/tree/0.17.0/core/src/main/java/site/ycsb/workloads
workload=site.ycsb.workloads.CoreWorkload

# DynamoDB に 100 個のデータを投入します。
recordcount=100

# ケース 1 では 100 オーペレションに到達したら終了します。
# 1 QPS をかけますので、100 秒で終了になります。
operationcount=100

# 単純化のため、読み込みなどの操作の割合を 0% にしてあります
readproportion=0
scanproportion=0
insertproportion=0

# Update の割合は 100% です
updateproportion=1

負荷かける前に、テストに必要なデータを準備しますので、operationcount で設定されている 100 個のデータを投入します。

# 1 thread で合計 1 QPS の速度でデータを投入
$ ycsb load dynamodb -P test-1 -P dynamodb-common.properties \
  -target 1 -s -threads 1

DynamoDB コンソールの 項目を探索 を利用することで、DynamoDB テーブル内に投入したデータを確認することができます。また、DynamoDB コンソールではデータの追加、編集、削除などの操作も行うことができるため、データの管理をする際にも便利です。

クリックすると拡大します

投入したアイテムのカウントやテーブルのサイズは DynamoDB テーブル の中で確認することもできます。こちらの情報はリアルタイムではないため、少し遅れて表示されます。

クリックすると拡大します

DynamoDB のテーブル内にあるアイテムの数をリアルタイムで取得するには、DynamoDB コンソールのライブ項目数を取得を利用することができます。このボタンをクリックすると、その時点でのテーブル内のアイテム数が表示されます。

ただし、本番環境などでテーブルサイズが大きい場合には、テーブル内のすべてのアイテムをスキャンするために、DynamoDB に大量のリクエストを送信します。このような場合、DynamoDB のスループットを超えるリクエストが発生する可能性がありますので、ご注意ください。

クリックすると拡大します

さて、準備はできましたので、いよいよ負荷をかけてみましょう !

# 1 thread で合計 1 QPS の速度 item を update
$ ycsb run dynamodb -P test-1 -P dynamodb-common.properties \
  -target 1 -s -threads 1

今回は更新 (updateproportion) が 100% の負荷を掛けています。DynamoDB のテーブルで更新操作を実行すると、それに伴い Write Capacity Units (WCU) が使用されます。Write Capacity Units (WCU) は、DynamoDB テーブルが 1 秒あたりに書き込める能力を表す概念で、Provisioned Capacity の設定値に基づいて、事前に確保することができます。

DynamoDB コンソール では、Write usage グラフで、Write Capacity Units (WCU) の使用状況を確認することができます。このグラフでは、赤い線が Provisioned Capacity を表し、青い線が実際に観測された Consumed Capacity を表します。また、Write throttled requestsWrite throttled events のカウントも表示されています。今回のテストで、それらが発生していないことも確認できました。これで想定通りの負荷が掛かっていたことは分かります。

クリックすると拡大します

ケース 2 : スパイクした負荷を掛けてみよう

DynamoDB では短時間のスパイクをベストエフォートで処理できるように、バーストキャパシティー機能を導入しています。

現在、DynamoDB は、未使用の読み込みおよび書き込み容量を最大 5 分 (300 秒) 保持します。読み込みまたは書き込みアクティビティが時折バーストする間、これらの余分な容量ユニットをすばやく消費できます。これは、テーブルに対して定義した 1 秒あたりにプロビジョンされるスループットキャパシティよりも高速です。

また、DynamoDB はバックグラウンドメンテナンスやその他のタスクのために予告なしにバーストキャパシティを消費する場合があります。

これらのバーストキャパシティの詳細は将来変更される可能性があります。 

詳細はこちら » 

これにより、DynamoDB は、スパイクなアクセスを処理するために、通常の読み取りキャパシティーや書き込みキャパシティーを増やすことに加え、バーストキャパシティーを使用しスパイクなアクセスを処理することができるようになります。

では、スパイクなアクセスをシミュレートして、バーストキャパシティーがどのように動作するかを理解してみましょう。

現在 Write キャパシティーユニットは 1 に設定しているため、バーストキャパシティー最大の容量は 1 WCU x 300s になります。例えばスパイクな Write 負荷を 5 QPS で掛ける場合、 1 WCU x 300s / ( 5 QPS - 1 QPS) = 75s まで耐えられます。

$ cp test-1 test-2

# metrics 観測しやすいように、operationcount を 600 に設定
$ vim test-2 
$ diff -u test-1 test-2
--- test-1    2022-12-02 05:30:06.066016917 +0000
+++ test-2    2022-12-02 06:09:30.256767226 +0000
@@ -1,7 +1,7 @@
 workload=site.ycsb.workloads.CoreWorkload

 recordcount=100
-operationcount=100
+operationcount=600

 readproportion=0
 updateproportion=1

# Write QPS を 5 に変更します。threads 数は増やしても良いですが、
# 5 qps 程度は余裕ですので、そのまま 1 にしてあります。
# threads の計算方法ですが
# 1 + qps/(1000ms/(1 qps の平均 latency))
# が baseline を考えています。
$ ycsb run dynamodb -P test-2 -P dynamodb-common.properties \
  -target 5 -s -threads 1 2>&1 | tee /tmp/test-2.log

# metrics は基本 DynamoDB 側の metrics 見ていますが、
# client latency を知りたい場合は例えば、YCSB の output を解析する方法もあります
# 今回は output の方を解析して、csv に直してグラフにも生成してみました。
$ MODE=UPDATE
$ file=/tmp/test-2.log
$ header=$(echo -n Time,; tail -100 $file | sed -n -e "s/.*$MODE: \([^]]*\).*/\1/p" | sed -e 's/=[^,]*//g' | sed -e 's/ //g' | uniq)
$ content=$(paste -d ',' <(cat $file | egrep '[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2} [0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}' | grep $MODE | awk '{print $1,$2}') <(cat $file | sed -n -e "s/.*$MODE: \([^]]*\).*/\1/p" | sed -e 's/[^,]*=//g' | sed -e 's/ //g'))
$ echo "$header"$'\n'"$content" > /tmp/test-2.csv

CloudWatch Contributor Insights for DynamoDB の方では、最もアクセスしたキーや、スロットリングされたキーを確認することができます。今回はバーストキャパシティーを使い切った後スロットリングが発生し、具体的なキー名まで確認できました。この情報を利用することで、パーティションキーの偏りを確認することができます。

クリックすると拡大します

その他に、全体的に Write usage や、Write throttled requestsWrite throttled events も確認できました。

クリックすると拡大します

ここまでのメトリクスでは、DynamoDB 視点でしたが、負荷をかけた側のメトリクスも確認してみましょう。こちらのグラフは test-2.csv から生成されています。負荷は 06:11:18 で開始し、06:12:28 までは Write QPS は 5、レイテンシーは 30ms 以下で安定しています。その後 Write QPS は大きく低下し、レイテンシーも悪化していることがわかります。一方で 75s まではバーストキャパシティーで耐えられる計算通りの動きになります。

ケース 3 : 不均等なアクセスパターンを掛けてみましょう

DynamoDB には、テーブル内のデータを分散するための パーティション という概念があります。パーティションキーをハッシュ化することで、どのパーティションにデータが属するかを決定します。DynamoDB は、パーティション数にかかわらず、項目がテーブルのパーティション全体に渡って均一にディストリビューションされている状態に対して最適化されています。また、DynamoDB にプロビジョンされたキャパシティは、各パーティションに均等に割り当てられます。

では、たとえば不均等なアクセスが発生し、該当パーティションに割り当てられたキャパシティ以上にアクセスがあるとスロットリングされるかどうかの疑問はあるかもしれません。DynamoDB はこのようなアクセスを処理するために、アダプティブキャパシティーという機能を導入しています。公式の例 を確認してみましょう。

次の図は、アダプティブキャパシティの機能を示しています。このテーブルの例のプロビジョニングでは、4 つのパーティション間で 400 WCU が均等に共有されることで、各パーティションで 1 秒間に最大 100 WCU を維持できます。パーティション 1、2、3 はそれぞれ、50 WCU/秒の書き込みトラフィックを受け取ります。パーティション 4 は 150 WCU/秒の書き込みトラフィックを受け取ります。このホットパーティションは、未使用のバーストキャパシティーがある場合でも書き込みトラフィックを受け入ることができます。しかし、バーストキャパシティーだけでは、最終的には 1 秒間に 100 WCU を超えるトラフィックのスロットリングとなります。

DynamoDB アダプティブキャパシティーはパーティション 4 の容量を増大して応答するため、このパーティションはスロットリングされることなく、150 WCU / 秒の高度なワークロードを維持できます。

簡単にまとめますと、プロビジョニング済みキャパシティーが消費されたキャパシティーよりも余っている分は、個別のパーティションで利用は可能となっています。不均等なアクセスがある際もスロットリングなしで耐えられます。注意点としましては、パーティションごとの上限キャパシティーは存在しますので、詳細は 公式ドキュメント を確認してくださいね。

それでは、不均等なアクセスをやってみましょう !

次の図のように、スループットやストレージサイズに応じてそれぞれ独立したパーティション数を計算し、そのうち大きい方を選択する方法になります。注意すべきポイントとして、DynamoDB は実際のパーティション数を運用の過程で自動的に最適な数に増やしますので、利用者は意識する必要はありません。なので、パーティション数に関しては、お客様から直接確認することはできません。

今回のテストでは、アダプティブキャパシティーの性質を示すために、2 つのパーティションを作りますが、通常の利用においては DynamoDB サービスが最適なパーティション数となるよう自動で変更を加えるため、お客様はパーティションを意識する必要はありません。上記の計算式で分かるように、2 つのパーティションを作るには、データを 10GB 以上にするかもしくはスループットを調整すれば実現は可能です。

今回は Write キャパシティーユニット (WCU) を 1000 に調整し、Read キャパシティーユニット (RCU) を 1 にすることで作ってみました。こちらのテストは一時的にプロビジョン済みの WCU を 1000 にする必要があります。現時点で東京リージョンの場合 1 時間あたりの 1000 WCU のご利用に 0.742 ドルが発生しますので、なる早でプロビジョン済みの WCU を戻しておくようにしましょう。使用料金はこちら でご確認ができます。

# テスト2からコピーします
$ cp test-2 test-3

# 合計のデータの数 recordcount を 1 にすることで、
# 負荷をかける際には、1 つのキーにのみアクセスすることができます。
# つまり、2 つのパーティションのうち、1 つのパーティションだけにアクセスすることになります。
# また、テスト時間を 10 分以上に調整するために、operationcount を調整ます
$ diff test-2 test-3
3,4c3,4
< recordcount=100
< operationcount=600
---
> recordcount=1
> operationcount=2400

# 二つの partition を作るために、RCU 1、WCU 1000 を一時的にスケール
$ aws dynamodb update-table --table-name my-load-test \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1000

# partition 拡張できるまで待ちます
$ watch aws dynamodb describe-table --table-name my-load-test

# TableStatus が ACTIVE になったら、準備は完了です
$ diff -u /tmp/update_before /tmp/update_after
-    "TableStatus": "UPDATING",
+    "TableStatus": "ACTIVE",
         "CreationDateTime": "2022-12-02T02:26:17.923000+00:00",
         "ProvisionedThroughput": {
-            "LastIncreaseDateTime": "2022-12-02T07:12:57.044000+00:00",
+            "LastIncreaseDateTime": "2022-12-02T07:38:43.756000+00:00",
             "NumberOfDecreasesToday": 0,
             "ReadCapacityUnits": 1,
-            "WriteCapacityUnits": 1
+            "WriteCapacityUnits": 1000
         },
     "TableSizeBytes": 55490,
         "ItemCount": 100,

# Partition 作成した後は、Capacity を小さくしても
# Partition の数は減りません!
# WCU を 2 に設定
$ aws dynamodb update-table --table-name my-load-test \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=2

# これで二つの partition それぞれ 1 WCU がプロビジョンされます
# 今回の負荷は、一つの key だけに 2 write qps をかけてみますが、
# Burst Capacity だけを利用する場合、
# 2 WCU x 300s / ( 2 - 1 ) WCU = 600s
# まで耐えられますが、最終的には Throttling されてしまいます。
# ここで、Adaptive Capacity と併用することで問題なく捌くことができる想定です。
$ ycsb run dynamodb -P test-3 -P dynamodb-common.properties \
  -target 2 -s -threads 1 2>&1 | tee /tmp/test-3.log

CloudWatch Contributor Insights for DynamoDB で一つのキーだけにアクセスされているを確認できました。

継続した 2 QPS の負荷が 15 分以上にかかり続けていて、プロビジョン済み WCU が 2、消費済み WCU も 2 を確認し、スロットリングが発生していないことも確認できました。想定通りの動きですね。


クリーンアップ

最後に忘れず、テストで作成したリソースを削除していきます。

# DynamoDB Table の削除
$ aws dynamodb delete-table --table-name my-load-test

# IAM 関連を削除
$ aws iam delete-user-policy --user-name ddb-tester --policy-name ddb-test-full
$ aws iam delete-access-key --user-name ddb-tester --access-key-id xxx
$ aws iam delete-user --user-name ddb-tester

まとめ

今回は DynamoDB に負荷を掛け方を紹介しました。その手法を使用することで、バーストキャパシティーやアダプティブキャパシティーについても解説しました。また、この方法は、大規模なテストや伸縮性能の検証にも使用できるため、ぜひ活用してみてください !


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます


筆者プロフィール

邵 正
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト

Linux カーネル、コンテナ、データベースにフォーカスしています。お客様と協力して様々なクラウドインフラストラクチャのチャレンジを一緒に解決することで、よりヘルシーな設計・運用ができるように、サポートしています。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する