ローカル・セカンダリ・インデックスの射影範囲について

AWSのDynamoDBで使用されるLGS(ローカル・セカンダリ・インデックス)について纏めます。

#1.ローカル・セカンダリ・インデックスとは

DynamoDBでは項目を識別するためのキーとしてパーティションキーのみが使用されるものとパーティションキーとソートキーを組み合わせた複合キーを使用するものの2種類があります。
LSIは複合キーを使用しているテーブルにのみ設定可能なインデックスです。
複合キーが設定されているテーブルでは暗黙的にこの複合キーで検索を行います。(この検索結果は0件or1件の一意なものになります。)
しかし、例えばとある条件に一致する項目を複数抽出したいなどといったほかの検索の要望が出てくることもあると思います。
その時に有効なのがLSIです。
暗黙的なインデックスは「プライマリキー+ソートキー」で構成されていますが、これとは別に「プライマリキー+指定した属性」でインデックスを作成することで任意属性を用いた検索を行うことができるようになります。


#2.LSIの宣言
LSIはテーブルの作成時にのみ作成することが可能です。
CLIからテーブルを作成する場合、このようになります。

```
    aws dynamodb create-table \
    --table-name helloLSI \
    --attribute-definitions AttributeName=CLASS,AttributeType=N AttributeName=NUMBER,AttributeType=N AttributeName=ENGLISH,AttributeType=N \
    --key-schema AttributeName=CLASS,KeyType=HASH AttributeName=NUMBER,KeyType=RANGE \
    --local-secondary-indexes "IndexName=ENGLISH_LSI,KeySchema=[{AttributeName=CLASS,KeyType=HASH},{AttributeName=ENGLISH,KeyType=RANGE}],Projection={ProjectionType=KEYS_ONLY}"\
    --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
```

上記のコマンドのうち、

```
 --local-secondary-indexes "IndexName=ENGLISH_LSI,KeySchema=[{AttributeName=CLASS,KeyType=HASH},{AttributeName=ENGLISH,KeyType=RANGE}],Projection={ProjectionType=KEYS_ONLY}"
 ```

がLSIの宣言部です。今回はCLASSとENGLISHを組みあわせたキーをENGLISH_LSIという名前で定義しています。

#3.LSIを使用した検索

このhelloLSIというテーブルは複合キーは
プライマリキー：CLASS
ソートキー：NUMBER
の二つで他にENGLISHという名前の属性を定義しています。

このテーブルに項目を下記のように追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/162114/4bf63e6c-8b0a-bfb7-1e8a-acf07becf4f9.png)

まず始めに、プライマリキーとソートキーを組み合わせた複合キーで検索してみます。
CLASS=1、NUMBER=1の項目の検索結果

```
 aws dynamodb query \
>   --table-name helloLSI \
>   --key-condition-expression "#cl = :class AND #num =:number" \
>   --expression-attribute-values '{":class":{"N":"1"},":number":{"N":"1"}}'\
>   --expression-attribute-names '{"#cl":"CLASS","#num":"NUMBER"}'
  {
    "Count": 1,
    "Items": [
        {
            "NAME": {
                "S": "ITO"
            },
            "NUMBER": {
                "N": "1"
            },
            "JAPANESE": {
                "N": "90"
            },
            "ENGLISH": {
                "N": "80"
            },
            "CLASS": {
                "N": "1"
            },
            "MATH": {
                "N": "70"
            }
        }
    ],
    "ScannedCount": 1,
    "ConsumedCapacity": null
}
```

CLASS=1,NUMBER=1のイトウさんの情報が取得できました。
このテーブルへの検索がCLASSとNUMBERを用いたものだけならこれでいいのですが、英語の点数が90点の人を検索したいとなった場合にLSIの出番です。
--index-nameオプションでインデックス名を指定することでLSIを使用して検索できるようになります。

```

aws dynamodb query \
>   --table-name helloLSI \
>   --index-name ENGLISH_LSI \
>   --key-condition-expression "#cl = :class AND ENGLISH = :english" \
>   --expression-attribute-values '{":class":{"N":"1"},":english":{"N":"90"}}'\
>   --expression-attribute-names '{"#cl":"CLASS"}'
{
    "Count": 3,
    "Items": [
        {
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "NUMBER": {
                "N": "3"
            }
        },
        {
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "NUMBER": {
                "N": "4"
            }
        },
        {
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "NUMBER": {
                "N": "5"
            }
        }
    ],
    "ScannedCount": 3,
    "ConsumedCapacity": null
}

```

英語の点数が90点の人は3人なので3つの項目が返されていますね。
しかし、これらの項目はほかにNAMEやJAPANESE、MATHといった属性を持っていましたが、今回それらは取得できていません。
これはLSIの定義時の```ProjectionType=KEYS_ONLY```が原因です。LSIには同じプライマリキーをもつ項目の合計サイズが10GBまでという制限があるのでインデックスに射影する属性を必要最低限に抑えるための設定です。
ProjectionTypeには「KEY_ONLY」「ALL」「INCLUDE」の3つから選択して設定することができます。

#4.ProjectionTypeの設定

###KEY_ONLY

上記の通り、KEYとして定義されている項目のみを取得する設定です。(プライマリキーとソートキーとLSI用のキー)
宣言は

```
Projection={ProjectionType=KEYS_ONLY}
```

###ALL
全属性を取得する必要があるのであればとALLを使用します。
宣言は

```

ProjectionType=ALL

```

先ほどと同じデータに対して検索を掛けると下記の結果が得られます。

```
{
    "Count": 3,
    "Items": [
        {
            "NAME": {
                "S": "SHIINAGAWA"
            },
            "NUMBER": {
                "N": "3"
            },
            "JAPANESE": {
                "N": "80"
            },
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "MATH": {
                "N": "85"
            }
        },
        {
            "NAME": {
                "S": "TIBA"
            },
            "NUMBER": {
                "N": "4"
            },
            "JAPANESE": {
                "N": "80"
            },
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "MATH": {
                "N": "85"
            }
        },
        {
            "NAME": {
                "S": "NINOMIYA"
            },
            "NUMBER": {
                "N": "5"
            },
            "JAPANESE": {
                "N": "90"
            },
            "ENGLISH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            },
            "MATH": {
                "N": "90"
            }
        }
    ],
    "ScannedCount": 3,
    "ConsumedCapacity": null
}
```

登録されているすべての属性が取得できていることが分かります。

###INCLUDE

KEY以外にも取得したい項目はあるけれどすべては必要ない。そういう時はINCLUDEを使用します。これはKEY+指定した項目を取得することができます。
今回の例ではkeyは「CLASS」「NUMBER」「ENGLISH」ですが、「JAPANESE」「MATH」も取得したいとすると定義はこのようになります。

```
Projection={ProjectionType=INCLUDE,NonKeyAttributes=["JAPANESE","MATH"]}"\
```

NonKeyAttributesを追加してほしい属性を記述すればOKです。
検索結果は下記のようになりました。

```
{
    "Count": 3,
    "Items": [
        {
            "NUMBER": {
                "N": "3"
            },
            "ENGLISH": {
                "N": "90"
            },
            "JAPANESE": {
                "N": "80"
            },
            "MATH": {
                "N": "85"
            },
            "CLASS": {
                "N": "1"
            }
        },
        {
            "NUMBER": {
                "N": "4"
            },
            "ENGLISH": {
                "N": "90"
            },
            "JAPANESE": {
                "N": "80"
            },
            "MATH": {
                "N": "85"
            },
            "CLASS": {
                "N": "1"
            }
        },
        {
            "NUMBER": {
                "N": "5"
            },
            "ENGLISH": {
                "N": "90"
            },
            "JAPANESE": {
                "N": "90"
            },
            "MATH": {
                "N": "90"
            },
            "CLASS": {
                "N": "1"
            }
        }
    ],
    "ScannedCount": 3,
    "ConsumedCapacity": null
}

```

keyに加えてJAPANESEとMATHも取得できています。