DynamoDBのキー・インデックスについてまとめてみた

DynamoDBには以下の単語が登場します。

- パーティションキー
- ソートキー
- プライマリキー
- ローカルセカンダリインデックス
- グローバルセカンダリインデックス

これらのキー・インデックスについて改めて整理してみました。

# キー

まず「パーティション」「データの読み書きを行うAPIの種類」について整理します。その後、キーの種類について整理します。

### パーティション

DynamoDBのデータは複数のパーティションに分散して保存されます。このときデータがどのパーティションに保存されるかは **パーティションキー** を元に決定されます。

また **ソートキー** が設定されている場合、データはパーティション内でソートキーを元に並べ替えられて物理的に近くに配置されます。

例として、`AnimalType`（パーティションキー）と`Name`（ソートキー）で構成される`Pets`テーブルのデータは以下のように分散して保存されます。

![パーティション](https://qiita-image-store.s3.amazonaws.com/0/61641/b0a32ab2-80a1-c7c0-54d7-338a04f6b156.png)

（図は[パーティションとデータ分散 - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html)より引用）

### データの読み取りを行うAPIの種類

DynamoDBにはデータの読み書きを行う以下のAPIがあります。

- データの作成
    - PutItem
        - プライマリキーを指定して単一のデータを書き込む。
    - BatchWriteItem
        - PutItemのバッチ版。
- データの読み取り
    - GetItem
        - プライマリキーを指定して単一のデータを取り出す。
    - BatchGetItem
        - GetItemのバッチ版。
    - Query
        - パーティションキーを指定して複数のデータを取り出す。ソートキーを指定して取り出すデータの範囲をフィルタすることができる。
    - Scan
        - テーブルの全てのデータを取り出す。
- データの更新
    - UpdateItem
        - プライマリキーを指定して単一のデータを更新する。
- データの削除
    - DeleteItem
        - プライマリキーを指定して単一のデータを削除する。
    - BatchWriteItem
        - DeleteItemのバッチ版。


**プライマリキー** はデータを一意に識別するためのキーで、「パーティションキー」または「パーティションキーとソートキーの複合キー」のことです。

一部のAPIを除き、基本的にデータの読み書きはプライマリキーを指定して行います。データアクセスの特徴にあわせてパーティションキーとソートキーを設計すると良いでしょう。

### キーの種類
ここまでの内容をもとに、キーの種類を整理します。

- パーティションキー
    - データをどのパーティションに配置するか決定する。
    - 各パーティションへのアクセスがなるべく均一になるようパーティションキーを設計すると良い。
- ソートキー
    - ソートキーによってデータはパーティション内で並べ替えられて物理的に近くなるように配置される。
    - `Query`APIではソートキーを指定して取り出すデータの範囲をフィルタできる。
    - ソートキーの設定は任意。
- プライマリキー
    - 「パーティションキー」または「パーティションキーとソートキーの複合キー」のこと。
    - プライマリキーによってデータは一意に識別される。

# セカンダリインデックス

オリジナルのテーブルのパーティションキー・ソートキーだけでは不十分な場合、別のパーティションキー・ソートキーを設定することができます。それが **グローバルセカンダリインデックス（GSI）** と **ローカルセカンダリインデックス（LSI）** です。

## グローバルセカンダリインデックスの例

例としてゲームのスコアを格納する`GameScores`テーブルを考えます。この例と図は[グローバルセカンダリインデックス - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html)の引用です。

![GSI_01](https://qiita-image-store.s3.amazonaws.com/0/61641/59ab585d-96d3-eb60-97c7-e7afc7cfd5de.png)

このテーブルのパーティションキーは`UserId`、ソートキーは`GameTitle`です。

ここで「各ゲームのトップスコアを持つユーザは誰か？」という問題を考えます。今のパーティションキー・ソートキーの設計では効率よくこの答えを出すことはできません。

そこで`GameTitle`をパーティションキー、`TopScore`をソートキーとする別のテーブルを作成します。また必要なのはユーザ情報のみなので、新しいテーブルは`UserId`のみを持つようにします。

![GSI_02](https://qiita-image-store.s3.amazonaws.com/0/61641/3d96f9b8-13fb-0f1d-0e05-e4b364a37748.png)

これで「各ゲームのトップスコアを持つユーザは誰か？」という問題の答えを効率よく出せるようになりました。

このように、あるテーブルをベースに、異なるパーティションキー・ソートキーのテーブルを作成する仕組みを **グローバルセカンダリインデックス（GSI）** といいます。

## ローカルセカンダリインデックスの例

例としてフォーラムのスレッドのデータを格納する`Thread`テーブルを考えます。この例と図は[ローカルセカンダリインデックス - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html)の引用です。

![LSI_01](https://qiita-image-store.s3.amazonaws.com/0/61641/9ac3260b-ce6c-54a0-66ab-6d26d015dcc0.png)

このテーブルのパーティションキーは`ForumName`、ソートキーは`Subject`です。

ここで「フォーラム毎に過去nヶ月に更新のあったスレッドのタイトル一覧を表示したい」という問題を考えます。今のパーティションキー・ソートキーの設計では効率よくこの答えを出すことはできません。

そこでパーティションキーはそのままで、`LastPostDateTime`をソートキーとする別のテーブルを作成します。また必要なのはタイトルのみなので、新しいテーブルは`Subject`のみを持つようにします。

![LSI_02.png](https://qiita-image-store.s3.amazonaws.com/0/61641/21e51387-d366-78a3-f9df-ab6f7c2e6d22.png)

これで「フォーラム毎に過去nヶ月に更新のあったスレッドのタイトル一覧を表示したい」という問題の答えを効率よく出せるようになりました。

このように、あるテーブルをベースに、パーティションキーはそのままに、異なるソートキーのテーブルを作成する仕組みを **ローカルセカンダリインデックス（LSI）** といいます。

## グローバルセカンダリインデックスとローカルセカンダリインデックスの比較
| | グローバルセカンダリインデックス | ローカルセカンダリインデックス |
| :-- | :-- | :-- |
| 新しく設定するキー | パーティションキー、ソートキー（\*1） | ソートキーのみ |
| キーの重複（\*2） | 可 | 可 |
| インデックス数の上限 | 5 | 5 |
| 項目コレクションのサイズ制限 | なし | 10GB以下 |
| テーブル作成後の追加 | 可 | 不可 |
| 読み込み整合性 | 結果整合性 | 結果整合性、強い整合性 |
| キャパシティユニットの消費 | GSIから消費 | ベーステーブルから消費 |

\*1: ソートキーの設定は任意
\*2: インデックスにおいては一意制約はありません（参考：[DynamoDBでGSIやLSIのキーは重複や値なしが許容されるのか確認してみた | DevelopersIO](https://dev.classmethod.jp/articles/whether-dynamodb-allows-duplicates-or-no-values-%E2%80%8B%E2%80%8Bin-gsi-of-lsi-keys/)）

#### 項目コレクションのサイズ制限
ベーステーブルおよびローカルセカンダリインデックスの同じパーティションキーを持つデータの集合を **項目コレクション** と言います。以下は`Thread`テーブルにおける項目コレクションの例です。

![項目コレクション](https://qiita-image-store.s3.amazonaws.com/0/61641/8856837e-3666-3fce-c0de-83a045b67c5b.png)

テーブルがローカルセカンダリインデックスをもつ場合、各項目コレクションのサイズは最大10GBです（ローカルセカンダリインデックスのないテーブルであればこの制限はありません）。

#### テーブル作成後の追加
グローバルセカンダリインデックスはテーブル作成後に作成できます。ローカルセカンダリインデックスはテーブル作成時に作成する必要があります。

#### 読み込み整合性
グローバルセカンダリインデックスは結果整合性のみをサポートします。ローカルセカンダリインデックスは結果整合性と強い整合性をサポートします。強い整合性が必要な場合はローカルセカンダリインデックスの利用を検討します。

#### キャパシティユニット
セカンダリインデックスの読み込み・書き込みに対してもキャパシティユニットは消費されます。

- セカンダリインデックスに対する読み込みアクセスがあった場合、読み込みキャパシティユニットが消費されます
- ベーステーブルの更新によりセカンダリインデックスが更新された場合、書き込みキャパシティユニットが消費されます

ローカルセカンダリインデックスはベーステーブルとキャパシティユニットを共有します。ローカルセカンダリインデックスへの読み込み・書き込みがあった場合、ベーステーブルのキャパシティユニットが消費されます。

グローバルセカンダリインデックスは独自のキャパシティユニットを持ちます。グローバルセカンダリインデックスへの読み込み・書き込みがあった場合、そのグローバルセカンダリインデックスのキャパシティユニットが消費されます。

# 参考
- [Amazon DynamoDB とは - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Introduction.html)
    - [DynamoDB API - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.API.html)
    - [パーティションとデータ分散 - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html)
    - [セカンダリインデックスを使用したデータアクセス性の向上 - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SecondaryIndexes.html)
    - [パーティションキーを効率的に設計し、使用するためのベストプラクティス - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-partition-key-design.html)
    - [ソートキーを使用してデータを整理するためのベストプラクティス - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-sort-keys.html)
    - [DynamoDB でセカンダリインデックスを使用するためのベストプラクティス - Amazon DynamoDB](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-indexes.html)
- [DynamoDBでGSIやLSIのキーは重複や値なしが許容されるのか確認してみた | DevelopersIO](https://dev.classmethod.jp/articles/whether-dynamodb-allows-duplicates-or-no-values-%E2%80%8B%E2%80%8Bin-gsi-of-lsi-keys/)