Web アプリ開発のための Amazon DynamoDB 設計を考える
Author : 嶋田 朱里 (監修 : 大渕 麻莉)
AWS 長期インターン生の嶋田です。
私は現在就活中なので、インターンや選考に応募するためにエントリーシートを書くことが多いです。エントリーシートでは「学生時代に頑張ったことは ?」など、200 ~ 500 字程度で答える設問がありますが、1 つの設問に対して複数の回答文候補を作成したり、それらを修正したりする中で、多くの回答文ができてしまい、以下のような課題を感じていました。
- 複数の回答文の中でどれを提出したのか、わからなくなってしまう。
- 作成した回答文を時系列でわかりやすく見たい。
面接前など「提出した作文を見直したい !」という時に「確認したい回答文が見つからないまま面接の時間になってしまった」という悲しい事態が起こらないよう、エントリーシートを管理するためのウェブアプリを作りました。
データベースとして Amazon DynamoDB を利用したのですが、DB 初心者の私はその設計に苦労しました。そこで本記事ではどのような思考で DB 設計を行ったのかについてご紹介します。「DynamoDB の概要は理解したけど、実際どうやって設計するの ?」と悩まれている DB 初心者の方の参考になれば嬉しいです。
1. アプリの概要
DB 設計についてお話しする前に、アプリの内容をご紹介します。
まずログイン画面でアカウントへログインし、設問画面に遷移します。
設問画面では、過去に登録した設問文とそれがどの企業の何のプロジェクト (サマーインターンなど) なのか、そして登録日時が一覧で表示されます。新規課題の追加は画面上部のフォームから行います。
各設問文の上にある「回答文詳細」のボタンをクリックすると、回答画面に遷移します。
ここでは該当の設問への回答文の一覧が作成日時順で表示されます。これが上記の課題 2 の解決方法になります。
「回答文に修正を加えたいが、念の為現在の回答文も残しておきたい」などの場合は、画面上部のフォームに新たな回答文を記入し追加することで、新規回答文を作成します。
各回答文の上には「デフォルトに設定」というボタンがあり、これをクリックすると該当回答文の背景色がオレンジになります。これは 1 つの設問の中で 1 つの回答文にしか表示されません。そのため、企業に提出した回答文や現時点で一番良い回答文などの記録に使うことができ、上記の課題 1 の解決方法となります。(以下ではこの機能を「デフォルト設定」、デフォルト設定された回答文を「デフォルト回答文」と呼びます。)
クリックすると拡大します
2. アーキテクチャ
アーキテクチャは図のようになっています。
AWS Amplify によりサーバーレス構成でウェブアプリを構築しています。
URL にアクセスするとログイン画面が表示されます。このログイン機能は Amazon Cognito の JavaScript ライブリを使用しています。
新たな設問・回答文の登録や登録データ一覧の取得などの操作は、Amazon API Gateway と AWS Lambda、DynamoDB で行います。アプリケーションからの API を API Gateway で受け、Lambda で処理、必要な情報を DynamoDB に保存するという流れです。
3. Amazon DynamoDB の設計に挑戦
3-1. Amazon DynamoDB とは ?
Amazon DynamoDB はマネージドサービス型の NoSQL データベースです。データを探索するために必要なプライマリキーは「パーティションキー (PK)」または「パーティションキーとソートキー (SK) の組み合わせ」です。PK, SK について知りたい方はこちら をご覧ください。
公式ガイド では NoSQL 設計の重要概念として次の 2 点が挙げられています。
- アプリケーションのユースケースを理解してからスキーマの設計を開始する。
- DynamoDB ではできるだけ少ないテーブルを維持する。
開発前は「DynamoDB は NoSQL でスキーマレスだから事前設計は不要でしょ」と思っていたのですが、重要概念 1 にある通り、ユースケースを洗い出してからスキーマ設計をする必要があります。重要概念 2 に基づき、今回使用したテーブルは 1 つです。
3-2. ユースケースの洗い出し
まずはユースケースの洗い出しです。「ユースケースって具体的に何を考えればいいの?」と思いましたが、
- アプリの操作手順を具体的に想像する。
- それぞれの操作段階で「DB からどのようなデータを得たいのか」を考える。
- 「得たいデータを取得するための条件として指定するデータは何か」を考える。
という流れで、洗い出しを行いました。
今回想定したユースケースは以下の 7 個です。1〜2 は設問画面、3〜7 は回答画面で実装しています。
想定したユースケース
場面 | 条件データ | 応答データ | |
1 | 設問画面読み込み時に、ユーザーが登録した全ての「企業名・プロジェクト名・設問文・設問 ID・登録日時」を取得 | ユーザー ID | 企業名、プロジェクト名、設問文、設問 ID、設問登録日時 |
2 | 指定した設問の削除 | ユーザー ID、設問 ID | なし |
3 | 指定した設問に紐づくすえての回答文を取得 | ユーザー ID、設問 ID | 回答文、回答文の文字数、回答 ID、回答登録日時 |
4 | デフォルト回答文の新規設定 デフォルト回答文が既にある場合は、デフォルト回答文の変更 |
ユーザー ID、設問 ID | なし |
5 | 指定した回答文の削除 | ユーザー ID、設問 ID、回答 ID | なし |
6 | 指定した設問に紐づくすべての回答文を削除 | ユーザー ID、設問 ID | なし |
7 | 指定した設問のデフォルト回答文を取得 | ユーザー ID、設問 ID | (デフォルト回答文の) 回答文、回答文の文字数、回答 ID、回答登録日時 |
3-3. パーティションキー (PK)、ソートキー (SK) を考える
次にスキーマ設計です。今回はセカンダリインデックス等は使わないため、オリジナルテーブルの PK と SK について考えます。
試行錯誤の結果、PK, SK は以下の 3 種類になりました。1 と 2 の下段はデータの例です。
ユーザー ID は、ログイン後に Cognito から返ってくる ID トークンの sub という項目を利用しています。ID トークンや sub についてはこちら をご覧ください。
<PK, SK>
パートティションキー (PK) | ソートキー (SK) | その他の属性 | |
1 | user#{ユーザーID} | theme#{設問ID} | 企業名、プロジェクト名、設問文 |
user#a4d77439-8e06-4998-ad07-a71007c57a83 | theme#2021-09-16T15:07:34.333Z | ||
2 | user#{ユーザーID}_theme#{設問ID} | comp#{回答ID} | 回答文、回答文の文字数 |
user#a4d77439-8e06-4998-ad07-a71007c57a83_theme#2021-09-16T15:07:34.333Z | comp#2021-09-16T15:23:32.249Z | ||
3 | user#{ユーザーID}_theme#{設問ID} | default | 回答ID |
この設計にあたり工夫した点を 3 つ紹介します。
3-3-1. タイムスタンプを ID として使用
No.1, 2 のデータ例では、{設問ID}, {回答ID} がタイムスタンプになっており、それぞれが登録された日時を示しています。このようにした理由として以下の 2 点が挙げられます。
- タイムスタンプはミリ秒単位で取得する。同一ユーザーがミリ秒単位の同じタイミングで複数の登録をすることはないを判断した。
- タイムスタンプを SK に設定することで、設問や回答文の登録日時でソートできる (DynamoDB では、同一 PK 内の順序は SK で決まっている。)
DB から取得したタイムスタンプをアプリの登録日時表示に合う形に整形する処理はアプリケーション側で実装しています。
3-3-2. デフォルト情報をどのように持つか
No.3 の SK は “default” という固定文字です。このアイテム (行) はデフォルト回答文の ID を保持しています。
デフォルト回答文情報の他の持ち方として、No.2 の「その他の属性」に「デフォルト回答文か否か」という属性を追加し、「デフォルト回答文なら true。そうでない場合は false」とする方法もあります。
しかし、ユースケース 7「指定した設問のデフォルト回答文を取得」の際に、「デフォルト回答文か否か」の属性で「多くの false の中から 0 または 1 つの true を探す」というフィルター処理が必要となり、クエリ効率が良くないです。そのため、デフォルト回答文の情報は 1 つのアイテムに設定しました。
3-3-3. 3 条件以上でのクエリ
DynamoDB の設計で戸惑ったのは、「3 つ以上の条件を指定するクエリはどのように行うのか」ということでした。
今回は 2 つのデータを組み合わせた新たなデータを作ることで、3 条件以上のクエリを実装しました。
3 条件以上のクエリを行うユースケースとそのクエリ対象のアイテムを以下に示します。
<3 条件以上のユースケース>
場面 | 条件データ | 応答データ | |
5 | 指定した回答文の削除 | ユーザーID、設問ID、回答ID | なし |
<クエリ対象のアイテム>
パーティションキー (PK) | ソートキー (SK) | その他の属性 | |
2 | user#{ユーザーID}_theme#{設問ID} | comp#{回答ID} | 回答文、回答文の文字数 |
クエリ対象のアイテムの PK (user#{ユーザーID}_theme#{設問ID}) は、ユーザー ID と設問 ID の 2 つのデータの組み合わせになっています。クエリでは、PK でユーザー ID と設問 ID、SK で回答 ID による絞り込みが行われるため、3 条件を指定するクエリができます。
データの組み合わせ方について、以下のように PK をユーザー ID、SK を設問 ID と回答 ID とする方法も考えられます。しかし、この設計では、ユースケース 1「設問画面読み込み時に、ユーザーが登録した全ての『企業名・プロジェクト名・設問文・設問ID・登録日時』を取得」で PK に user#{ユーザーID} を指定してクエリをする際、theme#{設問ID}_comp#{回答ID} 等の不要なデータも含まれてしまいます。各ユースケースにおいて、クエリ結果に「期待するデータが不足なく含まれる」ことはもちろんですが、「期待するデータ以外のものはできる限り含まない」という点も考慮し、上記の組み合わせを採用しました。
<PK をユーザーID、SK を設問ID と回答ID とした場合>
パーティションキー (PK) | ソートキー (SK) | その他の属性 | |
2 | user#{ユーザーID} | theme#{設問ID}_comp#{回答ID} | 回答文、回答文の文字数 |
まとめ
本記事では、エントリーシート管理ウェブアプリとその DynamoDB 設計についてご紹介しました。
DynamoDB を使う場合は、効率の良いクエリができるよう、ユースケースに基づいてパーティションキーとソートキーを慎重に設計する必要があります。NoSQL はスキーマレスですが、だからと言って事前設計不要というわけではないという点が大きな学びでした。
この記事が「DynamoDB で DB 設計をしてみよう!」と思っていただくきっかけになれば幸いです。
筆者プロフィール
嶋田 朱里
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト 長期インターン
東京大学大学院 修士 1 年
ベンチャー企業でのインターンを通して AWS に興味を持ち、AWS の長期インターンに参加。インターンでは上記ウェブアプリの開発や IoT 案件の支援を経験。
AI 技術を用いた社会課題の解決に興味があり、大学院では画像認識を使ったシステムの開発に取り組む。
好きなものは読書、ドラマ、ラジオ、ダンス、歌、歩くこと。
監修者プロフィール
大渕 麻莉
アマゾン ウェブ サービス ジャパン合同会社
機械学習ソリューションアーキテクト
組込みソフトウェア開発から画像処理アルゴリズム開発を経てクラウドに到達し、2019
年にアマゾン ウェブ サービス ジャパン合同会社に入社。主に製造業のお客様の機械学習導入・運用の技術サポートを担当。
脳内 CPU の半分が猫のことで占められており、視界に入るすべての生き物がうちの猫に見えるという日々を過ごしている。
AWS を無料でお試しいただけます