DynamoDB で GSI がベーステーブルの書き込みをスロットルする理由
テーブルに書き込みます。その書き込みがスループット例外で失敗します — ただし、その 例外が名指しするのはテーブルではなく グローバルセカンダリインデックス です。 テーブルにはキャパシティの余裕があります。
SQL から来ると、これは意味不明です。セカンダリインデックスが INSERT をブロックする
はずがありません。ところが DynamoDB ではそれが起こり、その仕組みは GSI バックプレッシャー
と呼ばれます。
なぜ DynamoDB の GSI はベーステーブルの書き込みをスロットルするのか?
DynamoDB がベーステーブルの書き込みをスロットルするのは、すべての書き込みが各 GSI にも 複製されるからです。ある GSI パーティションが自分の分を吸収できないと、DynamoDB は インデックスが永続的に遅れをとらないようバックプレッシャーをかけます。その結果、 プロビジョニング不足だったりカーディナリティが低かったりする GSI キーが、ベーステーブルの 書き込みレートに対するハードな上限になります。
- ベーステーブルへの書き込みは、すべての GSI にも書き込みます。 GSI が自分の分を 吸収できないと、DynamoDB はインデックスが永続的に遅れをとらないよう、ベーステーブルの 書き込みをスロットルします。(AWS ドキュメント)
- ベーステーブルが均等でも救われません。 GSI は自身のキーでパーティション分割されます。
カーディナリティの低い GSI キー(
statusのような)は、ベーステーブルへの書き込みが 完璧に分散していても を生みます。 - 例外は被害者について嘘をつきます。
ResourceArnは GSI を指しますが、実際に スロットルされているのはテーブルへの書き込みです。 - 直し方はキャパシティかキー設計であって、リトライループではありません — GSI の スループットを上げるか、分散する GSI の を選びます。
1回の書き込みがどうインデックスに波及するか
ベーステーブルへの 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 | アカウント/リージョンのスループット境界 |
出典: Understanding GSI write throttling and back pressure。
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 AND begins_with(SK, :prefix)"
ExpressionAttributeNames { "#s": "order_state" }
ExpressionAttributeValues { ":state": { "S": "PENDING" }, ":prefix": { "S": "ORD#2026-06-23" } }
覚えておくべき罠
「インデックスは書き込みを少し遅くするだけ」というリレーショナルの直感は、ここでは 通用しません。DynamoDB の GSI は受動的な構造ではなく、スループットの依存関係です。 サイズを小さくしたり、まとまってしまうキーを選んだりすると、GSI はそれが支える テーブルにバックプレッシャーをかけます。
テーブルだけでなく GSI のディメンションで ConsumedWriteCapacityUnits と
WriteThrottleEvents を監視し、Contributor Insights を使ってホットキーを見つけましょう。
次のステップ
- GSI と LSI — なぜ GSI が独自のキャパシティと異なる パーティションキーを持つのか。
- シングルテーブル設計 — ホットなインデックスを増やさずに、 1つの GSI をオーバーロードして多くのパターンをさばく。
- Query と Scan — インデックスがその書き込みコストに見合わないとき。
DynoTable を試して、テーブル上のあらゆる GSI — キースキーマとアイテム数 — を 調べ、セールがインデックスを赤く染める前にインデックスへクエリしましょう。