中級読了 3 分

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 であり、節約しようとしていた レイテンシとコストがそっくり返ってきます。射影がアクセスパターンに対して薄すぎたのです。

各射影タイプが何をコピーするか

ベースアイテム: キー + subject +assignee + age + bodyKEYS_ONLY: キーのみINCLUDE: キー + subject,assignee, ageALL: すべての属性
  • KEYS_ONLY はベーステーブルのキー インデックスキーだけを格納します。クエリが どの アイテムがマッチするかだけを知る必要があり、詳細は別の場所で取得する — または まったく取得しない — 場合に使います。
  • INCLUDE はキーに加えて、あなたが名前で指定した固定リストの属性を格納します。 スイートスポットは、クエリの描画にちょうど必要なフィールドだけを射影し、それ以上は 射影しないことです。
  • ALL はアイテム全体をコピーします。クエリはインデックスだけで完結しますが、 アイテム全体のストレージと書き込みスループットをそこへ複製する代償を払います。

サポートデスクのキューでは、subjectassigneeage を含む INCLUDE が正解です — キューはインデックスだけで描画され、2回目の取得は不要で、チケットの大きな body を インデックスに複製することもありません。

あなたがトレードしているコスト

射影する属性はすべて 2度目に格納されベースアイテムが変わるたびにインデックスで書き直されます。したがって、頻繁に更新される テーブルへの潤沢な ALL 射影は、ストレージと書き込みキャパシティの両方を倍増させます。 「念のため全部」ではなく、クエリが 読む ものを射影しましょう。

知っておく価値のある細かい点: スパース インデックスでは、射影はインデックスキーを 持つアイテムだけを保持します — なので スパースインデックス 上の INCLUDE/ALL は、 インデックス自体が小さいために小さいままです。射影のストレージと書き込みの倍率は、 DynamoDB 料金計算ツール で比較検討し、インデックスへの クエリ自体は DynamoDB Expression Builder で 組み立てましょう。

DynoTable で射影を見る

DynoTable は、テーブルの各セカンダリインデックスを一覧し、そのうちの1つを通して直接クエリ できます。同じアクセスパターンをベーステーブルに対してと GSI に対して実行し、結果を 比較してみてください — インデックスの結果に欠けている属性が、まさにそれが射影していない 属性です。だからテーブル定義を読み直さなくても、射影の効果が目に見えます。

DynoTable のインデックス選択で、クエリをどの DynamoDB インデックス経由で実行するかを選ぶ。
DynoTable のインデックス選択で、クエリをどの DynamoDB インデックス経由で実行するかを選ぶ。

落とし穴と次のステップ

  • GSI 上の射影されていない属性はベーステーブルへの取得を意味します — クエリが描画する ものを中心に射影を設計しましょう。
  • ALL はめったに無料ではありません — ストレージと書き込みコストを複製します。 インデックスが本当にすべてのフィールドを必要とするのでない限り、INCLUDE を既定に しましょう。
  • 射影はほぼ固定です。 インデックスを作り直さずに GSI の射影を後から自由に編集する ことはできません — 最初に意図をもって選びましょう。
  • 関連: GSI と LSIスパースインデックス が、射影が実際にどれだけ 格納するかを左右します。

再設計の前に、各インデックスが実際に何を返すか見てみたいですか? DynoTable をダウンロード して、テーブルに直接クエリしましょう。

ハイドレーションコスト: KEYS_ONLY + N 回の get

サポートデスクのキュー例に戻ります。未解決チケット50件を、件名・担当者・経過時間付きで表示します。

射影インデックスクエリフォローアップ読み取りEC RCU の目安(ベース 2 KB)
KEYS_ONLY50キーを返す50 × GetItem約50 インデックス RCU + 約50 ベース RCU
INCLUDE subject, assignee, age自己完結の50行なし約50 インデックス RCU のみ
ALL50件のフルコピーなし約50 インデックス RCU。ストレージと書き込み増幅は高い

正確な数字は射影属性のサイズ次第です — サンプルチケットを アイテムサイズ計算機 に貼り、キュー深さで掛けてください。リストビューでめったに出さない大きな body 属性があるなら、UI フィールドだけを列挙した INCLUDEALL に勝つことがよくあります。

LSI の射影フェッチ動作

LSI だけが、クエリ中に射影されていない属性をベーステーブルから任意で取得できます(追加の読み取りコスト付き)。GSI はこれを決してしません — 欠けた属性はアプリがベーステーブルへ GetItem する必要があります。その差が、多くの GSI 設計を最初から少し広めの INCLUDE 射影へ押しやります。

あとから射影を変える

GSI の射影は作成時に固定です。KEYS_ONLYINCLUDE に広げるには、新しいインデックスを作り、バックフィルし、トラフィックを切り替え、古いインデックスを消す必要があります — 起動前にフィールドを計画してください。LSI も同じ制限を共有します。

新しいアクセスパターンを評価するときは、DynoTable で候補インデックスをクエリし、どの属性が出るかを一覧にしてください — 欠けは射影エントリの欠落と1対1で対応します。

スパースインデックスと組み合わせる

status = open のチケットだけを索引するスパース GSI は、オープン行の射影だけを保持します。ベーステーブルにクローズ済みが何百万件あっても、そのインデックス上の INCLUDE は安いままです — インデックスはそれらをコピーしていません。

フィルタ後の部分集合がテーブルに対して小さいときは、スパースインデックスのパターン と組み合わせてください。

まずアクセスパターンを組み立てる

CloudFormation を触る前に、クエリビルダー で GSI クエリ — キー条件、射影式、フィルタ — を試作してください。設計議論では UI が描画する列を問い、それ以外はベーステーブルに残す、という形で射影タイプを入れ替えます。

更新日