概要
サーバレスアプリケーションで利用されることの多いAmazon DynamoDBについてまとめてみました。DynamoDBの利用を検討されている方やDynamoDBのテーブル・インデックス設計を考えられている方の参考になると幸いです。
Amazon DynamoDB
DynamoDBの特徴
- フルマネージド型の NoSQL データベースサービス
- データは3つの Availability Zone に同期的にレプリケートされるため可用性とデータ耐久性が高い
- 性能要件に応じて、テーブルごとに読み込み・書き込みキャパシティを設定可能。キャパシティの Auto Scalingやオンデマンドキャパシティにも対応
- ストレージの容量制限がない。使った分だけの従量課金制
基礎知識
テーブルは各Itemを一意に識別できるPrimary Keyを持ちます。Primary Keyは、Partition KeyおよびSort Keyで構成されます。Partition Keyのみで各Itemを一意で識別できる場合はSort Keyは必須ではありません。Primary Key以外の項目は不揃いでも構いません。
Primary Key
Primary Keyは、テーブル内の各Itemを一意に識別できる必要があります。DynamoDBは2種類のプライマリキーをサポートします。
- Partition Key(PK)
- PKが重複する項目は登録できません。
- Partition Key(PK) & Sort Key(SK)
- PKは重複しても構いません。
- PKとSKがともに重複する項目は登録できません。
Secondary Index
Primary Keyを用いたクエリに加えて、柔軟なクエリを実現するためにDynamoDBは2種類のSecondary Indexをサポートしています。
Local Secondary Index(LSI)
- SK以外に絞り込み検索を行うためのIndexです。
- Primary KeyのPKと同一ですが、SKが異なる属性による検索のために利用します。
- LSIはテーブル作成時にのみ作成可能です。
Global Secondary Index(GSI)
- Primary Keyの代わりとなる、PKをまたいだ検索を行うためのIndexです。
- GSIにより、Primary Keyに依存しない新たなPK・SKを持つことができます。
- テーブル作成後でも作成可能です。
キャパシティユニットについて
- Read Capacity Unit (RCU)
- 結果整合性:1ユニットにつき、最⼤4KBのデータを1秒に2回読み込み可能です。
- 強い整合性:1ユニットにつき、最⼤4KBのデータを1秒に1回読み込み可能です。
- Write Capacity Unit (WCU)
- 1ユニットにつき、最⼤1KBのデータを1秒に1回書き込み可能です。
クエリパターン
具体例がないとわかりにくいと思うので、Primary Key(PK, SK)、LSI, GSIを用いたクエリパターンをご紹介します。
DynamoDBのクエリにおける大前提
- PKとSKを用いてのみ検索が行なえます。その他の属性に対して検索をかけることはできません。
- PKは必ず検索条件に指定する必要があります。SKのみを検索条件にすることはできません。
- PKの検索条件は完全一致条件のみです。
- SKの検索条件は完全一致および部分一致による検索が可能です。
シンプルな例(セカンダリインデックスを使用しない)
Primary Keyのみでクエリを行う最もシンプルな検索です。
ブログ記事テーブル (Primary Key:PKのみ)
ArticleId(PK) | Category | AuthorId | Title |
---|---|---|---|
A001 | Tech | U001 | Agents for Amazon BedrockがGAしました |
A002 | Tech | U002 | DynamoDBもベクター検索に対応 |
A003 | Food | U001 | 東京のラーメン有名店10選 |
このデータから、「ArticleIdがA001である記事」を取得することを考えます。ArticleIdはPKなので、シンプルにArticleIdがA001である記事を取得できます。
ブログ記事 いいねテーブル(Primary Key:PKとSK)
ArticleId(PK) | UserId(SK) | LikedAt |
---|---|---|
A001 | U001 | 2023-08-21T14:15:35.095+09:00 |
A001 | U002 | 2023-08-27T19:02:01.598+09:00 |
A003 | U001 | 2023-04-06T17:21:26.776+09:00 |
このデータから、「ArticleIdがA001である記事」を取得することを考えます。ArticleIdはPKなので、シンプルにArticleIdがA001である記事を取得できます。ただし注意点として、取得するデータはArticleIdがA001であるすべての記事であるため上記の場合は2記事取得されます。ArticleId(PK)/UserId(SK)の両方を指定することで該当する1記事が取得されます。
メリット
- 検索要件がPrimary Keyの一致のみで満たせる場合はシンプルな構成となる。
- 追加のインデックスを必要としない。
デメリット
- Primary Key(PK,SK)を用いた検索しかできない。
- Scan + Filterで他属性に対しても検索できるがコストパフォーマンスが悪い
ローカルセカンダリインデックス(LSI)を使用
ブログ記事 いいねテーブル(Primary Key:PKとSK)
ArticleId(PK) | UserId(SK) | LikedAt |
---|---|---|
A001 | U001 | 2023-08-21T14:15:35.095+09:00 |
A001 | U002 | 2023-08-27T19:02:01.598+09:00 |
A003 | U001 | 2023-04-06T17:21:26.776+09:00 |
このデータから、「ArticleIdがA001で、2023/08にいいねされた」データを取得することを考えます。仕様上から、セカンダリインデックスなしではLikedAtを検索条件にすることはできません。この問題は、LikedAtをローカルセカンダリインデックスにすることで解決できます。
ArticleId(PK) | UserId(SK) | LikedAt(LSI-1-SK) |
---|---|---|
A001 | U001 | 2023-08-21T14:15:35.095+09:00 |
A001 | U002 | 2023-08-27T19:02:01.598+09:00 |
A003 | U001 | 2023-04-06T17:21:26.776+09:00 |
この例だと、「ArticleIdがA001で、LSIに"2023-08"(部分一致)が含む記事」を検索できます。繰り返しになりますが、LSIはテーブル作成時にのみ作成可能なので注意です。
メリット
- Primary KeyのSKに加えてLSI-SKを追加できクエリの柔軟性が向上する
デメリット
- テーブル作成時にしか作成できない。
- LSIは1つのテーブルあたりに最大5個まで。
グローバルセカンダリインデックス(GSI)を使用
基本的なGSI使用例
ArticleId(PK) | Category | AuthorId | Title |
---|---|---|---|
A001 | Tech | U001 | Agents for Amazon Bedrockが GAしました |
A002 | Tech | U002 | DynamoDBもベクター検索に対応 |
A003 | Food | U001 | 東京のラーメン有名店10選 |
このデータから、「AuthorIdがU001である記事」を取得したいとします。このままの状態では仕様上、AuthorIdをkeyとして検索することはできません。AuthorIdがU001である記事に対してArticleId(PK)は異なるので、LSIも使用することはできません。この問題は、AuthorIdをPKとするグローバルセカンダリインデックスを作成することで解決できます。
ArticleId (PK) |
Category | AuthorId (GSI-1-PK) |
Title |
---|---|---|---|
A001 | Tech | U001 | Agents for Amazon BedrockがGAしました |
A002 | Tech | U002 | DynamoDBもベクター検索に対応 |
A003 | Food | U001 | 東京のラーメン有名店10選 |
これにより、GSI-1を用いてAuthorIdがU001である記事を取得できます。
また、GSIにはPKに加えてSKを設定することもできます。
ArticleId(PK) | Category | AuthorId | Title |
---|---|---|---|
A001 | Tech | U001 | Agents for Amazon BedrockがGAしました |
A002 | Tech | U002 | DynamoDBもベクター検索に対応 |
A003 | Food | U001 | おすすめレストランがあります |
このデータから、「AuthorIdがU001であり、CategoryがTechである記事」を取得したいとします。このニーズは、「AuthorIdをPK、CategoryをSK」とするグローバルセカンダリインデックスを作成することで解決できます。
ArticleId (PK) |
Category (GSI-1-SK) |
AuthorId (GSI-1-PK) |
Title |
---|---|---|---|
A001 | Tech | U001 | Agents for Amazon BedrockがGAしました |
A002 | Tech | U002 | DynamoDBもベクター検索に対応 |
A003 | Food | U001 | 東京のラーメン有名店10選 |
これにより、GSIを用いて「AuthorIdがU001であり、CategoryがTechである記事」を取得できます。
メリット
- Primary Key(PK,SK)に依存せず、新規でPK,SKを作成しそれを用いて検索できる
- GSIは、テーブル作成後に後から作成可能
デメリット
- GSIが増えることによって利用料および管理コストが増加。
- GSIは1つのテーブルあたりに最大20個まで。
GSI OVERLOADING
前述したGSIを利用したテーブル・インデックス設計は、検索要件の増加に応じてGSIが増加するデメリットがあります。例えば、Category、AuthorIdなど各属性に対してそれぞれ検索したい場合は各属性ごとにGSIを作成する必要があります。この問題はGSI OVERLOADINGというスキーマレスなDynamoDBらしいテーブル設計パターンで解決することができます。
具体的には以下のdataValueのようにひとつのGSIに複数の検索要件をもたせることができます。
ArticleId (PK, GSI-1-SK) |
DataType (SK) |
DataValue (GSI-1-PK) |
Title |
---|---|---|---|
A001 | categorty | Tech | |
A001 | authorId | U001 | |
A001 | title | Agents for Amazon BedrockがGAしました | |
A002 | categorty | Tech | |
A002 | authorId | U002 | |
A002 | title | DynamoDBもベクター検索に対応 | |
A003 | categorty | Food | |
A003 | authorId | U001 | |
A003 | title | 東京のラーメン有名店10選 |
メリット
- 少ないGSIで複数検索要件を満たすことができ、コストやクエリを最適化できる
デメリット
- テーブル構成が複雑になる
まとめ
- サーバレスアプリケーションでは、コストやスケーラビリティの観点からDynamoDBが使いやすい
- DynamoDBは同時に2つ(PK, SK)のkeyのみを用いてクエリする必要があるため、Primary KeyやLSI/GSIの設計が鍵
- クエリに使用するKey, Index
- Primary Key(SK, PK)
- LSI
- GSI, GSI OVERLOADING
よいサーバレスライフを!