DynamoDBと喧嘩しないスキーマ設計
DynamoDBはフルマネージドな「Key-Value Store」だ。
まず、KVSであることを前提に考える必要がある。
サマリー
1つのテーブルで全てのデータを持つ
AWS公式のベストプラクティスでもあるように、RDBみたいにテーブルを分けずに1つのテーブルで全てのデータを持つ。
例外的にアクセス頻度が高くて大容量が求められるものは分けた方がリソース面で効率がいいケースはあります( DynamoDB で時系列データを処理するベストプラクティス。 - Amazon DynamoDB )
そもそもJOIN出来ないから分けても意味ないっていう話ありますが、テーブルを分けないと一覧の取得が難しい、GSI(グローバルセカンダリーインデックス)も個数に制限があって、RDB脳だと分けた方が管理しやすい気持ちあるのですが、以下の問題があります。
- リソースはテーブル単位で与えられるため、複数テーブルあると、リソース面で効率が悪い。キャパシティーユニットは読み込み・書き込みが発生してなくても、テーブルに割り当てられてる分だけ時間で請求される(オンデマンドキャパシティーもあって利用分だけ支払えますが割高)
- オートスケールもテーブル単位になってしまい、スケールされてないテーブルにアクセスが集中するとスロットリングが発生する。
1テーブルに全てを収めるときのコツは、汎用的なカラムをパーティションキー + ソートキーに設定して、汎用的なカラムにGSIを設定する(GSIの多重定義)ことで、GSIを節約します。
- パーティションキー(PK): ID
- ソートキー(SK): ColumnName
この様に定義しておくと、PK+SKで一意なレコードを保存できるようになるため、IDにリソース名のPrefixを付けることで、複数のリソースを持てるようになります。
ID(PK) | ColumnName(SK) | ColumnData |
---|---|---|
User-1 | User-name | hikouki |
User-1 | User-age | 100 |
Order-1 | Order-orderedBy | User-1 |
Order-1 | Order-amount | 9999 |
ここで、前方一致でユーザー名を検索したい場合のGSIを考えます。
(※部分一致は scan でもサポートされてないはず)
ColumnNameをPK、ColumnDataをSKとするGSIを定義すれば、そのGSIを使って前方一致で一致するUserを取得できる。
ColumnName: (eq) User-name ColumnData (begins_with) hik => |User-1|User-name|hikouki| がヒットする。
GSIを設定しなくても、scanを使えば検索できるのですが、scanはテーブルフルスキャンになるため、データ量が多くなると線形にパフォーマンスが落ちる。
スキーマレスだけど、スキーマ設計はRDBよりも考える必要ある
RDBの場合、アプリケーションの事を考えずに設計できるので、スキーマ定義を早い段階から着手できる。 ようは、DBに対してどうアクセスされるか(アクセスパターン)を気にする必要が少ない。(indexとか、最適化する意味では別はですけど)
DynamoDBの場合、アクセスパターンを意識してスキーマを定義する必要があるため、 まずは、「どの画面でどんな情報を表示する必要があるか」を決める必要がある。
アクセスパターンを網羅できるようにパーティションキーやソートキーを設定して、必要に応じてGSIを設定していく感じになる。
注文履歴などの一覧をクエリする難易度が高い
パーティションキーは必ず完全一致でないとqueryでは検索できない。
なので、それを回避するために、リソース名をパーティションキーに設定する方法もある。
Resource(PK) | ID(SK) | UserName | OrderBy |
---|---|---|---|
users | User-1 | hikouki | |
users | User-2 | toby | |
orders | Order-1 | User-1 | |
orders | Order-2 | User-2 |
ただ、これで高頻度でアクセスされるとスロットリングが発生するはず。
パーティションキーによって分割してデータが登録されるため、パーティションキーが同じデータが多くあると、そのパーティションにアクセスが集中して I/O キャパシティが効率的に使われずにホットパーティション化して、スロットリングが発生します。
ユーザーに紐付かないトランザクションデータを一覧したい場合は、大抵高機能な一覧画面になるので、ESを使えばいい。
ユーザーに紐づく場合はユーザーIDをPKにして検索項目をSKにしたGSIを作って検索する。