DynamoDB で GSI がベーステーブルの書き込みをスロットリングする理由
テーブルに書き込みます。書き込みがスループット例外で失敗します。しかし、その例外が名指しするのはテーブルではなくグローバルセカンダリインデックス (GSI) です。テーブルには余裕があります。
SQL の出身者にはナンセンスに見えます。セカンダリインデックスが INSERT をブロックできるはずがないからです。DynamoDB ではそれが起こり得て、その仕組みは GSI のバックプレッシャーと呼ばれます。
なぜ DynamoDB の GSI はベーステーブルの書き込みをスロットリングするのか?
DynamoDB がベーステーブルの書き込みをスロットリングするのは、すべての書き込みが各 GSI にも複製されるためです。GSI のパーティションが自分の取り分を吸収できないと、DynamoDB はインデックスが永久に遅れを取らないようバックプレッシャーを適用して、書き込みを止めます。つまり、プロビジョニング不足や低カーディナリティの GSI キーは、ベーステーブルの書き込みレートの厳格な上限になるのです。
- ベーステーブルへの書き込みは、すべての GSI への書き込みでもある。 GSI が自分の取り分を吸収できない場合、DynamoDB はインデックスが永久に遅れを取らないよう、ベーステーブルの書き込みをスロットリングします。(AWS ドキュメント)
- ベーステーブルが均等でも救われない。 GSI は独自のキーでパーティション化されます。低カーディナリティの GSI キー(
statusなど)は、ベーステーブルの書き込みが完璧に分散されていても、ホットなインデックスパーティションを作ります。 - 例外は被害者について嘘をつく。
ResourceArnは GSI を指しますが、実際にスロットリングされている操作はテーブルへの書き込みです。 - 修正はキャパシティかキー設計であって、リトライループではありません。GSI のスループットを上げるか、分散する GSI パーティションキーを選びましょう。
単一の書き込みがどうインデックスに触れるか
ベーステーブルへの PutItem は1つの書き込みではありません。DynamoDB はアイテムの投影された属性を各 GSI に、結果整合性のモデルで非同期に複製します。1つの論理的な書き込みが N 個の物理的な書き込み(テーブル+すべてのインデックス)にファンアウトします。
その複製は無料でも任意でもありません。GSI は追いつかなければならず、さもなければインデックスは操作のたびにテーブルからさらにずれていきます。
そのずれを止めるため、DynamoDB はバックプレッシャーを適用します。インデックスが際限なく古くならないよう、ソースの書き込みをスロットリングするのです。
つまり、GSI の書き込みキャパシティは、たとえあなたが GSI に直接書き込まないとしても、ベーステーブルの書き込みレートの厳格な上限になります。
実例: 注文テーブル
注文テーブルを運用しているとします。ベースアイテム:
| field | value | note |
|---|---|---|
| PK | "CUST#8841" | partition key |
| SK | "ORD#2026-06-23#A7" | sort key |
| order_state | "PROCESSING" | |
| warehouse | "EU-MAD-2" | |
| total_cents | 4990 |
ベーステーブルの書き込みは健全です。CUST#... は高カーディナリティなので、注文の書き込みはベースパーティション全体に均等に分散します。ホットキーはなく、キャパシティも十分です。
ここで「指定された状態のすべての注文を表示する」に答えるための GSI を追加します:
| field | value | note |
|---|---|---|
| GSI-PK | order_state | "PENDING" | "PROCESSING" | "SHIPPED" | "CANCELED" |
| GSI-SK | SK |
パーティションキーの値は4種類しかありません。フラッシュセール中、ほとんどすべての新規注文が order_state = "PENDING" に着地します。それらの書き込みのすべてが同じ GSI パーティションに当たります。
そのパーティションにはパーティション単位のスループット上限があり、あなたは書き込みの嵐をすべてそこに向けてしまったのです。
ベーステーブルは無事です。PENDING の GSI パーティションが炎上しています。DynamoDB はインデックスを守るためにベーステーブルの PutItem をスロットリングします。
あなたを噛むフロー
バックプレッシャーのパスはこうです。ベースの書き込みは均衡し、インデックスの書き込みが集中します:
スロットリングは逆方向に伝わります。ホットな GSI パーティションが、それを供給したベーステーブルの書き込みを拒絶するのです。
勘ではなく例外を読む
例外の種類が、どの上限に達したかを正確に教えてくれます。ResourceArn は GSI を名指ししますが、スロットリングされた操作は依然としてテーブルへの書き込みです。
| モード | 理由コード | 何が枯渇したか |
|---|---|---|
| プロビジョンド | IndexWriteProvisionedThroughputExceeded | GSI のプロビジョンド書き込みキャパシティ |
| プロビジョンド | IndexWriteKeyRangeThroughputExceeded | 単一のホットな GSI パーティション |
| オンデマンド | IndexWriteMaxOnDemandThroughputExceeded | GSI に設定されたオンデマンドの最大上限 |
| オンデマンド | IndexWriteAccountLimitExceeded | アカウント/リージョンのスループット境界 |
出典: GSI の書き込みスロットリングとバックプレッシャーの理解。
KeyRange の理由は、上記のホットパーティションのケースを見分ける手がかりです。GSI 全体のキャパシティは問題なく見えても、1つのキー範囲が飽和していることがあります。
修正方法
GSI に余裕を与える。 最も単純な原因はプロビジョニング不足です。GSI はテーブルとは完全に分離された独自の読み取り・書き込みキャパシティを持ちます — GSI と LSI の比較を参照してください。
テーブルを潤沢にプロビジョニングして GSI を薄いままにしたなら、GSI の書き込みキャパシティ(またはそのオンデマンド最大値)を上げましょう。
パーティションキーを直す。 キャパシティでは低カーディナリティのキーを救えません — 単一のホットパーティションをプロビジョニングで打ち負かすことはできないからです。分散する GSI パーティションキーを選びましょう。
それを組み立てましょう: order_state#shard(shard は小さなランダムサフィックス)か、日付を折り込む(PENDING#2026-06-23)。書き込みはパーティション全体に分散し、シャードに対して Query することで状態を引き続き照会できます。
投影する属性を減らす。 各 GSI への書き込みは投影された属性をコピーします。KEYS_ONLY か絞った INCLUDE の投影は、ALL よりも小さなインデックス書き込みと低い圧力を意味します。インデックスから読み取らないものは投影しないこと。
レポート用途だけなら GSI を削除する。 「状態ごとの注文」がホットパスではなく、たまの管理者向けの問いなら、フィルタ付きの定期的なスキャンのほうが、常時ホットなインデックスに勝るかもしれません — Query と Scan の比較と天秤にかけましょう。
そのインデックスを照会するときは、Expression Builder が KeyConditionExpression を書いてくれます。たとえば #s = :state AND begins_with(SK, :prefix) のように、名前と値を正しくエスケープして:
KeyConditionExpression "#s = :state"
ExpressionAttributeNames { "#s": "order_state" }
ExpressionAttributeValues { ":state": { "S": "PENDING" } }
覚えておくべき罠
リレーショナルの直感 — 「インデックスは書き込みを少し遅くするだけ」 — は通用しません。DynamoDB の GSI は受け身の構造ではなく、スループット上の依存関係です。サイズを小さくしたり、固まるキーを選んだりすると、それが奉仕するテーブルにバックプレッシャーをかけます。
テーブルのものだけでなく、GSI の ConsumedWriteCapacityUnits とパーティション単位の ThrottledRequests を監視しましょう。
次のステップ
- GSI と LSI の比較 — なぜ GSI は独自のキャパシティと別のパーティションキーを持つのか。
- シングルテーブル設計 — ホットなインデックスを増やさずに1つの GSI をオーバーロードして多くのパターンに奉仕する方法。
- Query と Scan の比較 — インデックスがその書き込みコストに見合わないとき。
DynoTable を試して、セールがテーブルを赤くする前に、自分のテーブルに対して GSI の消費キャパシティとスロットリングイベントを観察しましょう。