上級読了 4 分

DynamoDB の GSI は内部的にどう保存されるか

は、テーブルへ戻るポインタではありません。それは 別個の、内部で管理されるテーブル — 独自のパーティション、独自のキースキーマ、独自の キャパシティ — で、DynamoDB が書き込みをそこへ非同期にコピーすることで同期に保ちます。

SQL から来ると、インデックスは同じ物理テーブルにボルト留めされた B ツリーで、同じ トランザクション内で更新されます。GSI はその両方の前提を破り、ほとんどすべての GSI の 驚きは、その 1 つの事実にたどり着きます。

DynamoDB の GSI はどう保存されるのか?

DynamoDB の GSI は、ベーステーブルへのポインタとしてではなく、別個の内部で管理されるテーブル — 独自のパーティション、キースキーマ、キャパシティ — として保存されます。DynamoDB は各書き込みをインデックスへ非同期にコピーし、GSI キー、ベーステーブルのキー、そして 属性だけを保存します。

  • GSI はそれ自身のテーブル。 ベーステーブルのではなく、GSI のパーティションキーで キー付けされた、完全に独立したパーティション空間を持ちます。
  • 書き込みは非同期に複製される。 あなたの書き込みはまずベーステーブルにコミットされ、 その後 DynamoDB がバックグラウンドのパスで各 GSI へファンアウトします。
  • 射影された属性だけが保存される。 インデックスは GSI キー、ベースキー、そしてあなたが 射影した属性だけを保持します — それ以外は何も。
  • GSI キーは一意である必要はない。 複数のベースアイテムが 1 つの GSI パーティション/ ソートキーを共有できます。ベースの が、それらを区別に保つ タイブレーカーです。

1 つのベースアイテムから始める

SaaS の 監査ログ を取ります。ワークスペース内のすべての特権的なアクションが不変の イベントになります。ベーステーブル WorkspaceEvents は、あるワークスペースのすべての イベントが 1 つの に、時刻順で存在するようにキー付け されています。

WorkspaceEvents (base table)
EventPKEventSKactorIdverbtargetRef
WS#orbit-9TS#2026-06-23T14:02:11ZUSR#kpROLE_GRANTEDUSR#mara

EventPK = "WS#orbit-9" はワークスペースでパーティション分けし、EventSK は ISO タイム スタンプなので Query はあるワークスペースのイベントを時系列順で返します。それは「この ワークスペースのタイムラインを見せて」を完璧に提供します。

それ以外は何も提供しません。「USR#kp はすべてのワークスペースで何をしたか?」は問えません — actorId はキーではないので、ベーステーブルでそれに答える唯一の方法はフルの Scan です。それこそ、GSI が追加するために存在するアクセス パターンです。

GSI を追加して 2 つ目のテーブルが現れるのを見る

同じイベントを、それを実行した者で再パーティション分けする GSI ByActor を定義します。

ByActor (GSI)
partition key = actorId   ("USR#kp")
sort key      = EventSK   ("TS#2026-06-23T14:02:11Z")

DynamoDB は今や 2 つ目の物理構造を維持します。同じ論理イベントが 2 回 保存されます — 一度はベーステーブルの WS#orbit-9 パーティションに、そしてもう一度は GSI の USR#kp パーティションに。

ByActor (GSI) — its own partition space
actorIdEventSKEventPKverb
USR#kpTS#2026-06-23T14:02:11ZWS#orbit-9ROLE_GRANTED

何が一緒に乗ってきたかに注目してください。ベーステーブルのキーEventPKEventSK)は、 すべての GSI アイテムに自動的に保存されます。それが、GSI のヒットがあなたを完全なアイテムへ 指し戻せる理由 — そして KEYS_ONLY インデックスでも ストレージがかかる理由です。

GSI に実際に何が住むのか

インデックスはアイテム全体を コピーしません。各 GSI エントリはちょうど 3 つのものを 保持し、あなたが制御するのは 3 つ目だけです。

GSI に保存されるものどこから来るか任意?
GSI パーティション + ソートキーGSI キーとして名付けた属性いいえ
ベーステーブルのキーすべてのベースアイテムからコピーいいえ
射影された属性あなたの Projection の選択はい

ProjectionKEYS_ONLYINCLUDE(名前付きリスト)、または ALL です。GSI に対する Query は、インデックス内にある属性しか返せません。

射影されていないものを求めると、DynamoDB はそれを透過的に取得 しません — そのフィールド については何も返ってきません。 (AWS GSI ドキュメント

それはリレーショナルの罠が逆になったものです。SQL なら欠けたカラムのためにヒープへ結合し 戻します。GSI は決してそうしません。 が契約のすべてです。

書き込みがインデックスに届く仕組み

レプリケーションが、SQL の直感を最も強く破る部分です。ベースの書き込みとそのインデックス 更新は、1 つのアトミックな操作ではありません

PutItem すると、DynamoDB はベーステーブルに耐久的にコミットし、あなたの書き込みを承認し、 その後 その変更を各 GSI を更新するバックグラウンドのパスへ伝播します。承認はインデックスを 待ちません。

監査書き込みの、上から下への順序はこうです。

PutItemWS#orbit-9 イベントベースパーティションにコミット200 OKを呼び出し元へ非同期パス:GSI キーを抽出ByActor パーティションUSR#kp へルート射影された属性を書き込む

呼び出し元はステップ 3 で 200 OK を受け取ります — ステップ 4 から 6 が終わる前に — なので そのギャップ中に ByActor に対する Query は、真新しいイベントを見逃しうります。

その非同期性は設計によるものです。2007 年の Amazon Dynamo の論文 の系譜から来ており、同期的な整合性より可用性を選びました。完全な帰結は なぜ GSI は結果整合性なのか にあります。

GSI キーは一意キーではない

SQL では、非一意のセカンダリインデックスがデフォルトで、一意なものはオプトインする制約です。 GSI は逆です。一意性の保証を 決して 持ちません。

同じアクターからの、衝突するタイムスタンプの 2 つの監査イベントは、同じ GSI1PK かつ GSI1SK を共有します。DynamoDB は両方を保存し — 常に一緒に運ばれるベーステーブルのプライマリ キーで内部的に区別します。

だから 1 人のアクターの 1 つの瞬間に対する GSI Query は、正当に複数のアイテムを返しうります。 SQL の一意インデックスが与えるようにキーごとに 1 行と仮定していたなら、それが地雷です。

インデックスをクエリするとき、DynamoDB 式ビルダー は 名前と値を正しくエスケープして KeyConditionExpression を書きます — 例えば、あるカット オフ以降の 1 人のアクターに一致させる場合。

KeyConditionExpression: "#a = :actor AND #ts > :since"
ExpressionAttributeNames:  { "#a": "actorId", "#ts": "EventSK" }
ExpressionAttributeValues: {
  ":actor": { "S": "USR#kp" },
  ":since": { "S": "TS#2026-06-01T00:00:00Z" }
}

キャパシティはテーブルではなくインデックスにある

GSI はそれ自身のテーブルなので、ベーステーブルとは別に課金されスロットルされる、独自の 読み取りと書き込みのキャパシティを持ちます。ByActor からの読み取りは GSI の読み取りユニットを 消費し、テーブルのものは決して消費しません。

逆向きの結合こそが噛みつく部分です。すべてのベーステーブルの書き込みはインデックスにも書き込み、 GSI がそれを吸収できないと、ベースの書き込みにバックプレッシャーをかけます。その仕組みは それ自身のガイドを持っています — GSI がベーステーブルの書き込みをスロットルするとき

これはまた、GSI のパーティションキーがベーステーブルのそれと同じくらい重要な理由でもあります。 低カーディナリティの GSI キーは、ベースの書き込みが完璧に分散していても、書き込みを 1 つの インデックスパーティションに固めます — あなたが再キー付けによって作り出したホット パーティションです。

GSI の書き込み増幅(課金)

GSI に射影するすべてのベーステーブル書き込みは、us-east-1 オンデマンドで ベース WCU + インデックス WCU がかかります。ALL 射影の 1 KB アイテムは通常合計 約 2 WCU — テーブル行に1、インデックスコピーに1 — を課金します。KEYS_ONLY はインデックス書き込みを小さくし、ALL はストレージと書き込み増幅を倍にします。アイテムサイズと射影は 料金計算機 でモデルしてください。

落とし穴と次のステップ

  • 射影されていない属性が返ってくると期待しない。 GSI Query はインデックスが保存する ものだけを返します。完全なアイテムが必要なら、それを射影するか、一緒に運ばれるキーで ベーステーブルから取得しましょう。
  • GSI キーを一意として扱わない。 Query がキーごとに複数のアイテムを返すことを想定 しましょう。ベースのプライマリキーだけが唯一の本当のアイデンティティです。
  • それに供給した書き込みの直後に GSI を読まない。 非同期パスは、インデックスがまだ あなたの書き込みを表示していないことを意味します — read-your-own-writes が必要なときは ベーステーブルを読みましょう。
  • GSI のキャパシティを意図的にサイジングする。 それは読み取りでは独立し、書き込みでは 隠れた依存関係です。

すべての勝負どころは、あなたのパターンを提供するキーの形を選ぶことです — シングルテーブル設計 は 1 つの GSI を多数のパターンにまたいで オーバーロードします。GSI と LSI は、代わりにローカルインデックスが 合うのはいつかを扱います。

GSI の KeyConditionExpressionDynamoDB 式ビルダー で組み立ててプレビューし、それから DynoTable を試して インデックスの射影された 属性を調べ、書き込みが自分のテーブルで GSI に複製される様子を見てみましょう。

更新日