Amazon Web Services ブログ

Amazon DynamoDB スケーリングのベストプラクティス

はじめに

長年にわたって、Amazon DynamoDB はものすごいスピードで進化を遂げています。グローバルセカンダリインデックス(GSI)、クエリ式のサポート、Auto Scaling 機能、そして、ポイントインタイムリカバリ (PITR) 、オンデマンドモードを断間なくリリースされました。お客様はモバイルバックエンドをはじめ、ゲームや IoT (Internet of Things) の実装まで、さまざまなユースケースで DynamoDB を使用してきました。

本記事は、DynamoDB を利用する中で、スケーリングの部分に焦点に当たって、スケーリングを考えるときのポイントを説明します。その後、代表的なワークロード、例えば、新しいサービスローンチや、ゲームにおいては新しいガチャがリリースする直後の運用例を持って具体的な設定方法を話します。最後に他の Tips について紹介します。

スケーリングを考えるときのポイント

スケーリングを考えるときに、重要なポイントとして水平スケールの速度、運用方法、コストが挙げられます。 DynamoDB は Capacity Unit (CU) を使って、テーブルやインデックスごとのキャパシティー管理をしています。キャパシティー管理のモードとしては、オンデマンドおよびプロビジョニングがあります。オンデマンドモードなら、DynamoDB が自動的に必要なだけのリソースを提供するため、リクエストを処理するのに必要な CU をユーザーが管理する必要がありません。プロビジョニングモードの場合、アプリケーションに必要な読み込みと書き込みの CU をあらかじめ指定します。Auto Scaling の機能とあわせて利用することも可能で、トラフィックの変化に応じて、キャパシティーを自動的に調整できます。

次に、水平スケールの速度を考えます。スケールアウトに関して多くのケースではできる限り早いスケール速度が求められますが、一方で、スケールインの際には、次のスパイクを上手く対処できるように、ゆっくりと下がっていくことが安定運用に対して非常に重要です。では、実際スケールについてどのくらいの性能が出るか気になるかと思います。まずオンデマンドモードの場合、スケールアウトもスケールインも即座に対応が可能です。これに対して、プロビジョニングモードの場合、Auto Scaling 機能を利用し、スケールアウトではトラフィックの増加を検知してから、CU を上げる処理が実行完了するまで、サーバーの状態によって変化しますが、私がテストしたところおおよそ 6 分程度で完了しました*1 *2。スケールインについては、全ての処理が 20 分以内で完了しています。ここで一つ注意点ですが、DynamoDB はこれまでに利用する過去の最大 CU をすぐに利用できるようにパーティションを保持しています。過去の最大 CU とは、オンデマンドモードは過去最大 Consumed CU の 2 倍、プロビジョニングモードは過去最大 Provisioned CU の値となります。それ以上に必要となると、パーティションを追加する処理が必要なため、スケールの速度に影響する可能性があります。

さて、運用の部分に移ります。オンデマンドモードはほとんどの場合キャパシティプランニングを意識せずに利用することは可能です。プロビジョニングモードは Auto Scaling と組み合わせての利用が一般的ですので、たとえばターゲットの使用率を 50% に仮定しましょう、この設定でしたら瞬時に倍までの負荷を対応できるので、通常緩やかの増加に関しては特に追加の対応は必要ありません。それでは、たとえば、10 時から限定品の発売やテレビで放映されるような、予定されているイベントが分かっていますが、直前のトラフィックと比べて大きくスパイクする場合はどう対応すべきかについては、スケジュールされたスケーリング機能を使用して、特定の時間にスケールアウトするように設定すれば、急なスパイクにも対応が容易です。

最後コストについてです。オンデマンドモードはリクエストごと、使用した分だけ課金されます。プロビジョニングモードでは、まず設定したキャパシティユニット分の時間課金が掛かります。これはDynamoDBを利用しなくても変わりません。Auto Scaling の機能を利用することにより、DynamoDB のキャパシティーを柔軟に調整したり、必要最小限の CU をリザーブドキャパシティーを利用すれば、コストを最適化することができます。

*1 スケールの速度は、2022 年 5 月 24 日時点で DynamoDB のデフォルト Auto Scaling ポリシーを利用したテストに基づいて書かれています。必ず全てのスケール処理がこの時間内に完了する保証はありません。また、今後も仕様変更の可能性もありますので、目安としてご利用ください。
*2 CloudWatch メトリクスにおける ProvisionedReadCapacityUnitsProvisionedWriteCapacityUnits は 5 分間隔で集計されていますので、それらを利用して確認する際では、実際の動作と比べて、CloudWatch 上は少し遅れて表示される可能性があります。

代表的なアクセスパターンの運用例

よくあるアクセスパターンをここで紹介します。CU の中で Read Capacity Unit (RCU) および Write Capacity Unit(WCU) それぞれありますが、実際運用するときに考える要素が同じため、ここでは合わせて Capacity Unit として話します。

新規サービスのローンチ時

新規サービスのローンチ時のアクセスパターン
新規サービスのローンチの場合、実際どのくらい流量が来るのか、どのようなアクセスパターンなのかを事前に想定することは難しいです。このケースは以下の手順を踏まえて対応することができます。

  1. 必要な CU の最大値を事前に見積もりをします。一つの計算例として、最大の Daily Active Users (DAU) は 100 万人、同時アクセスの割合は 20%、一人当たりは 10 秒に 1 回程度のアクセス頻度、一回のアクセスに必要な平均 CU は 1 を仮定します。必要最大の CU は 1,000,000 x 0.2 x 0.1 x 1 CU = 20,000 CU の計算です。
  2. 必要となる CU がデフォルトクォータを超えていないかを確認し、超えている場合はクォータの引き上げを申請します。
  3. (オプション) 必要な CU 値に設定します。オンデマンドモードは、デフォルトのテーブルスループットとして、12,000 RCU および 4,000 WCU までが既に利用できます。ほとんどの場合はこれで十分ですが、大規模のシステムで、これよりも多くの CU が必要で、かつアクセスは緩やかではなく 30 分以内で倍以上となる急激なアクセスの時だけに、プロビジョニングモードを利用して CU を設定しましょう。以下の図では、RCU を 20,000 に設定した例です。RCU を設定する
  4. (オプション) 同じテーブルで負荷試験を実施します。DynamoDB のパーティションニングは自動的に最適はされますが、負荷試験により実際のワークロードに近い状態でさらに最適されやすいためです。負荷試験実施後は、生成されたデータの削除もしくは本番運用に影響しないように処理を行います。パーティションをキープしながら、全てのデータを削除する方法ですが、一つ目の方法としては、データを入れる際に Time to Live(TTL) を同時に設定することによって、負荷試験の後で自動的に削除することができます。こちらの方法でしたら、削除のために WCU を消費しないため、コストが抑えられます。一方でより柔軟に削除のタイミングを調整したいときは、一度全てのキーをスキャンしてから、BatchWriteItem を利用して、高速に削除することができます。
  5. オンデマンドモードに切り替えます。オンデマンドモードおよびプロビジョニングモード間の切り替えは、1 日 1 回の制限がありますので、ご注意ください。
  6. ローンチしてからは、どのようなアクセスパターンかまだ分らない間はオンデマンドモードを利用し、アクセスパターンが分かってきて、プロビジョニングモードでうまく動作すると判断できれば、プロビジョニングモードに切り替えます。さらに常に利用する CU の部分にリザーブドキャパシティーを活用しましょう。一方で、うまく対応できないアクセスパターン、もしくは管理コストを削減したいならば、そのままオンデマンドモードを利用するのがいいですね。

緩やかな増減パターン

緩やかな増減のアクセスパターン

時間帯に伴うアクセスの自然増減が最も一般的のパターンです。プロビジョニングモードの Auto Scaling 機能を利用し Consumed CapacityProvisioned Capacity に離れていれば安全ではありますが、近づけるのがコスト最適化に繋がりますので、例えば最初の時に、Auto Scaling のターゲット使用率を 40% に設定し、運用している中で、少しずつ 50%、60%、70% まで増やしていく手法をお勧めします。

急激な増加

急激な増加によるアクセス

ゲームにおいては新しいガチャのリリース直後や、ネットショップにはブラックフライデーなどのイベントで、急激の増加パターンもよくあるかと思います。この図は理解しやすいように作成されていますが、実際の場合、Consumed CapacityProvisioned Capacity を上回ることはありません。このように、Consumed Capacity の増加に追いつかない場合は、スロットリングエラーが発生してしまうため注意が必要です。ここでは、急激の増加の発生時間が予測できるか、できないか、二つのケースを分けて考えましょう。発生時間が予測できるのであれば、新たにスケジュールされたスケーリングを設定し、既存の Auto scaling ポリシーと併用すれば、容易に対応が可能です。

こちらの例では、毎日の 14 時 30 分 UTC に、TestTable の最小キャパシティーを 1000 に設定する一例です。その後負荷が落ち着くタイミングで、最小キャパシティーを戻します。

$ aws application-autoscaling put-scheduled-action \
--service-namespace dynamodb \
--scheduled-action-name my-recurring-action \
--schedule "cron(30 14 * * ? *)" \
--resource-id table/TestTable \
--scalable-dimension dynamodb:table:WriteCapacityUnits \
--scalable-target-action MinCapacity=1000

また、単発のスケジュールも設定可能です。以下の例では、日本時間の 2022 年 6 月 30 日の 22 時に一度のみ発動する設定です。詳細の設定方法についてはこちらを参考にしてください。

$ aws application-autoscaling put-scheduled-action \
--service-namespace dynamodb \
--scheduled-action-name my-recurring-action-2 \
--schedule "at(2022-06-30T22:00:00)" \
--timezone "Asia/Tokyo" \
--resource-id table/TestTable \
--scalable-dimension dynamodb:table:WriteCapacityUnits \
--scalable-target-action MinCapacity=1000

それでは、発生時間が予測できないケースの対応方法について、前を持って Auto Scaling の最小 CU を引き上げに設定する方法、もしくは一度オンデマンドモードに切り換えて利用することで対応は可能です。

これまでに DynamoDB 側で急激の増加の対応手段を紹介しました。運用を楽にする工夫として、たとえばイベントの企画やアプリケーションの仕様について、なるべく短時間に集中的なアクセスが行われないようにランダムな待ち時間 sleep(n) を入れたり、フロント側で流量制限を入れたり、急増なアクセスを数十秒から数分ほどに分散することができれば、既存の Auto Scaling で対応しやすくなります。また、DynamoDB 以外でも、キャッシュ、計算リソースもこの急増に合わせて増設対応が必要なため、企画や設計の段階で、緩やかなアクセスを実現できれば、運用は楽になります。

過去の最大 CU を上回るスパイク

過去の最大 CU を上回るアクセスパターン

新規サービスローンチ時の手順を踏まえたとしても、長く運用する中で、これまでの過去の最大 CU を上回る可能性もあるかと思います。「スケーリングを考えるときのポイント」で話したように、過去の最大 CU 以上に必要となる時に、パーティションを追加する処理が必要なため、スケーリングが遅れる可能性があります。緩やかな増加パターンでしたら、多少のスロットリングやエラーが出たとしても、リトライすることでレイテンシーの微増くらいで収まるでしょう。たとえば、ローンチ時に宣伝をせず、ある程度運用してからテレビ CM 等の宣伝によって、これまでの 5 倍、10 倍の負荷が 30 分以内に殺到するようなアクセスの増加で過去の最大 CU を大きく上まる可能性があるとしたら、前をもってプロビジョニングモードを利用し、Provisioned Capacity を予測最大の CU 以上に設定することができます。また、過去の最大 CU を確認する方法ですが、現状としては、CloudWatch から最大 15 ヶ月前までのデータを取得可能ですので、たとえば以下のプロビジョニングモードの例では、TestTable に対して、過去 15 ヶ月間の ProvisionedReadCapacityUnits の最大値を確認することができます。

$ aws cloudwatch get-metric-statistics --namespace 'AWS/DynamoDB' \
--metric-name 'ProvisionedReadCapacityUnits' \
--start-time $(date -I --date="15 month ago") \
--end-time $(date -I) \
--period 86400 \
--dimensions Name=TableName,Value=TestTable \
--statistics Maximum | jq '.Datapoints | max_by(.Maximum)'
{
"Timestamp": "2022-04-17T00:00:00+00:00",
"Maximum": 2000,
"Unit": "Count"
}

繰り返し瞬間的なスパイク

繰り返し瞬間的なスパイクのアクセスパターン

上のグラフのように、直前の負荷から数倍、数十倍、数百倍に増加する場合では、時間が決まってあれば、スケジュールされたスケーリング機能を利用して対応は可能です。一方で、いつスパイクするか予測できないケースでは、Auto Scale だけでは対応が難しい時もあります。こういったケースでは、オンデマンドモードを利用するとオペレーションをせずに対応ができます。さらに、コストの面でも、リクエストごと使用した分が課金され、使用していない時間は課金されないため、常に一定としたキャパシティーを利用した運用方法と比べて、コストを抑えることが可能です。

その他の Tips

スロットリング・エラーについて

DynamoDB の動作中に、リクエストの速度が許容されているスループットを超えている場合には 4xx ThrottlingException エラーが返ります。また、稀に内部の状態によって、5xx エラーを返す時があります、これらは AWS SDK を利用しているのであれば、リトライロジックが使われているため自動的に解決します。ご自身で実装する場合は、エクスポネンシャルバックオフアルゴリズムを使ったリトライ処理を入れましょう。レイテンシーが許容できて、かつエラーレートが増加し続けなければ正常の動作ですので、運用の中ではとても重要なポイントです。リトライの設定によっては、長くリトライされるケースやタイムアウトするケースもあります。ここでは、AWS JAVA SDK による既定値の例ですが、これらのパラメーターを設定して、予期する復旧時間を調整したり、最大レイテンシーを制限したり、リトライによる DynamoDB へスパイクの負荷を制限することができます。

/** Ref:
https://github.com/aws/aws-sdk-java/blob/1.12.233/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedBackoffStrategies.java
https://github.com/aws/aws-sdk-java/blob/1.12.233/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java
**/

/**
* スロットリングされた時に、EqualJitterBackoffStrategy が利用されます。
* 500 ms、1000 ms、2000 ms のように、最大 20 秒までリトライされます。
**/
static final int SDK_DEFAULT_THROTTLED_BASE_DELAY = 500;

/**
* 5XX エラーが発生するときに、FullJitterBackoffStrategy が利用されます。
* 25 ms からスタートし、最大 20 秒までリトライされます。
**/
static final int DYNAMODB_DEFAULT_BASE_DELAY = 25;

/**
* リトライ可能な最大時間が 20 秒です。
**/
static final int SDK_DEFAULT_MAX_BACKOFF_IN_MILLISECONDS = 20 * 1000;

/** DynamoDB client 標準最大のリトライ回数は 10 回です。 **/
private static final int DYNAMODB_STANDARD_DEFAULT_MAX_ERROR_RETRY = 10;

JAVA SDK より詳細のチューニングはこちらのブログを確認ください

DynamoDB の仕様を理解するためのテスト方法について

DynamoDB の性質、例えばパーティションニング、アダプティブ容量、スロットリングの概念を理解する際に、ある程度負荷掛けていない状態では、性質を確認することが難しい場合があります。手軽にパーティションキーやソートキーを大量に生成して、メカニズムを理解するときは、ご自身でスクリプトを書いて確認する、もしくは aws-samples にある dynamodb-consumed-capacity-check-tool ツールをご利用いただけます。

より大規模の負荷をかけたい、性質確認やベンチマークを行う時は、Yahoo Cloud Serving Benchmark (YCSB) を使えば、高い精度で RCU/WCU 負荷を制御し、数万、数十万 QPS を簡単に生成することができます。

最後に

DynamoDB を運用する際には、通常使うことであればハードルは低いが、より効果的に利用し DynamoDB のポテンシャルをフルに引き出すなら CU の管理は重要になってきます。初めは少し馴染みが無くハードルに感じるかもしれませんが、しかしそれを理解することによってメンテナンスやバージョン管理が必要なくなるのは、運用面非常に大きなベネフィットがあります。是非とも素敵な DynamoDB Life を送ってください。何かあればお近くのソリューションアーキテクトまで!

著者について

zheng shao's profile image

Zheng Shao

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