イラストで理解しない Amazon DynamoDB

# tl; dr

https://qiita.com/Yona_Sou/items/caf1d2ac5867ed0c3154

これの二番煎じ

# Amazon DynamoDB とはなにか

https://aws.amazon.com/jp/dynamodb/

> フルマネージドでサーバーレスの key-value NoSQL データベースです

重要なのは **key-value** です。

https://aws.amazon.com/jp/nosql/key-value/

キーがあって、それに対応する値があるだけです。

ある意味 Amazon S3 と近いです。

https://aws.amazon.com/jp/s3/faqs/?nc=sn&loc=7

> Amazon S3 はシンプルなキーベースのオブジェクトストアです。

## key-value っぽい例

`ファイルのフルパス` と `ファイルの中身` も key-value っぽいモデルです。

例えば `C:\Users\Name\Documents\README.md` が

```
# README 
This is the document.
```

みたいな中身だった場合

key が `C:\Users\Name\Documents\README.md` で 

value が

```
# README 
This is the document.
```

であると言えるわけです。

(もちろん、macOSとかの場合は `/Users/Name/Documents/README.md` がキーで、そのファイルの中身が値であると言えるわけです)

Amazon DynamoDB とは、その `ファイルが全部 JSON ファイルになっているケースである` と私は連想しています。

## Amazon DynamoDB を JSON ファイルで雑に説明してみる

例えば、ユーザのデータを全部 JSON で管理するとして

`User1.json` `User2.json` みたいなファイルをどっかのフォルダに格納して行くことになると思います。

例えばこんな感じのデータです

```
{
  "name": "Mike",
  "class": "1-1",
  "age": 1,
  "account": "thomas",
  "birthYear": "2010",
  "birthDate": "0504",
}
```

そのとき、もし 
`name` が "Ken" で
`class` が "1-2" であるユーザの情報を検索したいと思ったらどうするでしょうか？

面倒なので chatGPT に聞いたんですが

```
find . -type f -name "*.json" -exec jq '.[] | select(.name == "Ken" and .class == "1-2")' {} \;
```

こんな感じで検索する例を教えてくれました。

つまり、全部の JSON ファイルをスキャンする必要があるわけです。これが Scan になります。ファイルの数だけ全部中身を調べて行く地道な作業です。件数が増えれば増えるだけ時間がかかっていきます。

ここで、ちょっと工夫をしてみましょう。

## パーティションキーをフォルダとして捉える

今まではすべての JSON ファイルを同じフォルダにどんどん入れていましたが、あらかじめ class で JSON ファイルの保存先を変えてみましょう。

例えば User1.json の class が "1-1" だった場合は `1-1/User1.json` となります。

このルールでフォルダ分けをする管理をした場合

`name` が "Ken" で
`class` が "1-2" であるユーザの情報を検索したいと思ったら

```
find ./1-2 -type f -name "*.json" -exec jq '.[] | select(.name == "Ken")' {} \;
```

こうなります。ファイルを走査する範囲がパーティションキーの中に絞られました。大きな改善です。

## ソートキーをファイル名として捉える

ではもうちょっと工夫してみましょう。

ここで JSON ファイルのファイル名を name と一致させるようにした場合

例えば User1.json の class が "1-1" で name が "mike" だった場合は `1-1/mike.json` となります。

このルールでフォルダ分けと名付けを管理した場合

`name` が "Ken" で
`class` が "1-2" であるユーザの情報を検索したいと思ったら

単に `1-2/Ken.json` にアクセスすればいいわけです。

この場合、ディレクトリの中について全部のファイルを走査する必要はなくなります。一発で取ってこれるわけです。

## ファイル名でソートされているのだと考える

このモデルで名付けをした場合

例えば `class` が "1-3" で `name` が "P" で始まるデータを取ってくる場合、単にファイル名が P で始まるものを取得すればいいわけです

ここで、このファイルシステムは `自動的にファイル名でソートされた状態で格納されるもの` だと考えれば、ソートキーの使い方のイメージができてくるわけです

例えば

- name が P で始まる
- name が A から M の範囲

みたいな絞り込みをするためにソートキーがあるのだと考えるわけです。

## ここまでのまとめとプライマリキー

DynamoDB のイメージとして

- `パーティションキー` フォルダに `ソートキー.json` というファイル名で JSON ファイルを保存するデータベース
- そのため、キー以外の値で検索する場合は全部のフォルダの全部のファイルをスキャンする必要がある
- パーティションキー が分かっていれば検索する範囲がもうちょっと絞られる
- パーティションキーとソートキーが分かっていればぴったり1ファイルとしてデータを取得できる

という感じのモデルとして捉えることができます

つまり、あとからいい感じに検索しようと考えた場合には、フォルダ分けとファイルの名付けが重要であるわけですね。(それがパーティションキーとソートキーの設計に対応するわけです)

そして `パーティションキー(と存在する場合ソートキーのセット)` を `プライマリキー` と呼びます。

このモデルで考える場合 `プライマリキー` = `JSONファイルのフルパス` なので **異なる中身のファイルが同一のプライマリキーを持てない** ということになります。例えば、別のユーザなのに同じプライマリキーにはならないわけです。

# GSI(Global Secondary Index) is 何

GSIとは力業です。

プライマリキー = パーティションキーとソートキー というモデルで考えた場合、同じソートキーをもつデータが作れないわけです。

例えば、誕生日は重複する可能性があるのでソートキーにできないのですが、その場合、特定の範囲の誕生日のユーザを得ようと考えた場合、全件スキャンして中身をいちいち確認していくしかないわけです。つらいわけです。

そこで例えば、以下のファイルを同時に作る事を考えます

フルパス: `GSI/<birthYear>/<birthDate>/<プライマリキー>.json`

例えば以下のようなファイルを格納しようと考えた場合

例えばこんな感じのデータです

```
{
  "name": "Mike",
  "class": "1-1",
  "age": 1,
  "account": "thomas",
  "birthYear": "2010",
  "birthDate": "0504",
}
```


まず `1-1/Mike.json` としてプライマリキーを基準としたファイルを保存します。
同時に `GSI/2010/0504/1-1/Mike.json` に同じ物を保存します。

こうすることにより `誕生年` + `誕生月日` で絞り込んだデータを取得したい場合 `GSI/誕生年/誕生月日` フォルダ以下の JSON ファイルを全部もってくれば、データを全件スキャンする必要がなくなるわけです。データを2倍保存するわけですね。

これが GSI を考える際のモデルになります。実際 **GSI が設定されている DynamoDB への書き込みを行う場合、設定されている GSI の数だけ書き込みのコストがかかります。**