DynamoDB のスロットリングとその直し方
スロットリングは、上限に触れたことを DynamoDB が伝えている状態です。上限は4種類、それを報告する例外は3種類あり、ある原因への対処が別の原因を悪化させることがあります。
テーブルのキャパシティを上げてもホットキーには何の効果もなく、オンデマンドへの切り替えもホットキーには効きません。しかもオンデマンドは、オンデマンド自身のルールでスロットリングします。
DynamoDB はなぜリクエストをスロットリングするのか?
文書化された4つの上限のいずれかに触れたからです。どれに触れたかは、例外の
ThrottlingReasons フィールドが名指しします。何かのサイズを変える前に、まずそれを読みましょう。
- ホットキー: 1つのパーティションが1秒あたり読み取り 3,000 ユニットまたは書き込み 1,000 ユニットを超えた。どちらのキャパシティモードでも起こります。
- プロビジョンドキャパシティ: プロビジョンドのテーブルまたは GSI が、その RCU または WCU を超えて使用した。
- テーブルごとのクォータ: オンデマンドのテーブルまたは GSI が、1秒あたり 40,000 読み取りまたは書き込みリクエストユニット (デフォルト) を超えた。
- 自分で設定した最大値: オンデマンドのテーブルまたは GSI が、そこに設定した最大スループットを超えた。
オンデマンドテーブルは、30 分以内に直前のピークの2倍を超えてスケールする間にもスロットリングすることがあります。
スロットリングの4つのシナリオ
AWS 自身のトラブルシューティングページは、スロットリングをちょうど4つのケースに分けています。
- キーレンジ (パーティション) のスループット超過、両モード。 すべてのパーティションは 1秒あたり読み取り 3,000 ユニットと書き込み 1,000 ユニットを最大値として設計されており (パーティションキーのドキュメント)、アイテムサイズもそこに効いてきます。テーブルレベルの設定でこれを引き上げることはできず、分散させられるのはキー設計だけです。これがホットパーティションのケースで、スロットリングしていてもテーブルは大きく使い残しているように見えることがあります。
- プロビジョンドスループットの超過、プロビジョンドモード。 消費がテーブル (または GSI) のプロビジョンド RCU/WCU を上回り、約5分のバーストキャパシティの緩衝も使い切った状態です。対処の段階はキャパシティ側です。オートスケーリング、より高いプロビジョン値、あるいはモードの切り替え。
- テーブルごとのクォータ超過、オンデマンドモード。 オンデマンドのテーブルまたは GSI がテーブルごとのクォータを超えた状態です。デフォルトでは、リージョンごとに1秒あたり読み取り 40,000、書き込み 40,000 リクエストユニットです (クォータ)。調整可能なので、Service Quotas コンソールで引き上げを申請します。(アカウントごとの 80,000 のクォータはプロビジョンドテーブルだけが対象で、プロビジョンできる量に上限を設けるものです。)
- オンデマンドの最大スループット超過、オンデマンドモード。 コスト管理のためにテーブルまたは
GSI に最大値を設定しており、トラフィックがそれを超えた状態です。引き上げるか、
-1に設定して上限を外します (スロットリングの診断)。
aws dynamodb update-table --table-name my-table \
--on-demand-throughput MaxReadRequestUnits=20000,MaxWriteRequestUnits=8000この4つとは別に、オンデマンドテーブルには立ち上がりがあります。新しいテーブルは1秒あたり書き込み 4,000、読み取り 12,000 を維持でき、その後は直前のピークの2倍までを即座に受け止めます。
30 分以内にその2倍を超えて伸びると、スロットリングすることがあります (オンデマンドのドキュメント)。増加を少なくとも 30 分かけて分散させるか、計画済みのスパイクの前に事前ウォームしましょう。
aws dynamodb update-table --table-name my-table \
--warm-throughput ReadUnitsPerSecond=24000,WriteUnitsPerSecond=8000事前ウォームは非同期で (WarmThroughput.Status が ACTIVE になるまで待ちます)、課金され、後から下げることはできません
(ウォームスループット)。新しいテーブルが実際にどう立ち上がるかは、オンデマンドのスループットで測っています。
下のビジュアライザはシナリオ 1 を再現します。トラフィックを1つのキーに偏らせると、テーブル全体の合計レートは控えめなまま、単一のパーティションがパーティションごとの上限を突破します。
3つの例外と、原因を名指しするフィールド
ProvisionedThroughputExceededException: プロビジョンドモードのスロットリングです。 "you exceeded your maximum allowed provisioned throughput for a table or for one or more global secondary indexes"。プロビジョンドテーブルのホットキーも、KeyRangeThroughputExceededを理由としてここで報告されます。ThrottlingException: コントロールプレーンの操作を速く出しすぎた場合、そしてオンデマンドテーブルでは、設定した最大値などの上限を超えたデータプレーンのリクエストです。RequestLimitExceeded: アカウントレベルのスループット上限です。Service Quotas コンソールでクォータの引き上げを申請します。
3つとも再試行可能とされており、3つとも ThrottlingReasons のリストを伴います。各エントリには、リソース + 操作 + 上限の形をした reason (TableReadProvisionedThroughputExceeded、
IndexWriteKeyRangeThroughputExceeded、TableWriteAccountLimitExceeded) と、テーブルまたはインデックスを名指しする resource ARN があります
(エラーリファレンス)。
JavaScript SDK v3 では、このフィールドの大文字・小文字が例外によって異なります。
ThrottlingException では throttlingReasons、他の2つでは ThrottlingReasons です。両方を読みましょう。
const reasons = err.ThrottlingReasons ?? err.throttlingReasons ?? [];
for (const {reason, resource} of reasons) console.warn(reason, resource);ハンドリングは例外クラスではなく、理由の文字列を軸に組み立ててください。AWS のページは、アカウント上限のスロットリングが RequestLimitExceeded として現れるのか、
AccountLimitExceeded を理由に持つ ThrottlingException として現れるのかで食い違っています。
TransactWriteItems の中でスロットリングされたアクションは、キャンセルされたトランザクションとして現れることもあります。キャンセル理由コード ThrottlingError または
ProvisionedThroughputExceeded を伴う TransactionCanceledException です。SDK はこの例外を再試行しないので、バックオフ付きで自分で再試行しましょう (トランザクション)。
スロットリングされる前に負荷を吸収するもの
3つの組み込み機能が上限を和らげます。その境目を知っておくと、「昨日は動いていたのに」の説明がつきます。
- バーストキャパシティは、スパイクに備えて最大5分 (300秒) 分の未使用の読み取りキャパシティと書き込みキャパシティを保持します。DynamoDB がバックグラウンドのメンテナンスのために "without prior notice" 消費することもあり、AWS は詳細が変わり得ると明記しています。バーストを前提に設計してはいけません。運が良かった程度に扱いましょう。
- アダプティブキャパシティは、スループットを自動的かつ即座にホットパーティションへ寄せ、高頻度でアクセスされるアイテムを独自のパーティションへ隔離できます。ただし "provided that traffic does not exceed your table's total provisioned capacity or the partition maximum capacity" という条件付きです。偏りを均すだけで、パーティションごとの 3,000/1,000 の天井を持ち上げることは決してなく、テーブルに LSI があるとアイテムコレクションを分割しません。
- split-for-heat (熱による分割) は、現在の AWS のトラブルシューティングページが頼りにしているもので、持続する熱を受けたパーティションを分割します。これには時間がかかり、単一のホットキーには効きません。
メトリクスから診断する
CloudWatch はリクエストとイベントを区別しており、この違いこそが診断そのものです (メトリクスリファレンス)。
ThrottledRequestsは、リクエストの中のいずれかのイベントがスロットリングされたとき、そのリクエストを1回として数えます。GSI を3つ持つテーブルへのPutItemは、リクエストとしては 1つですが書き込みイベントとしては4つです。バッチでは、すべてのアイテムがスロットリングされた場合にのみ増えます。ReadThrottleEvents/WriteThrottleEventsは、スロットリングされたイベントを1つずつ数えます。10 アイテムのBatchGetItemは 10 個のGetItemイベントです。GSI の書き込みスロットリングを見るには、TableNameとGlobalSecondaryIndexNameの両方を指定してメトリクスを問い合わせる必要があります。GSI バックプレッシャーがテーブルレベルのダッシュボードから隠れるのは、これが理由です。- 新しい理由別のイベントメトリクス (
WriteProvisionedThroughputThrottleEvents、ReadKeyRangeThroughputThrottleEvents、…AccountLimitThrottleEvents、…MaxOnDemandThroughputThrottleEvents) は、同じ4つの原因でカウントを分けます。自分のリージョンで見えるなら、「どの上限か」という問いに直接答えてくれます。
理由が KeyRangeThroughputExceeded なら、キーを特定しましょう。resource ARN が指すテーブルまたは GSI で CloudWatch Contributor Insights をスロットリングされたキーのモードで有効にすると、最もスロットリングされたキーが一覧になります
(スロットリングの診断)。
aws dynamodb update-contributor-insights --table-name my-table \
--contributor-insights-action ENABLE --contributor-insights-mode THROTTLED_KEYS突出したキーが1つもなければ、原因はたいてい1つのキーレンジにまたがる順次アクセスです。
Scan や、スキャン順に行う書き込みがそれにあたります。1秒あたり読み取り 3,000 ユニットまたは書き込み 1,000 ユニット未満にレート制限するか、並列スキャンで読み取りを分散させましょう。
SDK はスロットリングされたリクエストを自動的に再試行するので、軽いスロットリングはエラーではなくレイテンシとして現れます。例外のログだけでなく、スロットリングのメトリクスを見てください。試行回数は SDK によって異なり、JavaScript v3 の標準モードはデフォルトで3回です。
2026 年の更新版リトライ動作 (デフォルトになるまでは AWS_NEW_RETRIES_2026=true でオプトイン)
では、DynamoDB クライアントは4回試行します。スロットリングのリトライは、まず 0〜1,000 ms のランダムな時間、次に最大 2,000 ms、その次に最大 4,000 ms 待ちます
(リトライ動作)。
GSI バックプレッシャー: 間違ったテーブルを指すスロットリング
どれか1つの GSI が書き込みの増幅を吸収できないと、"DynamoDB throttles writes to the base table
to maintain data consistency"
(GSI スロットリングのドキュメント)。ベーステーブルにキャパシティの余裕があってもです。例外の ResourceArn はインデックスを指しますが、失敗した操作はあなたのベーステーブルへの書き込みです。インデックスにはそれぞれ独自のキャパシティ計画 (と独自のオートスケーリングポリシー)
が必要です。
GSI がベーステーブルの書き込みをスロットリングする理由がその仕組みを追っています。
原因に合った対処を選ぶ
| 原因 | 効く対処 | 効かないもの |
|---|---|---|
| ホットキー / パーティション | Contributor Insights でキーを特定してシャーディングする (ホットパーティション)。split-for-heat には時間が必要 | テーブルのキャパシティを上げる、オンデマンドへの切り替え |
| プロビジョンドキャパシティ | オートスケーリング、最小値の引き上げ、またはオンデマンド | リトライだけ (負荷を増やします) |
| GSI バックプレッシャー | インデックスをスケールする。スパースインデックス化や射影の見直し | ベーステーブルのスケール |
| テーブルごとのクォータ (オンデマンド) | Service Quotas での引き上げ | テーブルレベルの設定 |
| 自分で設定したオンデマンドの最大値 | MaxReadRequestUnits / MaxWriteRequestUnits を引き上げるか、-1 に設定する | 待つこと、事前ウォーム |
| ピークの 2× を超えるオンデマンドの立ち上がり | 事前ウォーム (ウォームスループット)、または増加を 30 分以上に分散 | 設定していない最大値の引き上げ |
DynoTable でやってみる
フィルタ付きの Scan は、フィルタが何を残すかにかかわらず読み取り全量を消費します。ダッシュボードのクエリ1つが忙しいテーブルをスロットリングさせ得るのは、このためです。SQL Workbench と
PartiQL エディタでは、DynoTable の実行前コストプレビューが、プランが得られる場合に Scan と
Query のどちらかと、使用するインデックスを示します。
RCU の見積もりは、テーブルのメタデータが対応している場合にのみ表示されます。通常のパーティション
Query では RCU は表示されません。違いはScan と Query のガイドで扱っています。無料のアイテムサイズ計算ツールは、実際のアイテムを、上記の上限が測られている単位である RCU/WCU の数値に変換します。
落とし穴と次のステップ
- リトライは過負荷を増幅します。 SDK の呼び出しを独自のリトライループで包まないでください。まさに苦しんでいるそのパーティションへの圧力が何倍にもなります。代わりに SDK の最大試行回数を調整するか、1つのテーブルだけを扱うクライアントなら、スロットリングの後に送信を遅らせるアダプティブリトライモード (
AWS_RETRY_MODE=adaptive) に切り替えましょう。 - バッチは部分的なスロットリングを隠します。
BatchWriteItemは、どれか1つでも成功している限り、例外を投げずに未処理のアイテムを返します。UnprocessedItems(BatchGetItemではUnprocessedKeys) を指数バックオフ付きで再送するか、boto3 のbatch_writerのようにそれを代行するヘルパーを使いましょう。 - テーブルレベルのメトリクスは GSI のスロットリングを見逃します。 スロットリングイベントはインデックスごとに図示しましょう。バックプレッシャーの最中でも、ベーステーブルのダッシュボードはきれいに見えます。
- キャパシティの対処ではホットキーは動きません。 オートスケーリングもクォータの引き上げも即座には効かず、そもそもホットキーはどのキャパシティモードでもスロットリングします。ランダムなサフィックスか計算したサフィックスでシャーディングし、シャードを並列に読み戻しましょう。やり方はホットパーティションにあります。パーティションキーの仕組みも参照してください。
DynoTable をダウンロードして、Workbench や PartiQL のクエリが Scan と Query のどちらとして実行されるか、そしてプランが許す場合はその推定読み取りコストを、キャパシティに対して実行される前に確認しましょう。