DynamoDB の設計について考えてみる。

https://speakerdeck.com/_kensh/dynamodb-design-practice


## 概要

### クラウドアプリケーションの特性

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/2b913910-4e2d-12e2-f321-9a4ea4653564.png)


クラウドアプリケーションの特性は、従来のアプリケーションと大きく異なります。

ユーザー数は非常に簡単に数百万人に達し、データボリュームは短期間で PB レベルや EB レベルに到達することもあります。
パフォーマンスは、多くの場合、ミリ秒で動作することが期待されます。

アプリケーションによっては、 1 秒間に数百万件のリクエストを処理し、多い日には 1 秒間に数千万件のリクエストを処理することもあるでしょう。

適切なコストとパフォーマンスを維持するためにシステムが分散し、スケールアップ、アウト、インできるアプリケーションである必要があります。

Amazon DynamoDBでは、開発者は完全マネージド型の API を介して瞬時にアクセスし利用することができます。

* クラウドアプリケーションに求められる特性

|要素  |特性  |
|---|---|
|ユーザー  |100 万人以上  |
|データボリューム  |TB、PB、EB  |
|データの局所性  |グローバル  |
|パフォーマンス  |ミリ秒、マイクロ秒  |
|リクエスト頻度  |数百万  |
|アクセス  |モバイル、IoT、デバイス  |
|スケール  |Up and down  |
|エコノミクス  |従量制料金  |
|開発手法  |API アクセス  |

<details><summary>Amazon DynamoDB 自体の解説についてはこちら</summary><div>


### Amazon DynamoDB の特性

* フルマネージド型の NoSQL データベースサービス
* 3つの Availability Zone に保存されるので信頼性が高い
* 性能要件に応じて、テーブルごとにスループットキャパシティを定義するキャパシティの Auto Scaling、オンデマンドキャパシティといった設定も可能
* ストレージの容量制限がない

### DynamoDB のテーブル

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/4be5c450-1401-7729-2e88-01662e882e1b.png)

DynamoDB におけるテーブルはRDBMSにおけるテーブルと概念が異なります。

テーブルを作成する際に、Primary Key を指定する必要があります。
Primary Key はテーブルの各項目を一意に識別するために使います。Primary Key は、Partition Key および Sort Key で構成されます。(Sort KeyがなくPartition Keyのみの場合もあります)

Item は RDBMS でいうレコードになります。

各ItemはAttributesによって構成されますが、Primary Key 以外の Attribute については不揃いであっても問題ありません。

#### Partition Key と Sort Key

DynamoDB では2種類のプライマリーキーをサポートする

https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/

* Partition Key
* Partition Key & Sort Key

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/096de478-f555-471b-d3dd-ddd0203d0a97.png)


#### Secondary Index 

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SecondaryIndexes.html

DynamoDB では2種類の Secondary Index を利用することができる

* Local Secondary Index (LSI)
  * Sort Key 以外に絞り込み検索を行う Key を持つことができる
  * Partition Key はベーステーブルと同じで、Sort Key が異なる
* Global Secondary Index (GSI)
  * Partition Key 属性の代わりとなる、Partition Key をまたいで検索を行うためのインデックス

LSIの例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/bb254c59-44d0-5dcf-2897-b0f8ae48c7e9.png)



#### Capacity Unitの考え方

* Read: 1ユニットにつき、最大4KBのデータを1秒に2回読み込み可能（強い一貫性を持つ読み込み設定であれば、1秒あたり1回）
* Write: 1ユニットにつき、最大1KBのデータを1秒に1回書き込み可能

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html

#### DynamoDB におけるデータの操作

API Call によるデータ操作を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/d9a56b43-e87e-5b35-8016-7eacb8becca6.png)


https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.API.html

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-query-scan.html


### DynamoDB の知識とベストプラクティス

* テーブルの数は最小限に留める
  * 一箇所にあるデータに、テーブルやインデックスを通じアクセスすることで望む形のデータが入手しやすいように構成
  * キャパシティユニットの効率向上
  * テーブルを分けるべき例外はある

* DynamoDB は Primary Key（Partition Key(PK)、または　PK + Sort Key(SK) の複合）でデータを識別し、アクセスする
 * Query 等のAPI利用

* グローバルセカンダリインデックスは元テーブルから非同期レプリケーションされる別テーブルのような存在

* 1テーブルや1インデックスに複数種類のアイテムをもたせてもよいし、1属性に複数種類の値を入れてもよい

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/best-practices.html

</div></details>

## DynamoDB の設計について

### RDBMS と DynamoDB のモデリングの違い


#### RDBMS のモデリングプロセス

みなさまよくご存知の RDBMS におけるモデリングプロセスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/dc4b335f-07d5-9a61-4cb4-c43e8f0bb6b4.png)



#### DynamoDB のモデリングプロセス

DynamoDB でよくある誤解の一つに、「DynamoDB ってスキーマレスだから、事前の設計いらないでしょ？」があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/4e6b79bd-e528-2253-cd54-a95c9cac16e7.png)
しかし、このように、DynamoDB においても、**アクセスパターン** に基づいた設計が必要です

### イベント検索アプリを実際に設計してみる

#### 対象とするサンプルアプリケーションについて

今回は、イベントの検索画面と検索結果の一覧表示画面を持つアプリケーションを考えていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/05489551-4f12-e13c-5e9b-2d56f6285c15.png)


※ この例題では、シンプルに各項目を完全一致で検索できるようにするものとします
※ 現実的には OpenSearch 等の利用も検討してください(今回はDynamoDBの学習教材としてお考えください)

それぞれのモデリングプロセスを実例を交えてもう少し詳しく見てみよう


####  ドメインデータモデリング

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/20460d28-44cd-b835-c000-ddb3989623bf.png)

* 対象ドメインのデータをモデリング
* RDB 設計と同じく ER 図による概念と論理レベルの整理は有効

今回のアプリケーションのER図はこんな感じにしてみました。他にも様々な設計方針があると思いますが、今回はこれで。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/e5da484d-3288-8bf3-6c83-a46211159883.png)

もし、この ER を SQL でアクセスするならこんな感じになるはずです。

```SQL
-- 例: 会場名とタグでイベントを完全一致検索
SELECT e.id, v.name, ... 
FROM Events e
  JOIN	Venues v
    ON v.id = e.venue_id
  JOIN	EventTags et
    ON et.event_id = e.id
  JOIN	Tags t
    ON t.id = et.tag_id
WHERE	v.name = :vname AND t.name = :tname
```

### アクセスパターン設計


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/45726f53-f011-f6be-1386-1b42bb3e1173.png)

* ビジネス要件からアプリケーションで必要な機能とデータ（アクセスパターン）を整理する
  * 「イベント情報を ID で検索する」など
* アクセスパータンが洗い出せたら、それぞれ Use Case に落とし込んで見る
  * 「イベント情報を ID で検索する」=> 「getEventByEventID」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/9274e277-eb9f-9a9b-7476-0f741107fea1.png)


### Index設計

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/8657fbe6-852a-9896-69da-cee04f7aef1d.png)

* ユースケースを満たせるテーブルおよびインデックスのスキーマを設計する

何も考えずにシンプルに DynamoDB に落とし込んでみると？ どうなる？

#### RDBMS の経験則に沿った設計方針

##### シンプルな Attributes 列挙パターン

RDBMSのスキーマ設計のように、まずはシンプルに Attributes (RDBMSでいうカラムのようなもの)を横並びに持ってみたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/86fb7296-1323-8c9e-bf7b-9d023e0e1f36.png)
※ {foo} は何らかの値が入っていることを示す

この構成の懸念点

* GetItem や Query が EventID にしか使えず、それ以外の属性で検索したいとき Scan + Filter で高コストになってしまう
* Scan はテーブル・インデックス内を文字通りフルスキャンするため、パフォーマンスおよびコスト面のリスクがある
* 項目数が少なく Scan で問題ないことが担保できている場合などを除き、一般に Scan でなく Query や GetItem / BatchGetItem の利用を検討すべき

##### Attributes 列挙 GSI パターン

前例の懸念点を踏まえて、GSIによるインデックス追加を実施してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/c1d9bc20-1ebe-cae8-836c-7741d22ff4f5.png)

この構成で解決した点

* 各 Attribute に Global Secondary Index が貼られたため Query が可能に
  * ※ GSI はデフォルトで 20 個/テーブル。それ以上は上限緩和を申請

この構成の懸念点

* GSI が増えることによる金銭（スループット+ストレージ）コスト増、管理コスト増
* 多くの GSI をアプリケーションが意識する必要がある
* このスキーマが絶対にダメというわけではないが他の設計パターンを踏まえて検討すべき

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html#limits-secondary-indexes

#### DynamoDB らしい Index 設計テクニック

##### GSI オーバーローディング パターン

* 1 項目内にフラットに属性を並べるのではなく、情報を縦に持つ
* 一つの GSI に複数の検索要件をもたせる（GSI オーバーローディング）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/842e85f1-8e87-63cc-ffb0-34a8815da038.png)



それぞれ GSI をどのように構成しているかみてみましょう。
基本的な方針としては、検索条件として指定したい属性をキーとする GSI を定義するようにしています。


* GSI-1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/0c7ccb38-7e89-ae44-9266-314f93d65d1f.png)


* GSI-2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/ee35c327-6bae-3757-f267-1fbca8c1ca8b.png)

この構成で解決した点

* GSI の数が少なくて済み、コストやクエリを最適化できる

###### 格納されるデータイメージ

実際にはこのような値が入ることを想定
この例の場合、E123 と E145 の２つのイベント情報。会場はどちらも V32 としている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/f8814b3c-e088-4c33-448d-1e1cae493fd5.png)

###### データに対するQueryを考えてみる

EventのIDに対する検索では、Primary Key の Partition Key がそのまま利用できる。
つまり、Query(ID = :eventId) で該当 EventのIDでアイテムが取得できる
例: Table.Query(ID = "E123")

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/b92c6cd5-de18-dc69-fa09-84933b0eba90.png)

タグに対する検索は、GSI-1 の Partition Key を利用する。
Query(DataValue = :tagName) で該当 タグ を持つ Event の ID を取得できる
例: GSI1.Query(DataValue = "Tag_#Serverless")

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/c2ea09a0-90ed-faa6-16fd-1a645d93b6fd.png)


### Query条件設計

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/fc7ac068-4531-cb66-b874-2c22700cb277.png)


* どのユースケースにはどの Table / Index を使い、どうデータを問い合わせるのか（GetItem? GetBatchItem? Query? Scan?）を列挙
* Filter Condition や Projection Expression などを記述してもよし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/beb8e60d-59e8-2a10-8989-5cff47ff5468.png)


### まとめ

* サーバーレスアプリケーション にはスケーラブルな Amazon DynamoDB が使いやすい
  * 懸念事項を考慮の上で許容できるならその限りではない
* DynamoDB のモデリングプロセスは
  * 業務分析してデータモデルを整理
  * ユースケースリスト
  * テーブル・インデックス設計
  * クエリ条件設計
  
* 公式ドキュメントのベストプラクティスを踏まえた上で設計に臨むとよい。




### Appendix

GSI オーバーローディングに関するより詳細な情報はこちら。
[![thumbnail](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6088/e90329c3-ddcb-370a-4ce1-53b619c71a65.png)](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2018-amazon-dynamodb-advanced-design-pattern/33)
※ 画像をクリックするとBlackBelt資料に飛びます。