DynamoDBでシンプルなレビューデータ用DBを構築する
AWS DVAの出題範囲の1つでもある、AWSのサービスによる開発に関する内容です。
DynamoDBの入門として、簡単なデータベースを作成します。
レビューシステム用のデータベースを想定して設計します。
構築するデータベース
データベースには、以下のデータを格納します。
- user:ユーザー名
- service:サービス名
- datetime:投稿日時
- rating:評価値(1~5)
- comment:評価文
具体的には以下のようなデータが格納されるイメージです。
データベースに対して、以下のクエリが実行される想定です。
- 特定のユーザーが投稿した全レビューを取得する。
- 特定のユーザーが特定のサービスに対して投稿したレビューを取得する。
- 特定のユーザーが投稿したレビューの中で、最近のレビューから順番に取得する。
- 特定のサービスに対するレビューの中で、評価の高いレビューから順番にを取得する。
DynamoDB用語の整理
DynamoDBのキー
まずDynamoDBにおけるキーについて確認します。パーティションキーとソートキーがあります。
パーティションキー
DynamoDBでは、テーブル内に保存されている項目を、一意に特定できるプライマリキーを定義する必要があります。プライマリキーはパーティションキー単体、またはパーティションキーとソートキーの組み合わせで定義することができます。パーティションキーは、データが保存される物理的なパーティションを定義するキーです。
DynamoDB は、パーティションキーの値を内部ハッシュ関数への入力として使用します。ハッシュ関数からの出力により、項目が保存されるパーティション (DynamoDB 内部の物理ストレージ) が決まります。
プライマリキー
例えばユーザーIDといったユニークなデータをパーティションキーに指定することによって、パーティションキー単独でプライマリキーとして動作します。
ソートキー
ソートキーはパーティションキーで定義されたパーティション内の、データの順序を定義するキーです。
DynamoDB は、パーティションキーバリューを内部ハッシュ関数への入力として使用します。ハッシュ関数からの出力により、項目が保存されるパーティション (DynamoDB 内部の物理ストレージ) が決まります。同じパーティションキー値を持つすべての項目は、ソートキー値でソートされてまとめて保存されます。
プライマリキー
パーティションキーとソートキーに指定されたデータを組み合わせて、項目が一意に特定することができる場合、両者を組み合わせてプライマリキーとして動作します。これを複合プライマリキーと呼びます。
セカンダリインデックス
次にセカンダリインデックスについて確認します。グローバルセカンダリインデックスとローカルセカンダリインデックスがあります。前提として、DynamoDBはKey -Valueタイプのデータベースですので、プライマリキーを指定し、該当する項目を取得するという使い方が基本となります。加えて、特定の条件を満たす項目を検索するクエリを実行することもできます。クエリはソートキーに対してのみ実行することができます。ただソートキーは1つの属性に対してだけ設定することができます。そのためソートキーとして指定した属性以外には、クエリを実行することができないということになります。これを解決するためにセカンダリインデックスを定義します。
グローバルセカンダリインデックス
1つ目はグローバルセカンダリインデックスです。
グローバルセカンダリインデックス – パーティションキーおよびソートキーを持つインデックス。テーブルのものとは異なる場合があります。
セカンダリインデックス
グローバルセカンダリインデックスを定義することによって、元々のパーティションキー・ソートキーとは別に、新たに検索用のパーティションキー・ソートキーを定義することになり、こちらを使ってクエリを実行できるようになります。
ローカルセカンダリインデックス
2つ目はローカルセカンダリインデックスです。
ローカルセカンダリインデックス – パーティションキーはテーブルと同じですが、ソートキーが異なるインデックスです。
セカンダリインデックス
ローカルセカンダリインデックスを定義することによって、既存のパーティションキーに2つ目のソートキーを定義することになり、既存パーティションを別属性を使ってクエリすることができるようになります。
DynamoDBを設計する上でのポイント
まずプライマリキーを考える
先述の通り、プライマリキーはテーブル内の項目を一意に特定するためのものです。プライマリキーは、パーティションキー単体、またはパーティションキーおよびソートキーを組み合わせた複合プライマリキーとして定義します。
今回のシナリオでテーブルに格納するデータは4つありますが、その中で、プライマリキーの候補になり得るデータは以下の2つです。
- user
- service
ただどちらも単独では項目を一意に特定することはできません。例えば1ユーザーが多数のサービスに対してレビューを行うこともあり得ますし、1つのサービスに対して多数のユーザーがレビューを行うこともあり得ます。よって2つの属性を組み合わせてプライマリキーとします。2データを組み合わせることによって、レビューを一意に特定することができます。
そして改めて今回の検索要件を見ると、ユーザー情報を中心とするクエリがいくつかあることがわかります。具体的には、①、②、③です。ですからuserをパーティションキーとし、serviceをソートキーとします。これで検索パターン①および②に対応することができます。
ローカルセカンダリインデックスを定義して、2つ目のソートキーでクエリする
次に検索パターン③への対応を考えます。本パターンはユーザー情報をベースとして、レビューの投稿日時に対してクエリを実行することになります。先述の通り、クエリはソートキーに指定した属性に対してのみ実行できます。ただソートキーはserviceとしましたので、現状では、日時情報を使ってクエリは実行できません。
今回はローカルセカンダリインデックスを作成して、本検索パターンに対応します。ローカルセカンダリインデックスですので、パーティションキーはuserのまま、新たなソートキーとしてdatetimeを指定します。これで検索パターン③のクエリが実行できるようになります。
グローバルセカンダリインデックスを定義して、2つ目のプライマリキーでクエリする
最後に検索パターン④への対応を考えます。本パターンはサービス情報をベースとして、評価値に対してクエリを実行することになります。現状では、本検索用のインデックスが用意されていないため、クエリを実行することはできません。
今回はグローバルインデックスを作成して、本検索パターンに対応します。グローバルインデックスですので、新たなパーティションキーとしてserviceを、同じく新たなソートキーとしてratingを指定します。これで検索パターン④のクエリが実行できるようになります。
レビューデータ用DBの設計まとめ
設定上のポイントをまとめます。まずプライマリキーですが、以下とします。
- パーティションキー:user
- ソートキー:service
次にローカルセカンダリインデックスは以下とします。
- パーティションキー:user
- ソートキー:datetime
最後にグローバルセカンダリインデックスは以下とします。
- パーティションキー:service
- ソートキー:rating
CloudFormationテンプレートファイル
ハンズオン用のDynamoDBテーブルをCloudFormationで構築します。
以下のURLにCloudFormationテンプレートファイルと、DynamoDBに投入するサンプルデータ用のJSONファイルを設置しています。
https://github.com/awstut-an-r/awstut-dva/tree/main/03/002
テンプレートファイルのポイント解説
今回のアーキテクチャを構成するための、テンプレートファイルのポイントを取り上げます。
キーに指定するものだけ属性に指定する
テーブルの属性に関するパラメータを確認します。
Resources:
Table:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: user
AttributeType: S
- AttributeName: service
AttributeType: S
- AttributeName: datetime
AttributeType: S
- AttributeName: rating
AttributeType: N
#- AttributeName: comment
# AttributeType: S
Code language: YAML (yaml)
AttributeDefinitionsプロパティでテーブルの属性に関する設定を行います。
本プロパティの注意点は、テーブルに予定している全属性を指定するわけではないという点です。
AttributeDefinitions
A list of attributes that describe the key schema for the table and indexes.
AWS::DynamoDB::Table
つまりプライマリキーやセカンダリインデックスを定義する上で、キーとして指定する属性のみを、本プロパティに設定します。今回のテーブルで言いますと、5つの属性が作成されることになりますが、プライマリキーやセカンダリインデックスとして使用される属性は4つ(user, service, datetime, rating)です。ですからcommentは本プロパティには指定しません。なおcommentまで指定してしまうと、エラーが発生し、正常にCloudFormationスタックが作成できませんので、ご注意ください。
ハッシュキーがパーティションキー、レンジキーがソートキー
次にプライマリキーの設定を確認します。
Resources:
Table:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: user
KeyType: HASH
- AttributeName: service
KeyType: RANGE
Code language: YAML (yaml)
KeySchemaプロパティでプライマリキーを定義します。同プロパティ内のKeyTypeプロパティでパーティションキーまたはソートキーを指定し、対応する属性を指定します。両キーの指定は以下の通りです。
- パーティションキー:HASH(ハッシュキー)
- ソートキー:RANGE(レンジキー)
パーティションキーがハッシュキー、ソートキーがレンジキーと表記される理由は、以前はそのように呼ばれていた名残です。今回はパーティションキーにuser、ソートキーにserviceを指定します。
セカンダリインデックスを作成する場合は射影を指定する
続いてローカルセカンダリインデックス(LSI)を確認します。
Resources:
Table:
Type: AWS::DynamoDB::Table
Properties:
LocalSecondaryIndexes:
- IndexName: lsi-user-datetime
KeySchema:
- AttributeName: user
KeyType: HASH
- AttributeName: datetime
KeyType: RANGE
Projection:
NonKeyAttributes:
- service
- comment
ProjectionType: INCLUDE
Code language: YAML (yaml)
ローカルセカンダリインデックスの設定は、LocalSecondaryIndexesプロパティで行います。KeySchemaプロパティで、元々のパーティションキーである属性と、LSIのソートキーとなる属性を指定します。今回はパーティションキーがuserで、LSIのソートキーにdatetimeを指定します。
セカンダリインデックスを定義する上でのポイントは、射影(プロジェクション)です。射影はProjectionプロパティで指定します。射影はセカンダリインデックスを使用して検索を実行した場合に、プライマリキー以外の属性もクエリ結果に含めるかどうかに関する設定です。
今回で言いますと、射影の設定を行わなかった場合、userおよびdatetime属性のみがクエリ結果で取得できることになります。ただこれではレビューデータとして重要な属性が足りません。そこでProjectionTypeプロパティに「INCLUDE」を、NonKeyAttributesプロパティに属性を指定することで、射影に含める属性を指定することができます。今回はserviceおよびcomment属性を指定することで、クエリ結果に両属性を含めることができるようになります。
グローバルセカンダリインデックス(GSI)の設定はLSIと同様です。
Resources:
Table:
Type: AWS::DynamoDB::Table
Properties:
GlobalSecondaryIndexes:
- IndexName: gsi-service-rating
KeySchema:
- AttributeName: service
KeyType: HASH
- AttributeName: rating
KeyType: RANGE
Projection:
NonKeyAttributes:
- user
- comment
ProjectionType: INCLUDE
Code language: YAML (yaml)
今回はGSIのパーティションキーにserviceを、ソートキーにratingを指定します。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
今回作成されたDynamoDBテーブルは「dva-03-002-table」でした。AWS Management Consoleから作成状況を確認します。
正常にテーブルが作成されていることが確認できます。続いてセカンダリインデックスも確認します。
両者ともに正常に作成されていることがわかります。パーティションキー・ソートキー・射影も指定通りに設定されています。
テーブルにテストデータを書き込む
AWS CLIから作成したテーブルにデータを投入します。
$ aws dynamodb batch-write-item \
--request-items file://request-items.json
Code language: Bash (bash)
書き込むデータはJSONファイル(request-items.json)として用意し、request-itemsオプションに同ファイルを渡します。
テーブル操作の準備
AWS Management Consoleからテーブルに対して操作を行う場合は、「Explore table items」ボタンを押下します。
このページからテーブルに対して、スキャンやクエリを実施することができます。
テーブル操作:scan
準備が整いましたので、テーブルに検索を実行します。まずテーブルの全データを取得するスキャンを実行します。
「Scan」を選択後、「Run」ボタンを押下します。
ページ下部に実行結果が表示されます。正常に全データ(15件)が表示されました。スキャンでは射影といった設定はなく、全属性が取得できます。
テーブル操作:query ①
続いてクエリの動作を確認します。ユーザーaaaのレビューデータを全て取得することを考えます。
「Query」を選択後、パーティションキーであるuserのみを指定します。
クエリの実行結果を見ると、ユーザーaaaが投稿した5つのレビューデータが取得できていることがわかります。以上より、検索要件の①「特定のユーザーが投稿した全レビューを取得する。」を満たすことができました。
テーブル操作:query ②
ユーザーaaaのEC2に対するレビューを取得することを考えます。
パーティションキーであるuserに「aaa」を、ソートキーであるserviceに「EC2」を指定します。
クエリの実行結果を見ると、ユーザーaaaが投稿したEC2へのレビューデータが取得できていることがわかります。以上より、検索要件の②「特定のユーザーが特定のサービスに対して投稿したレビューを取得する。」を満たすことができました。
テーブル操作:query ③
ユーザーbbbのレビューを最新のものから順に取得することを考えます。
ローカルセカンダリインデックスに対してクエリを実行しますので、「lsi-user-datetime」を選択します。userに「bbb」を指定後、ソートを降順に指定します。
クエリの実行結果を見ると、ユーザーbbbが投稿したレビューの内、最新のものから順番に取得できていることがわかります。以上より、検索要件の③「特定のユーザーが投稿したレビューの中で、最近のレビューから順番に取得する。」を満たすことができました。
テーブル操作:query ④
サービスIAMに対する高評価のレビューを取得することを考えます。
グローバルセカンダリインデックスに対してクエリを実行しますので、「gsi-service-rating」を選択します。serviceに「IAM」を指定後、ソートを降順に指定します。
クエリの実行結果を見ると、IAMに対するレビューの内、評価値が高いものから3つ取得できていることがわかります。以上より、検索要件の④「特定のサービスに対するレビューの中で、評価の高いレビューを取得する。」を満たすことができました。
まとめ
DynamoDBのプライマリキーおよびセカンダリインデックスについて確認しました。
検索要件に応じてプライマリキー・セカンダリインデックスを設定することによって、さまざまなクエリに対応することができることを確認しました。