【SQL脳から切り替えよう】DynamoDB使ってみた

## 概要

業務でDynamoDBを利用する機会があったのですが、SQLに冒された脳ミソではかなり躓いたので備忘録として。。
第一弾は、DynamoDBの基本的な概念をおさらいしたいと思います。


対象：RDBは分かるけど、NoSQLを触ったことのないヒト

## DynamoDBのキホン

### テーブル、アイテム、属性

- テーブル
    - RDBとほぼ同じ概念。アイテムのコレクション
- アイテム
    - RDBでいうレコード。一意に識別可能な属性の集合
- 属性
    - RDBでいう列、カラム。

### プライマリキー

アイテムを一意に識別できるキー。以下の２種類がある。

- パーティションキー
- パーティションキーとソートキー（RDB的には複合キー）


それぞれの意味はこちら。

- パーティション
    - データが保存されるDynamoDB内部の物理ストレージ
    - DynamoDBのデータは複数のパーティションに分散して保存される
- パーティションキー
    - キー値をハッシュ化してデータを保存するパーティションを決定する
- ソートキー
    - パーティション内のデータはソートキーで並び替えられて、物理的に近くに保存される

公式の素晴らしい図解はこちら。

[パーティションとデータ分散](https://prime-x-co-ltd.github.io/px-blog/HowItWorksPartitionKeySortKey.png)

### セカンダリインデックス
異なるパーティションキー、ソートキーを設定できる仕組み。以下の２種類がある。

- グローバルセカンダリインデックス（GSI）
    - 元のテーブルと異なるパーティションキーとソートキーを設定する
    - テーブルあたり20個まで
- ローカルセカンダリインデックス（LSI）
    - 元のテーブルと同じパーティションキーと、異なるソートキーを設定する
    - テーブルあたり5個まで


とりあえず作ってみる
ちなみに[NoSQL Workbench](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.html)というツールで作成しました。わりと便利w

PK=Article, SK=Campaignという元テーブルに、
PK=Campaign, SK=Articleという（グローバル）セカンダリインデックスを設定してみました。

![Article](https://prime-x-co-ltd.github.io/px-blog/Article.png)

![Campaign](https://prime-x-co-ltd.github.io/px-blog/GSI_Article_Campagin.png)

なるほど、、こんな感じで「物件ごと」「キャンペーンごと」などの出し分けが出来るってわけですね。。


### データの操作
要はSQL文です。簡潔なネーミングで助かります。

- 基本的なCRUD
    - ```GetItem```
        - プライマリキーを指定して、単一のアイテムを取得する
        - プライマリキーが複合キー（パーティションキー＋ソートキー）の場合は全て指定する必要がある
    - ```PutItem```
        - プライマリキーを指定して、単一のアイテムを追加する
    - ```UpdateItem```
        - プライマリキーを指定して、単一のアイテムを更新する
    - ```DeleteItem```
        - プライマリキーを指定して、単一のアイテムを削除する

- バッチオペレーション
    - 複数の操作を並列処理してくれる
    - ```BatchGetItem```
        - 最大100個まで```GetItem```に対応
    - ```BatchWriteItem```
        - 最大25個の```PutItem```および```DeleteItem```に対応
        - ```UpdateItem```は対応していない

- クエリとスキャン
    - ```Query```
        - パーティションキーを指定して、複数のアイテムを取得する
        - ソートキーを指定して、検索結果を絞り込む
    - ```Scan```
        - 全てのアイテムを取得する

<br><br>

ここまではいい。躓いたのはここからorg
RDB的なリレーションをどうやって表現するんだ、、だって
<br>
**JOINできないんだもん。。**

## 「多対多」の関係を考える
記事にタグを付けて、タグは一元管理したいみたいなケース。
1つの記事は複数のタグを設定でき、タグも複数の記事に紐づくという典型的なパターン。
以下の様なデータモデルを考えます。

![M:Nリレーション](https://prime-x-co-ltd.github.io/px-blog/Post-Tag-mn-relation.png)

- RDBの場合
    - おそらく中間テーブルを作って１対多の関係を作りますよね。
    

![M:NリレーションRDB](https://prime-x-co-ltd.github.io/px-blog/Post-Tag-mn-relation-rdb.png)


- DynamoDBの場合
    - 同じ考え方でできそう。。
    - TagIdをPKとするセカンダリインデックスを作る
    - PK=PostId, PK=TagIdとして```Query```を投げれば投稿ごと、タグごとのデータが取り出せる

![M:NリレーションRDB](https://prime-x-co-ltd.github.io/px-blog/Post-Tag-mn-relation-dynamo.png)


一見良さそうだが（多分間違いではない）、実際にデータを入れてみると問題に気付く。

【例】あるタグが付けられた投稿の一覧を取得したい

- PK=TagIdで中間テーブル（Links）に```Query```を投げる（1回）
- 取得した各PostIdについて、PK=PostIdとしてPostsテーブルに```GetItem```を投げる（N回）

ちょ、リクエスト数多くない？？
しかも、アイテム数に比例してリクエスト数が増加するっていう。。JOIN出来ないの？
（ **n+1問題**というらしいです。勉強になりますw）

<br>
あれこれドキュメントを探っていると、、
[公式](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-adjacency-graphs.html)に素晴らしいプラクティスがありました。

[公式](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-general-nosql-design.html)にはまたこんな金言も。。
> DynamoDB アプリケーションではできるだけ少ないテーブルを維持する必要があります。

本当に、公式ドキュメントを読むのって大事ですよねw

## 隣接関係リスト
さあ、解決編です。
結論から言うと、一枚のテーブルにします。お馴染み(?)の[NoSQL Workbench](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.html)で作りました。
PostId=PK, TagId=SK/GSI-PKとしてます。

![隣接関係リスト](https://prime-x-co-ltd.github.io/px-blog/PostsJoinTags.png)

![隣接関係リストGSI](https://prime-x-co-ltd.github.io/px-blog/GSI_PostsJoinTags_TagId.png)

RDBに慣れた人にとっては、目眩がしそうなデータ構造ですね。。
ポイントは以下になります。

- ```PK=SK=PostId```のとき、それは投稿の詳細を表す
- ```PK=SK=TagId```のとき、それはタグの詳細を表す

実際のクエリはこうなります。

- ある投稿の詳細を取得したい -> PK=SK=PostIdで```GetItem```
- あるタグの詳細を取得したい -> PK=SK=TagIdで```GetItem```
- ある投稿に付いたタグ一覧を取得したい -> PK=PostIdで```Query```
- あるタグの付いた投稿一覧を取得したい -> PK=TagIdで```Query```

おまけですが、、

- ```UpdateItem```でタグ名を変更しても、各PostIdがもつのはTagIdなので整合性は保たれる（ハズ）


上手くいきそうですね。これ考えたヒトすごーーいw
これでなんとかDynamoDBでテーブル設計ができそうな気がします。。

次回はずばり「**トランザクション**」です！
ちゃんとロールバックするんだよね！？大丈夫だよね？

まだまだDynamoDBに半信半疑な今日この頃でした。。

## 参考文献

- [Amazon DynamoDBとは](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Introduction.html)
- [NoSQL Workbench](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.html)
- [DynamoDBで、RDBMSのリレーション設計に対応！GSIで効率良いクエリー設計を解説します😎](https://www.ragate.co.jp/blog/articles/3278)
- [或る阿呆の記](https://hack-le.com/dynamodb-many-to-many/)