DynamoDB のシングルテーブル設計
SQL から来ると、エンティティごとに1テーブル — customers、orders、order_items — という直感が働きます。DynamoDB では、その直感はたいてい間違いです。すべてのエンティティを格納し、オーバーロードしたキーのプレフィックスで区別する1つのテーブルなら、親とその子を1回の Query で取得できます。結合も N+1 もありません。
DynamoDB のシングルテーブル設計とは?
シングルテーブル設計は、すべてのエンティティ — 顧客、注文、注文明細 — を1つの DynamoDB テーブルに格納し、オーバーロードしたとソートキーのプレフィックスで区別します。キーがエンティティではなくアクセスパターンを中心に設計されているため、親とそのすべての子は1つのに収まり、単一の Query で返ってきます。結合も N+1 の読み取りもありません。
発想
汎用的なキー名(PK、SK)を選び、エンティティの種類を値にエンコードします。
| PK | SK | attributes |
|---|---|---|
| CUSTOMER#42 | PROFILE | name, email, plan |
| CUSTOMER#42 | ORDER#2026-001 | total, status |
| CUSTOMER#42 | ORDER#2026-002 | total, status |
これで、1つの Query PK = "CUSTOMER#42" が、プロファイルとすべての注文を1回の課金対象の読み取りで返します。SK begins_with "ORDER#" はそれを注文だけに絞り込みます。
視覚的には、オーバーロードされたアイテムが1つのの下に、単一のとして積み重なります。
パーティションを1回読むだけで、顧客とすべての注文がまとめて返ってきます。
オーバーロードされた GSI
同じ手口はインデックスでも使えます。アイテムに汎用的な GSI1PK/GSI1SK を載せれば、各アイテムがそれらの属性に何を書き込むかに応じて、1つの が複数のアクセスパターンを担います。
| PK | SK | GSI1PK | GSI1SK |
|---|---|---|---|
| ORDER#001 | METADATA | STATUS#OPEN | 2026-01-04 |
| ORDER#002 | METADATA | STATUS#OPEN | 2026-01-05 |
これで Query GSI1 WHERE GSI1PK = "STATUS#OPEN" が、オープンな注文を日付順に一覧します。ベーステーブルには答えられないパターンです。別のエンティティは GSI1 を独自の意味で再利用できます(例: CATEGORY#books)。1つのインデックスで、多くのクエリを。
多対多: 隣接リスト
関係(多くのチームに属するユーザー、多くのユーザーを持つチーム)については、id を入れ替えてエッジを2回書きます: PK=USER#1, SK=TEAM#9 と PK=TEAM#9, SK=USER#1。どちらの側をクエリしても他方が一覧されます。結合テーブルの DynamoDB 版です。
シングルテーブルにしないとき
無料ではありません。1つのオーバーロードしたテーブルは、推論しづらく、進化させづらく、分析に不向きです。アクセスパターンが本当に不明だったり絶えず変わったり、データがほぼ分析用途だったりするなら、テーブルを分ける(あるいは別のストアを使う)ほうが正気の判断になり得ます。シングルテーブルが勝つのは、パターンが既知で高ボリュームのときです。
誤った形のコスト
テーブルを分けてモデル化すると、顧客を組み立て直すために Scan かクライアント側の結合を強いられ、それが Scan の落とし穴です。まずアクセスパターンをモデル化し、それからキーを設計して各パターンを Query にしてください。(モデル化していなかったアドホックなエンティティ横断の質問には、DynoTableのSQL WorkbenchがJOINをクライアント側で実行します — 探索のために再モデル化を待つ必要はありません。)
設計そのものは無料のシングルテーブル設計ツールで 下書きしましょう — アクセスパターンのリストを、例のアイテムとコストのヒント付きの PK/SK/GSIプランに変換してくれます。 これらのアイテムが1回の読み取りでいくらかかるかは アイテムサイズ&キャパシティ計算機で見積もり、 シングルテーブルのスキーマをブラウズしてオーバーロードされたコレクションを並べて見るには DynoTable を試してください。