DynamoDB の GSI と LSI
グローバルセカンダリインデックス(GSI)もローカルセカンダリインデックス(LSI)も、テーブルのキーではない属性で Query できるようにします。両者は交換可能ではありません — その違いが、どのパターンにどちらが必要かを決めます。
DynamoDB の GSI と LSI の違いは何ですか?
グローバルセカンダリインデックスは 任意のトップレベルスカラー 属性(String、Number、Binary)をパーティションキーとして使え、独自のキャパシティを持ち、いつでも追加できます — ただし結果整合性のある読み取りしか提供しません。ローカルセカンダリインデックスはテーブルと 同じ パーティションキーを異なるソートキーで保ち、強い整合性のある読み取りをサポートし、テーブルのキャパシティを共有しますが、テーブルと同時に作成する必要があります。
効いてくる違い
| GSI | LSI | |
|---|---|---|
| パーティションキー | 任意の スカラー (S/N/B) | テーブルと 同じ |
| ソートキー | 任意のスカラー (S/N/B) | 任意のスカラー (S/N/B) |
| 作成のタイミング | いつでも | テーブル作成時のみ |
| 整合性 | 結果整合性のみ | 強い整合性が利用可能 |
| キャパシティ | 独自 | テーブルと共有 |
| 書き込み伝播 | 非同期(結果整合性) | 同期(アトミック) |
| テーブルあたり最大 | 20(デフォルト、引き上げ可) | 5(ハード上限) |
| 10 GB パーティション上限 | なし | あり(PK ごと) |
経験則
- 異なるパーティションキー が必要(例: 注文を
customerではなくstatusで引く)? なら GSI が必要です — LSI は再パーティションできません。 - 同じパーティション内での 2 つ目のソート順 が必要 — LSI はテーブルの正確なパーティションキーを保ち、異なる ソート キーだけを差し替えます — 前もって決めておき、強い整合性のある読み取りが欲しい? なら LSI が合います。
選択は 1 つの問いに収束します — どのキーを変えるのか、です。
異なるパーティションキーは GSI を強制します。同じパーティションで異なるソートキー、これが LSI の合う唯一のケースです。
実際には、ほとんどのチームがほぼ GSI ばかりに手を伸ばします。後から追加でき、独立してスケールし、10 GB のパーティションあたり制限を受けないからです。単一の GSI のキーをオーバーロードして複数のパターンに使いましょう — シングルテーブル設計を参照してください。
Scan を潰すために GSI を追加するなら、それが 独自 の読み取り/書き込みキャパシティを持つことを忘れないでください。その追加コストは料金計算ツールで見積もり、コミットする前に DynoTable を試してインデックスの射影された属性を調べてください。
実例:優先度順のオープンチケット
サポートデスクを運営しているとします。チケットは PK = TEAM#7、SK = TICKET#8842 の下にあります。2 つのアクセスパターンが競合します。
- ID で 1 件のチケットを取得 — ベーステーブルのキーに対する
GetItem。 - チームのオープンチケットを優先度の高い順に一覧 — 作成時刻とは異なるソート順が必要です。
パターン 2 は SK だけのベーステーブル Query にはできません。ソートキーはステータスではなくチケット ID だからです。選択肢はこうです。
| アプローチ | キー | 一覧の整合性 | 合う場面 |
|---|---|---|---|
GSI1PK = TEAM#7、GSI1SK = STATUS#open#P#1#TICKET#8842 の GSI | 新しいパーティション + ソート | 結果整合性 | ステータスが頻繁に変わる。ローンチ後にインデックスを追加する |
同じ PK で LSI1SK = STATUS#open#P#1#... の LSI | 同じパーティション、新しいソート | 強い整合性 | テーブル作成時にインデックスを計画済み。ステータスの読み取りが新鮮でなければならない |
ベースの Query でフィルタ | ベースのキーのみ | 強い整合性 | 小さなパーティションのみ — フィルタはパーティション全体を課金する |
エージェントが数秒おきに更新する忙しいキューなら、GSI の結果整合性はたいてい許容できます。API が返る前にステータス変更を反映しなければならない金融の元帳の行なら、読み取りはベーステーブルか LSI から提供しましょう。
書き込み増幅を数字で
ベーステーブルへの書き込みは、変更された属性を射影しているすべてのセカンダリインデックスに伝播します。GSI 2 つと LSI 1 つを持つテーブルで、ステータスと優先度に触れるチケット更新は、ベースの書き込みに加えて 4 回のインデックス書き込みに広がりえます。
us-east-1 のオンデマンド課金では、1 KB のアイテム書き込みは宛先ごとに 1 WCU です。そのアイテムの 1 属性を更新して GSI が 1 つあれば、リクエスト合計におよそ 1 WCU が上乗せされます。インデックスを追加する前に月額の項目を知りたいなら、料金計算ツールがアイテムサイズとリクエストレートを受け付けます。
LSI はテーブルのスループットプールを共有します。オーバーロードされたパーティション上でホットな LSI ソートキーがあると、ベースのパーティションキーの分散が健全に見えていてもベーステーブルの書き込みがスロットリングされることがあります — GSI は別のキャパシティという代償と引き換えにそのリスクを分離します。
10 GB の LSI パーティション上限
LSI はベーステーブルのパーティションキーと同じ物理パーティションの内側に存在します。AWS は LSI のデータを含むアイテムコレクションについて パーティションあたり 10 GB の制限を文書化しています。1 つの TEAM#7 パーティションの下に数百万のチケットを抱えるテナントは、LSI のないベーステーブルが書類上は問題なく見えていてもその上限にぶつかりえます。
GSI は独自のキーで再パーティションするので、1 つの顧客パーティションが LSI のペイロード全体を背負うことはありません。技術的には LSI でもその読み取りを提供できる場合ですら、ほとんどの本番スキーマが GSI をデフォルトにしているのは、この再パーティションのためです。
設計時の選び方
| 問い | GSI 寄り | LSI 寄り |
|---|---|---|
| 異なるパーティションキーが必要か? | はい | いいえ — LSI にはできない |
| 読み取りは強い整合性でなければならないか? | いいえ — GSI は結果整合性 | はい |
| テーブル作成後にインデックスを追加するか? | はい | いいえ — テーブル作成時のみ |
| 関連アイテムでパーティションが ~10 GB を超えうるか? | はい — GSI は分散させる | LSI では危険 |
| 書き込みスケールを分離したいか? | はい | いいえ — テーブルの WCU を共有 |
複数のアクセスパターンを 1 つの GSI にオーバーロードするときは、まずシングルテーブル設計ツールでキーのテンプレートを描いてみましょう — どの読み取りがインデックスを共有するかを示し、バックフィルに費用を払う前にサポートされないパターンを警告してくれます。
コミットする前にインデックスを調べる
DynoTable では、テーブルのインデックスピッカーを開いて、同じフィルタをベーステーブルと各 GSI に対して並べて実行できます。射影された属性がすぐに分かり — GSI の結果に無い属性は射影に含まれていません — 結果整合性のある GSI の読み取りが UI にとって十分新鮮かどうかを確認できます。
候補となる GSI の KeyConditionExpression は DynamoDB 式ビルダーで構築し、それからクエリビルダーでページネーション込みの完全なプログラムを出力して、ステージングのテストに貼り付けましょう。
関連する読み物
- GSI の結果整合性のある読み取り — 書き込んだばかりのアイテムが、なぜ一瞬 GSI のクエリから抜け落ちるのか。
- インデックスの射影 — 各インデックスが物理的に何を保存するか。
- スパースインデックス — マーカー属性を持つアイテムだけをインデックスして GSI を小さく保つ。