【初心者】Amazon DynamoDB LSI/GSI を使ってみる

# 1. 目的
- AWSのデータベース関連サービスの復習をしている。DynamoDBについて、LSI/GSIがなんだかよく分からなかったため、これを機に動作確認する。

# 2. やったこと
- 練習用のDynamoDBテーブルを作成する。
- LSI/GSIを設定し、それらを用いてどのようなクエリができるのかを確認する。

# 3. 構成図
- EC2インスタンス内のPythonスクリプトからクエリを実行する。

![dynamo07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/e55bf97f-d413-ed3f-6474-4d7d58f89714.png)



- 練習用のDynamoDBテーブルの内容は以下の通り。（野菜・果物分類表的なイメージ）


![dynamo06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/a54a160a-7718-5295-a539-6a67c5f0ef74.png)






# 4. 予習
- 分かりやすい解説のある記事は以下。
    - 「[今更 DynamoDB 入門](https://qiita.com/kazutomo/items/7c6123f6c8eea55dff33)」
    - 「[DynamoDBのキー・インデックスについてまとめてみた](https://qiita.com/shibataka000/items/e3f3792201d6fcc397fd)」
- LSI/GSIを付けておかないと、属性(列)を指定してのクエリができないということがなんとなく分かった。

# 5. 実機確認
## 5.1 DynamoDB テーブルとGSI/LSIの作成 
- DynamoDBテーブル「mksamba-qiita」を作成する。パーティションキーを「Name」、ソートキーを「Color」とし、その組み合わせをプライマリーキーとする。

![dynamo01a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/db2c8a4a-af63-bbd9-e411-86de6c193c95.png)

- LSIを作成する。
    - LSIの場合、パーティションキーはプライマリーキーで使用したものと同じ「Name」である必要がある。
    - ソートキーを「Kind」とする。
    - 「ローカルセカンダリインデックスとしての作成」にチェックを付ける。チェックを付けないとGSIになってしまう。

![dynamo02a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/20417858-bc84-4481-ae18-3341dce48440.png)

- GSIを作成する。
    - パーティションキーはプライマリーキーで使用したものとは別の「Country」とする。

![dynamo03a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/881caa41-45dc-59aa-0109-50476c036367.png)

- LSI/GSIが設定されていることを確認し、テーブルを作成する。

![dynamo04a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/daf8b7b4-4e05-740a-7488-c54082afec3e.png)

- 作成したテーブルにデータを入力する。

![dynamo05a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/675013b0-9f72-1ab0-cbed-058c2e683521.png)

## 5.2 クエリの動作確認

- 「Name」が「Apple」の項目(行)を取得する。
    - 「Name」がパーティションキーのため実行OK。2つヒットする。

``` query1.py
import boto3
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("mksamba-qiita")
response = table.query(
    KeyConditionExpression=Key("Name").eq("Apple")
)
print(response["Items"])
```
```
[ec2-user@ip-10-0-0-232 ~]$ python3 query1.py
[{'Season': 'Winter', 'Color': 'Green', 'Country': 'NZ', 'Kind': 'Fruit', 'Name': 'Apple'}, {'Season': 'Winter', 'Color': 'Red', 'Country': 'Japan', 'Kind': 'Fruit', 'Name': 'Apple'}]
```

- 「Name」が「Apple」で、「Color」が「Red」の項目(行)を取得する。（Pythonコードはクエリ部分のみ記載）
    - 「Color」がソートキーのため実行OK。1つヒットする。
    
``` query2.py
response = table.query(
    KeyConditionExpression=Key("Name").eq("Apple") & Key("Color").eq("Red")
)
print(response["Items"])
```

```
[ec2-user@ip-10-0-0-232 ~]$ python3 query2.py
[{'Season': 'Winter', 'Color': 'Red', 'Country': 'Japan', 'Kind': 'Fruit', 'Name': 'Apple'}]
```

- 「Name」が「Apple」で、「Kind」が「Fruit」の項目(行)を取得する。
    - 「Kind」がソートキーではないため、実行エラー。

``` query3.py
response = table.query(
    KeyConditionExpression=Key("Name").eq("Apple") & Key("Kind").eq("Fruit")
)
print(response["Items"])
```
```
[ec2-user@ip-10-0-0-232 ~]$ python3 query3.py
Traceback (most recent call last):
  File "query3.py", line 8, in <module>
～～
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the Query operation: Query condition missed key schema element: Color
```

- 「Name」が「Apple」で、「Kind」が「Fruit」の項目(行)を<font color="Red">クエリ時にLSIを指定して</font>取得する。
    - 「Kind」がLSIのソートキーになっているため、実行OKになる。2つヒットする。

``` query4.py
response = table.query(
    IndexName='Name-Kind-index', KeyConditionExpression=Key("Name").eq("Apple") & Key("Kind").eq("Fruit")
)
print(response["Items"])
```
```
[ec2-user@ip-10-0-0-232 ~]$ python3 query4.py
3 [{'Season': 'Winter', 'Color': 'Green', 'Country': 'NZ', 'Kind': 'Fruit', 'Name': 'Apple'}, {'Season': 'Winter', 'Color': 'Red', 'Country': 'Japan', 'Kind': 'Fruit', 'Name': 'Apple'}]
```

- 「Country」が「Japan」の項目(行)を取得する。
    - 「Country」がパーティションキーでないため、実行エラーになる。
    
``` query5.py
response = table.query(
    KeyConditionExpression=Key("Country").eq("Japan")
)
print(response["Items"])
```
```
[ec2-user@ip-10-0-0-232 ~]$ python3 query5.py
Traceback (most recent call last):
  File "query5.py", line 8, in <module>
～
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the Query operation: Query condition missed key schema element: Name
```

- 「Country]が「Japan」の項目(行)を<font color="Red">クエリ時にGSIを指定して</font>取得する。
   - GSIにおいては「Country」がパーティションキーのため、実行OK。3つヒットする。

``` query6.py
response = table.query(
    IndexName='Country-index', KeyConditionExpression=Key("Country").eq("Japan")
)
print(response["Items"])
```
```
[ec2-user@ip-10-0-0-232 ~]$ python3 query6.py
[{'Season': 'Winter', 'Color': 'Orange', 'Country': 'Japan', 'Kind': 'Vegetable', 'Name': 'Carrot'}, {'Season': 'Winter', 'Color': 'Red', 'Country': 'Japan', 'Kind': 'Fruit', 'Name': 'Apple'}, {'Season': 'Summer', 'Color': 'Red', 'Country': 'Japan', 'Kind': 'Vegetable', 'Name': 'Tomato'}]
```

# 6. 所感
- 使いこなせる感はないが、何となく設定する意味は分かるようになった。