概要
Amazon DynamoDB のテーブルに書き込みが集中すると、アプリケーションのログに ProvisionedThroughputExceededException が出たり、レスポンスが急に遅くなったりすることがあります。これがスロットリングです。DynamoDB が、テーブルに許されたスループットを超えたリクエストを受け付けずに返している状態です。
スロットリングの原因は 1 つではありません。プロビジョンドモードで指定したキャパシティが足りない、CloudWatch のグラフでは足りているように見えるのに秒単位では超えている、特定のパーティションキーにアクセスが偏っている(ホットパーティション)、Auto Scaling が急な増加に追いつかない、などがあります。オンデマンドモードでも、条件によってはスロットリングが起こります。
この記事では、スロットリングが起きる仕組みと確認するメトリクス、原因ごとの対処、オンデマンドとプロビジョンドの使い分けを整理します。実践では、書き込みキャパシティを 1 にしたプロビジョンドのテーブルに AWS CloudShell から短時間に書き込みを集中させてスロットリングを起こし、CloudWatch のメトリクスで確かめます。そのあとテーブルをオンデマンドに切り替え、同じ書き込みでスロットリングが出なくなることを確かめます。

クラウドおさる猿山のバナナ倉庫の入口が 1 秒に 1 匹しか通れないのに、収穫の日に猿が押し寄せたら、門番は「あとで来い」と追い返すしかないのでござる。今日はその追い返し、スロットリングをわざと起こして確かめるでござるよ。
この記事のメリット
- DynamoDB のスロットリングが、どの上限を超えたときに起きるかを説明できるようになる
- スロットリングを確かめる CloudWatch のメトリクス(
WriteThrottleEventsなど)と、1 分単位のメトリクスの読み方が分かる - バーストキャパシティ・ホットパーティション・Auto Scaling の遅れといった、「キャパシティは足りているはずなのに」の原因を切り分けられるようになる
- オンデマンドとプロビジョンドを、ワークロードの形から選べるようになる
- AWS CLI や SDK の自動再試行で、スロットリングが見えにくくなる理由が分かる
執筆者とキャラクター紹介
執筆者:土肥(とひ)
株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。


クラウドおさる



はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。



基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。



猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。
技術解説
キャパシティモードとスループットの単位
DynamoDB のテーブルは、読み込み・書き込みのスループットを 2 つのモードのどちらかで扱います。
| 項目 | オンデマンドモード | プロビジョンドモード |
|---|---|---|
| スループットの指定 | 不要。トラフィックに合わせて自動でスケールする | 1 秒あたりの読み込み(RCU)・書き込み(WCU)を指定する |
| 課金 | 読み込み・書き込みのリクエストごとの従量課金 | プロビジョニングしたキャパシティに対する時間単位の課金(実際に使った量ではない) |
| 向いているワークロード | 急に増減する、予測しにくいトラフィック。AWS はほとんどのワークロードでオンデマンドを推奨している | 予測しやすく安定した、徐々に増えるトラフィック |
| 主なスロットリングの原因 | 前のピークの 2 倍を 30 分以内に超える、自分で設定した最大スループット、アカウントのクォータ、パーティションの上限 | プロビジョニングした RCU・WCU を超える、パーティションの上限、アカウントのクォータ |
スループットの数え方はどちらのモードも同じです。
| 単位 | 1 単位で処理できる量 |
|---|---|
| 書き込み(WCU / 書き込みリクエストユニット) | 最大 1 KB の項目の書き込み 1 回 |
| 読み込み(RCU / 読み込みリクエストユニット) | 最大 4 KB の項目の、強力な整合性のある読み込み 1 回、または結果整合性のある読み込み 2 回 |
サイズは切り上げで数えます。たとえば約 9 KB の項目を 1 回書き込むと、9 WCU を消費します。WCU を 1 にしたテーブルでは、このサイズの項目は 1 秒に 1 回も書けない計算になります。
スロットリングを確かめるメトリクス
プロビジョンドのテーブルで、プロビジョニングした WCU を超えた書き込みは ProvisionedThroughputExceededException(HTTP 400)で返されます。CloudWatch では次のメトリクスで確かめます。
| メトリクス | 意味 |
|---|---|
ConsumedWriteCapacityUnits | 期間内に消費された書き込みキャパシティユニット。統計は Sum(合計)が最も役に立つ |
ProvisionedWriteCapacityUnits | プロビジョニングされている WCU |
WriteThrottleEvents | プロビジョニングされた WCU を超えた書き込みのイベント数 |
ThrottledRequests | リクエストの中のいずれかのイベントがスロットリングされると 1 増える |
WriteKeyRangeThroughputThrottleEvents | パーティションの上限でスロットリングされた書き込みの数 |
WriteMaxOnDemandThroughputThrottleEvents | オンデマンドの最大スループットでスロットリングされた書き込みの数 |
WriteAccountLimitThrottleEvents | アカウントの上限でスロットリングされた書き込みの数 |
ConsumedWriteCapacityUnits・WriteThrottleEvents・ThrottledRequests は 1 分間隔、ProvisionedWriteCapacityUnits は 5 分間隔で集計されます。
WriteThrottleEvents は、グローバルセカンダリインデックス(GSI)への書き込みもイベントとして数えます。GSI が 3 つあるテーブルへの PutItem は、テーブルと 3 つのインデックスで 4 つのイベントになります。どこで止まっているかは、ThrottledRequests と、テーブル・インデックスそれぞれの WriteThrottleEvents を比べて見ます。
1 分平均のグラフと、毎秒で効く上限
取り違えやすいのが、メトリクスの粒度と上限の粒度の違いです。CloudWatch のメトリクスは 1 分ごとの合計ですが、DynamoDB のレート制限は 1 秒単位で効きます。
たとえば 60 WCU をプロビジョニングしたテーブルに、1 分間で 3,000 回の書き込み(1 KB 以下の項目)を送るとします。1 分にならせば毎秒 50 回で、60 WCU に収まります。ところが 3,000 回が最初の 5 秒に集中すると毎秒 600 回になり、残りの 55 秒は何も書いていなくても、毎秒の上限を超えた分はスロットリングされます。このとき ConsumedWriteCapacityUnits の Sum を 60 で割った「1 秒あたりの平均」は 60 を下回るので、グラフだけを見るとキャパシティは足りているように見えます。


逆に、1 秒あたりの消費がプロビジョニングした値を少し超えただけでは、すぐにはスロットリングされません。DynamoDB は使わなかったキャパシティを最大 5 分(300 秒)ぶん バーストキャパシティとして取っておき、急な増加に使います。ただし、バーストキャパシティは DynamoDB がバックグラウンドのメンテナンスなどで予告なく消費することがあり、詳細は将来変わる可能性もあります。あてにして設計するものではありません。



1 分の合計で見ると門が空いているように見えても、猿が最初の数秒に殺到すれば追い返されるのでござる。グラフの平均ではなく、「毎秒の上限」で考えるのが肝でござるな。
「キャパシティは足りているはず」のときの原因
| 症状 | 考えられる原因 | 確かめ方・対処 |
|---|---|---|
| 1 分平均では余裕があるのにスロットリングされる | 書き込みが数秒に集中して、毎秒の上限を超えている | リクエストを時間的に散らす。スロットリングされたリクエストはエクスポネンシャルバックオフで再試行する。急な集中が前提ならオンデマンドを検討する |
| Auto Scaling を有効にしているのにスロットリングされる | Auto Scaling は、消費が目標使用率を 2 分間連続で超えてから動き、UpdateTable で容量が増えるまでに数分かかることがある。その間の超過分はスロットリングされる | 急増するワークロードは Auto Scaling ではなくオンデマンドで受ける |
| テーブル全体では余裕があるのに、一部のキーへのリクエストだけスロットリングされる | ホットパーティション。1 つのパーティションは 1 秒あたり 3,000 RCU / 1,000 WCU を超えるとスロットリングされる | CloudWatch Contributor Insights(追加料金あり)でアクセスの多いキーを見つける。パーティションキーのカーディナリティを上げる、書き込みシャーディングでキーを分散する |
| 大きなテーブルでスロットリングされる | アカウントのテーブルレベルのスループットクォータ(既定で 40,000 読み込み / 40,000 書き込みリクエストユニット)を超えている | Service Quotas でクォータの引き上げをリクエストする |
アクセスが多いパーティションのスループットは、アダプティブキャパシティで自動的に引き上げられます。すべてのテーブルで追加料金なしに有効になっていますが、テーブルにプロビジョニングした合計や、パーティションの上限を超えて使えるわけではありません。
Auto Scaling は、消費が目標使用率を下回る状態が 15 データポイント続くとスケールダウンします。徐々に増える予測しやすいトラフィックには向いていますが、短時間に跳ね上がるトラフィックには追いつきません。なお、コンソールでプロビジョンドのテーブルを作ると、Auto Scaling は既定で有効になります。
再試行でスロットリングが見えにくくなる
AWS の SDK は、ProvisionedThroughputExceededException を受け取ったリクエストを自動で再試行します。リクエストは最終的に成功することが多いので、アプリケーションからはエラーではなく「遅くなった」ように見えます。AWS CLI も同じで、バージョン 2 の既定(standard モード)では、最初の呼び出しを含めて最大 3 回まで試行します。
再試行は正しい振る舞いですが、検証でスロットリングを確かめるときは邪魔になります。実践では環境変数 AWS_MAX_ATTEMPTS を 1 にして、AWS CLI に再試行させずに 1 回ごとの成否を見ます。
BatchWriteItem では、スロットリングなどで処理されなかった項目がレスポンスの UnprocessedItems で返されます。返ってきた項目は、エクスポネンシャルバックオフを使って再試行します。



「エラーは出ていないのに遅い」ときこそスロットリングを疑うのでござる。SDK が裏で何度も門を叩き直しているだけかもしれぬでござるぞ。WriteThrottleEvents を見れば、追い返された回数が分かるのでござる。
キャパシティモードの切り替えとオンデマンドの上限
キャパシティモードは、テーブルを作った後でも切り替えられます。
| 項目 | 内容 |
|---|---|
| プロビジョンド → オンデマンド | 24 時間のローリングウィンドウで最大 4 回まで |
| オンデマンド → プロビジョンド | いつでも切り替えられる |
| 切り替えにかかる時間 | プロビジョンドからオンデマンドへの切り替えには数分かかることがある。その間は、以前にプロビジョニングした RCU・WCU と同等のスループットが提供される |
| 初めてオンデマンドにしたとき | それまで 4,000 WCU / 12,000 RCU を超えてプロビジョニングしたことがなければ、少なくとも毎秒 4,000 書き込みユニット・12,000 読み込みユニットをすぐに維持できる |
| Auto Scaling の設定 | コンソールでオンデマンドに切り替えると削除される(AWS CLI・SDK では保持される) |
オンデマンドでも、スループットに上限が無いわけではありません。
- 前のピークの 2 倍までは瞬時に対応するが、30 分以内に前のピークの 2 倍を超えるとスロットリングされる可能性がある
- テーブルや GSI ごとに、1 秒あたりの最大スループット(読み込み・書き込みのリクエストユニット)を任意で設定できる。既定では設定されていない。超えたリクエストはスロットリングされる。コストの上限の目安として使うもので、ベストエフォートで適用される
- アカウントのテーブルレベルのスループットクォータと、パーティションの上限はオンデマンドにも適用される
書き込みの急増を DynamoDB の手前で受け止める方法としては、キューで書き込みを平準化する構成もあります(SQS でデータベースへの書き込みの急増を受け止める方法 ── 疎結合で DB を落とさない)。
実践
書き込みキャパシティを 1 にしたプロビジョンドのテーブルを作り、CloudShell から約 9 KB の項目を続けて書き込んでスロットリングを起こします。CloudWatch のメトリクスで WriteThrottleEvents を確かめたら、テーブルをオンデマンドに切り替えて、同じ書き込みでスロットリングが出なくなることを確かめます。
前提条件
- リージョン:
ap-northeast-1(東京) - DynamoDB・CloudWatch・CloudShell の操作権限を持つ IAM ユーザーまたはロールでコンソールにログイン済みであること
作成済みのリソースは不要です。このハンズオンで作成するリソースは以下のとおりです。
| リソース種別 | リソース名 | 内容 |
|---|---|---|
| DynamoDB テーブル | throttle-lab | パーティションキー pk(文字列)。プロビジョンドモード、Auto Scaling オフ、読み込み 1 RCU・書き込み 1 WCU で作り、途中でオンデマンドに切り替える |
プロビジョンドモードのテーブルは、プロビジョニングしたキャパシティに対して時間単位で課金されます。オンデマンドに切り替えた後は、書き込みリクエストごとに課金されます。保存したデータにもストレージ料金がかかります。検証が終わったらテーブルを削除してください。
手順 1: WCU を 1 にしたプロビジョンドのテーブルを作る
AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。
上部の検索バーに DynamoDB と入力し、「DynamoDB」を選択します。左側ナビゲーションの「テーブル」を選択し、「テーブルの作成」をクリックします。以下を設定します。
- テーブル名:
throttle-lab - パーティションキー:
pk(文字列) - テーブル設定: 「設定をカスタマイズ」
- キャパシティーモード: 「プロビジョンド」
- 読み込みキャパシティー: Auto Scaling を「オフ」、プロビジョンドキャパシティーユニットを
1 - 書き込みキャパシティー: Auto Scaling を「オフ」、プロビジョンドキャパシティーユニットを
1
プロビジョンドを選ぶと、Auto Scaling は既定で「オン」、ユニット数は既定で 5 になっています。読み込み・書き込みの両方で、Auto Scaling をオフにしてからユニット数を 1 に書き換えます。その他の項目はデフォルトのままにして、「テーブルの作成」をクリックします。


テーブルの一覧で、throttle-lab のステータスが「アクティブ」になるまで待ちます。
手順 2: 1 件書き込んで消費する WCU を確かめる
コンソール上部のツールバーにある CloudShell のアイコンをクリックし、CloudShell を開きます。リージョンが ap-northeast-1 であることを確認します。
まず、AWS CLI に再試行させない設定にして、約 9 KB の文字列を用意します。
export AWS_MAX_ATTEMPTS=1
PAD=$(head -c 9000 /dev/zero | tr '\0' 'x')1 件だけ書き込み、消費したキャパシティを表示させます。
aws dynamodb put-item --table-name throttle-lab \
--item "{\"pk\":{\"S\":\"single-1\"},\"pad\":{\"S\":\"$PAD\"}}" \
--return-consumed-capacity TOTAL出力の ConsumedCapacity の CapacityUnits が 9.0 になっていることを確認します。項目が約 9 KB なので、1 回の書き込みで 9 WCU を消費しています。WCU 1 のテーブルでは、毎秒の上限を 1 回で超える書き込みです。


手順 3: 書き込みを集中させてスロットリングを起こす
同じ大きさの項目を 120 件、続けて書き込む関数を用意します。成功は .、失敗は x で表示し、最後に件数とエラーの種類を集計します。
write_burst() {
local prefix=$1 ok=0 ng=0
: > errors-$prefix.log
for i in $(seq 1 120); do
if aws dynamodb put-item --table-name throttle-lab \
--item "{\"pk\":{\"S\":\"$prefix-$i\"},\"pad\":{\"S\":\"$PAD\"}}" \
> /dev/null 2>> errors-$prefix.log; then
ok=$((ok+1)); printf '.'
else
ng=$((ng+1)); printf 'x'
fi
done
echo
echo "成功: $ok 失敗: $ng"
grep -o '([A-Za-z]*Exception)' errors-$prefix.log | sort | uniq -c
}プロビジョンドのテーブルに対して実行します。完了まで 2 分ほどかかります。
write_burst before始めのうちは . が続きますが、途中から x が混じるようになります。最後に、失敗の件数と (ProvisionedThroughputExceededException) の件数が表示されます。撮影時は、最初の 40 件ほどは成功が続き、全体では成功 101 件・失敗 19 件でした(件数は実行するたびに変わります)。始めに成功が続くのは、使っていなかったキャパシティ(バーストキャパシティ)が使われているためです。それを使い切ると、毎秒 1 WCU を超える書き込みはスロットリングされます。


cat errors-before.log | head -3 で、エラーメッセージの中身も確認できます。AWS CLI の再試行を止めているので、スロットリングされたリクエストがそのまま失敗として見えています。
手順 4: CloudWatch のメトリクスでスロットリングを確かめる
メトリクスが CloudWatch に反映されるまで数分待ちます。上部の検索バーに CloudWatch と入力し、「CloudWatch」を選択します。左側ナビゲーションの「メトリクス」→「クラシックメトリクス」(画面によっては「すべてのメトリクス」)を選択します。
「参照」タブで「DynamoDB」→「テーブルメトリクス」を選択し、検索ボックスに throttle-lab と入力して絞り込みます。ConsumedWriteCapacityUnits と WriteThrottleEvents のチェックボックスを選択します。
「グラフ化したメトリクス」タブで、両方のメトリクスの統計を「合計」、期間を「1 分」にします。WriteThrottleEvents は値が小さく線がつぶれるので、行の右端の矢印で右の Y 軸に移すと見やすくなります。グラフの時刻は既定で UTC です。


書き込みを集中させた時間帯に WriteThrottleEvents が出ていることを確認します。同じ時間帯の ConsumedWriteCapacityUnits の合計は、1 分あたり 60(毎秒 1 WCU × 60 秒)を大きく超えています(撮影時は 1 分ごとに 558・351。WriteThrottleEvents は 6・13 で、合計 19 が失敗の件数と一致しました)。プロビジョニングした量を超えた分は、バーストキャパシティなどで受け付けられた書き込みです。どれだけ受け付けられるかは保証されないので、あてにはできません。



「エラーが出た」と「WriteThrottleEvents が増えた」が同じ時間帯にそろったら、門の幅が足りないことの裏付けでござる。消費の合計とも並べて見ると、どれだけ押し寄せたかも分かるのでござるな。
手順 5: テーブルをオンデマンドに切り替える
DynamoDB コンソールに戻り、左側ナビゲーションの「テーブル」から throttle-lab を開きます。「設定」タブを選択し、「読み込み/書き込みキャパシティー」セクションの「編集」をクリックします。「読み込み/書き込みキャパシティーの編集」画面が開きます。
キャパシティーモードで「オンデマンド」を選択します。「最大テーブルスループット – オプション」が表示されますが、その他の項目はデフォルトのままにして、「変更を保存」をクリックします。


保存すると、テーブルの「概要」タブに戻ります。切り替えには数分かかることがあります(撮影時は約 2 分)。「設定」タブの「読み込み/書き込みキャパシティー」で、キャパシティーモードが「オンデマンド」と表示されることを確認します。
手順 6: 同じ書き込みでスロットリングが出ないことを確かめる
CloudShell に戻り、同じ関数をもう一度実行します。CloudShell のセッションが切れていた場合は、手順 2 の export と PAD=…、手順 3 の関数の定義からやり直します。
write_burst after今度は 120 件すべてが . になり、「成功: 120 失敗: 0」と表示されることを確認します。書き込みの量と速さは手順 3 と同じです。違うのはキャパシティモードだけです。


数分待ってから、手順 4 の CloudWatch のグラフを更新します。2 回目の書き込みの時間帯は、ConsumedWriteCapacityUnits は 1 回目と同じように出ていますが、WriteThrottleEvents は出ていないことを確認します。





急に増える・読めない書き込みはオンデマンド、予測できて安定した書き込みはプロビジョンドと Auto Scaling でござる。Auto Scaling は 2 分続いてから動くので、急な殺到の対策と取り違えないことでござるな。
まとめ
| 状況 | 選ぶもの・見るところ |
|---|---|
| トラフィックが急に増減する、予測しにくい | オンデマンドモード |
| トラフィックが予測でき、安定して徐々に増える | プロビジョンドモード + Auto Scaling(目標使用率) |
| 1 分平均では足りているのにスロットリングされる | 毎秒の上限。書き込みが数秒に集中していないか |
| 一部のキーだけスロットリングされる | ホットパーティション(1 パーティション 3,000 RCU / 1,000 WCU)。Contributor Insights、パーティションキーの設計、書き込みシャーディング |
| オンデマンドでもスロットリングされる | 前のピークの 2 倍を 30 分以内に超えていないか、最大スループットの設定、アカウントのクォータ |
- プロビジョンドのテーブルで WCU を超えた書き込みは
ProvisionedThroughputExceededExceptionになり、CloudWatch のWriteThrottleEventsに表れる - CloudWatch のメトリクスは 1 分ごとの合計だが、上限は 1 秒単位で効く。平均だけを見て「足りている」と判断しない
- 使わなかったキャパシティは最大 300 秒ぶんバーストキャパシティとして使われるが、保証されたものではない
- Auto Scaling は目標使用率を 2 分間連続で超えてから動き、容量が増えるまでに数分かかる。急増するワークロードには向かない
- SDK や AWS CLI はスロットリングされたリクエストを自動で再試行するため、エラーではなく遅延として見えることがある
- プロビジョンドからオンデマンドへの切り替えは 24 時間で最大 4 回、オンデマンドからプロビジョンドへはいつでもできる
参照先
- Amazon DynamoDB のプロビジョンドテーブルがスロットルされるのはなぜですか?
- Amazon DynamoDB でのスロットリングのトラブルシューティング – Amazon DynamoDB
- CloudWatch スロットリングメトリクス – Amazon DynamoDB
- DynamoDB のメトリクスとディメンション – Amazon DynamoDB
- DynamoDB のスループットキャパシティ – Amazon DynamoDB
- DynamoDB オンデマンドキャパシティモード – Amazon DynamoDB
- DynamoDB プロビジョンドキャパシティモード – Amazon DynamoDB
- オンデマンドテーブルの DynamoDB 最大スループット – Amazon DynamoDB
- DynamoDB でキャパシティモードを切り替える際の考慮事項 – Amazon DynamoDB
- DynamoDB バーストキャパシティとアダプティブキャパシティ – Amazon DynamoDB
- DynamoDB Auto Scaling によるスループットキャパシティの自動管理 – Amazon DynamoDB
- DynamoDB でのエラー処理 – Amazon DynamoDB
- 書き込みシャーディングを使用して DynamoDB テーブルでワークロードを均等に分散させる – Amazon DynamoDB
- CloudWatch Contributor Insights for DynamoDB: 仕組み – Amazon DynamoDB
- AWS CLI retries in the AWS CLI – AWS Command Line Interface













