DynamoDB のインデックス射影
セカンダリインデックスを作成しても、DynamoDB はアイテム全体を自動的にそこへ コピーしません。何を コピーするか — インデックスの 射影 — をあなたが選びます。少なく 選びすぎるとクエリは残りを取得するために2回目の読み取りを支払い、すべてを選ぶと更新の たびに余分なストレージと書き込みコストを支払います。インデックス作成時に一度だけ設定し、 以後それと付き合うトレードオフです。
(これを 射影式(projection expression) と混同しないでください。射影式は、単一の 読み取りが 返す 属性を絞るものです。このページは インデックスが物理的に格納する ものに 関するものです — もう一方については 射影式 を参照してください。)
DynamoDB のインデックス射影とは?
射影とは、DynamoDB がベーステーブルからセカンダリインデックスへコピーする属性の集合です。
3つのタイプのいずれかを選びます。KEYS_ONLY(キーのみ)、INCLUDE(キーに加えて名前で
指定した属性のリスト)、ALL(アイテム全体)です。射影が多いほどベーステーブルへの取得は
減りますが、ストレージと書き込みコストは高くなります。
- 射影とは、セカンダリインデックスへ コピーされる 属性の集合です。
KEYS_ONLY— テーブルとインデックスのキーのみ。最小・最安。INCLUDE— キーに加えて、あなたが選んだ追加属性の名前付きリスト。ALL— アイテムのすべての属性。最大。クエリがベーステーブルを必要とすることは 決してありません。- 射影されていない属性は、GSI からは単に利用できません — アプリが自前で ベーステーブルの読み取りを発行しなければなりません。(射影されていない属性を代わりに 取得してくれるのは LSI だけで、追加の読み取りコストがかかります。)
- 射影が多い = ストレージ増 + 書き込みコスト増 です。ベーステーブルへの書き込みは すべてインデックスに伝播するからです。
問題: あなたに2回読ませるインデックス
未対応 チケットを優先度順に一覧できる GSI を持つサポートデスクを運用しているとします。
リーンに保つため KEYS_ONLY を射影します。クエリは高速に返ってきます — が、返るのは
チケット ID だけで、キュー画面には各チケットの件名、担当者、経過時間が必要です。
そこでコードは、各結果を肉付けするためにベーステーブルに対して2回目の読み取りを行います。 設計した「1回のクエリ」は実際にはクエリ プラス N 回の Get であり、節約しようとしていた レイテンシとコストがそっくり返ってきます。射影がアクセスパターンに対して薄すぎたのです。
各射影タイプが何をコピーするか
KEYS_ONLYはベーステーブルのキー と インデックスキーだけを格納します。クエリが どの アイテムがマッチするかだけを知る必要があり、詳細は別の場所で取得する — または まったく取得しない — 場合に使います。INCLUDEはキーに加えて、あなたが名前で指定した固定リストの属性を格納します。 スイートスポットは、クエリの描画にちょうど必要なフィールドだけを射影し、それ以上は 射影しないことです。ALLはアイテム全体をコピーします。クエリはインデックスだけで完結しますが、 アイテム全体のストレージと書き込みスループットをそこへ複製する代償を払います。
サポートデスクのキューでは、subject、assignee、age を含む INCLUDE が正解です —
キューはインデックスだけで描画され、2回目の取得は不要で、チケットの大きな body を
インデックスに複製することもありません。
あなたがトレードしているコスト
射影する属性はすべて
2度目に格納され、
ベースアイテムが変わるたびにインデックスで書き直されます。したがって、頻繁に更新される
テーブルへの潤沢な ALL 射影は、ストレージと書き込みキャパシティの両方を倍増させます。
「念のため全部」ではなく、クエリが 読む ものを射影しましょう。
知っておく価値のある細かい点: スパース インデックスでは、射影はインデックスキーを
持つアイテムだけを保持します — なので
スパースインデックス 上の INCLUDE/ALL は、
インデックス自体が小さいために小さいままです。射影のストレージと書き込みの倍率は、
DynamoDB 料金計算ツール で比較検討し、インデックスへの
クエリ自体は DynamoDB Expression Builder で
組み立てましょう。
DynoTable で射影を見る
DynoTable は、テーブルの各セカンダリインデックスを一覧し、そのうちの1つを通して直接クエリ できます。同じアクセスパターンをベーステーブルに対してと GSI に対して実行し、結果を 比較してみてください — インデックスの結果に欠けている属性が、まさにそれが射影していない 属性です。だからテーブル定義を読み直さなくても、射影の効果が目に見えます。

落とし穴と次のステップ
- GSI 上の射影されていない属性はベーステーブルへの取得を意味します — クエリが描画する ものを中心に射影を設計しましょう。
ALLはめったに無料ではありません — ストレージと書き込みコストを複製します。 インデックスが本当にすべてのフィールドを必要とするのでない限り、INCLUDEを既定に しましょう。- 射影はほぼ固定です。 インデックスを作り直さずに GSI の射影を後から自由に編集する ことはできません — 最初に意図をもって選びましょう。
- 関連: GSI と LSI と スパースインデックス が、射影が実際にどれだけ 格納するかを左右します。
再設計の前に、各インデックスが実際に何を返すか見てみたいですか? DynoTable をダウンロード して、テーブルに直接クエリしましょう。
ハイドレーションコスト: KEYS_ONLY + N 回の get
サポートデスクのキュー例に戻ります。未解決チケット50件を、件名・担当者・経過時間付きで表示します。
| 射影 | インデックスクエリ | フォローアップ読み取り | EC RCU の目安(ベース 2 KB) |
|---|---|---|---|
KEYS_ONLY | 50キーを返す | 50 × GetItem | 約50 インデックス RCU + 約50 ベース RCU |
INCLUDE subject, assignee, age | 自己完結の50行 | なし | 約50 インデックス RCU のみ |
ALL | 50件のフルコピー | なし | 約50 インデックス RCU。ストレージと書き込み増幅は高い |
正確な数字は射影属性のサイズ次第です — サンプルチケットを アイテムサイズ計算機 に貼り、キュー深さで掛けてください。リストビューでめったに出さない大きな body 属性があるなら、UI フィールドだけを列挙した INCLUDE が ALL に勝つことがよくあります。
LSI の射影フェッチ動作
LSI だけが、クエリ中に射影されていない属性をベーステーブルから任意で取得できます(追加の読み取りコスト付き)。GSI はこれを決してしません — 欠けた属性はアプリがベーステーブルへ GetItem する必要があります。その差が、多くの GSI 設計を最初から少し広めの INCLUDE 射影へ押しやります。
あとから射影を変える
GSI の射影は作成時に固定です。KEYS_ONLY を INCLUDE に広げるには、新しいインデックスを作り、バックフィルし、トラフィックを切り替え、古いインデックスを消す必要があります — 起動前にフィールドを計画してください。LSI も同じ制限を共有します。
新しいアクセスパターンを評価するときは、DynoTable で候補インデックスをクエリし、どの属性が出るかを一覧にしてください — 欠けは射影エントリの欠落と1対1で対応します。
スパースインデックスと組み合わせる
status = open のチケットだけを索引するスパース GSI は、オープン行の射影だけを保持します。ベーステーブルにクローズ済みが何百万件あっても、そのインデックス上の INCLUDE は安いままです — インデックスはそれらをコピーしていません。
フィルタ後の部分集合がテーブルに対して小さいときは、スパースインデックスのパターン と組み合わせてください。
まずアクセスパターンを組み立てる
CloudFormation を触る前に、クエリビルダー で GSI クエリ — キー条件、射影式、フィルタ — を試作してください。設計議論では UI が描画する列を問い、それ以外はベーステーブルに残す、という形で射影タイプを入れ替えます。


