DynamoDB でリレーショナルデータをモデル化する例
この例では、Amazon DynamoDB でリレーショナルデータをモデル化する方法について説明します。DynamoDB テーブルの設計は、「リレーショナルモデル化」に示されているリレーショナルオーダーのエントリスキーマに対応しています。この設計では、単一の隣接リストではなく複数の特殊なテーブルを使用し、戦略的 GSI を活用してすべてのアクセスパターンを効率的に処理しながら、明確な運用上の境界を提供します。
設計アプローチでは、集約指向の原則を使用し、厳格なエンティティ境界ではなくアクセスパターンに基づいてデータをグループ化します。主な設計上の決定事項には、アクセス相関が低いエンティティに個別のテーブルを使用すること、常に一緒にアクセスされる場合に関連データを埋め込むこと、関係を特定するための項目コレクションを使用することが含まれます。
次のテーブルとそれに付随するインデックスは、リレーショナルオーダーのエントリスキーマをサポートしています。
従業員テーブルの設計
従業員テーブルには、従業員情報が項目ごとに 1 つのエンティティとして保存され、従業員を直接検索できるように最適化し、戦略的 GSI を通じて複数のクエリパターンがサポートされます。このテーブルは、独立した運用特性を持ち、エンティティ間のアクセス相関が低いエンティティに対して個別のテーブルを設計する原則を示しています。
このテーブルでは、各従業員が個別のエンティティであるため、ソートキーなしでシンプルなパーティションキー (employee_id) を使用します。4 つの GSI により、さまざまな属性による効率的なクエリが可能になります。
EmployeeByName GSI - すべての従業員属性で INCLUDE 射影を使用して、従業員の詳細を名前で完全に取得し、employee_id をソートキーとして重複する可能性のある名前を処理します。
EmployeeByWarehouse GSI - 必須属性 (名前、job_title、hire_date) のみを含む INCLUDE 射影を使用して、倉庫ベースのクエリをサポートしながらストレージコストを最小限に抑えます
EmployeeByJobTitle GSI - レポートと組織分析のための INCLUDE 射影を使用してロールベースのクエリを有効にします
EmployeeByHireDate GSI - hire_date をソートキーとする静的パーティションキー値「EMPLOYEE」を使用して、最近の採用者に対して効率的な日付範囲クエリを有効にします。従業員の追加/更新は通常 1,000 WCU 未満であるため、ホットパーティションの問題が発生することなく、単一のパーティションで書き込み負荷を処理できます。
| employee_id (PK) | 名前 | phone_number | Warehouse_id | job_title | hire_date | entity_type |
|---|---|---|---|---|---|---|
| emp_001 | John Smith | ["+1-555-0101"] | wh_sea | マネージャー | 2024-03-15 | EMPLOYEES |
| emp_002 | Jane Doe | ["+1-555-0102", "+1-555-0103"] | wh_sea | アソシエイト | 2025-01-10 | EMPLOYEES |
| emp_003 | Bob Wilson | ["+1-555-0104"] | wh_pdx | アソシエイト | 2025-06-20 | EMPLOYEES |
| emp_004 | Alice Brown | ["+1-555-0105"] | wh_pdx | スーパーバイザー | 2023-11-05 | EMPLOYEES |
| emp_005 | Charlie Davis | ["+1-555-0106"] | wh_sea | アソシエイト | 2025-12-01 | EMPLOYEES |
| 名前 (GSI-PK) | employee_id (GSI-SK) | phone_number | Warehouse_id | job_title | hire_date |
|---|---|---|---|---|---|
| Alice Brown | emp_004 | ["+1-555-0105"] | wh_pdx | スーパーバイザー | 2023-11-05 |
| Bob Wilson | emp_003 | ["+1-555-0104"] | wh_pdx | アソシエイト | 2025-06-20 |
| Charlie Davis | emp_005 | ["+1-555-0106"] | wh_sea | アソシエイト | 2025-12-01 |
| Jane Doe | emp_002 | ["+1-555-0102", "+1-555-0103"] | wh_sea | アソシエイト | 2025-01-10 |
| John Smith | emp_001 | ["+1-555-0101"] | wh_sea | マネージャー | 2024-03-15 |
| Warehouse_id (GSI-PK) | employee_id (GSI-SK) | 名前 | job_title | hire_date |
|---|---|---|---|---|
| wh_pdx | emp_003 | Bob Wilson | アソシエイト | 2025-06-20 |
| wh_pdx | emp_004 | Alice Brown | スーパーバイザー | 2023-11-05 |
| wh_sea | emp_001 | John Smith | マネージャー | 2024-03-15 |
| wh_sea | emp_002 | Jane Doe | アソシエイト | 2025-01-10 |
| wh_sea | emp_005 | Charlie Davis | アソシエイト | 2025-12-01 |
| job_title (GSI-PK) | employee_id (GSI-SK) | 名前 | Warehouse_id | hire_date |
|---|---|---|---|---|
| アソシエイト | emp_002 | Jane Doe | wh_sea | 2025-01-10 |
| アソシエイト | emp_003 | Bob Wilson | wh_pdx | 2025-06-20 |
| アソシエイト | emp_005 | Charlie Davis | wh_sea | 2025-12-01 |
| マネージャー | emp_001 | John Smith | wh_sea | 2024-03-15 |
| スーパーバイザー | emp_004 | Alice Brown | wh_pdx | 2023-11-05 |
| entity_type (GSI-PK) | hire_date (GSI-SK) | employee_id | 名前 | Warehouse_id |
|---|---|---|---|---|
| EMPLOYEES | 2023-11-05 | emp_004 | Alice Brown | wh_pdx |
| EMPLOYEES | 2024-03-15 | emp_001 | John Smith | wh_sea |
| EMPLOYEES | 2025-01-10 | emp_002 | Jane Doe | wh_sea |
| EMPLOYEES | 2025-06-20 | emp_003 | Bob Wilson | wh_pdx |
| EMPLOYEES | 2025-12-01 | emp_005 | Charlie Davis | wh_sea |
顧客テーブルの設計
顧客テーブルは、Account_rep_id を戦略的に非正規化して顧客情報を維持し、アカウント担当者のクエリを効率的に実行できるようにします。この設計の選択により、わずかなストレージオーバーヘッドと引き換えにクエリパフォーマンスが向上し、顧客データとアカウント担当者データの結合が不要になります。
このテーブルは、リスト属性を使用して顧客ごとに複数の電話番号をサポートしており、DynamoDB のスキーマの柔軟性を示しています。単一の GSI により、アカウント担当者のワークフローが有効になります。
CustomerByAccountRep GSI - 名前と E メール属性を含む INCLUDE 射影を使用して、完全な顧客レコードの取得を必要とせずに、アカウント担当者の顧客管理をサポートします。
| customer_id (PK) | 名前 | phone_number | E メール | account_rep_id |
|---|---|---|---|---|
| cust_001 | Acme Corp | ["+1-555-1001"] | contact@acme.com | rep_001 |
| cust_002 | TechStart Inc | ["+1-555-1002", "+1-555-1003"] | info@techstart.com | rep_001 |
| cust_003 | Global Traders | ["+1-555-1004"] | sales@globaltraders.com | rep_002 |
| cust_004 | BuildRight LLC | ["+1-555-1005"] | orders@buildright.com | rep_002 |
| cust_005 | FastShip Co | ["+1-555-1006"] | support@fastship.com | rep_003 |
| account_rep_id (GSI-PK) | customer_id (GSI-SK) | 名前 | E メール |
|---|---|---|---|
| rep_001 | cust_001 | Acme Corp | contact@acme.com |
| rep_001 | cust_002 | TechStart Inc | info@techstart.com |
| rep_002 | cust_003 | Global Traders | sales@globaltraders.com |
| rep_002 | cust_004 | BuildRight LLC | orders@buildright.com |
| rep_003 | cust_005 | FastShip Co | support@fastship.com |
注文テーブルの設計
注文テーブルは、注文ヘッダーと注文項目に個別の項目を含む垂直パーティショニングを使用します。この設計により、すべての注文コンポーネントを同じパーティション内に維持しながら、効率的な製品ベースのクエリが可能になり、効率的にアクセスできるようになります。各注文は複数の項目で構成されます。
注文ヘッダー - PK=order_id、SK=order_id の注文メタデータが含まれます
注文項目 - PK=order_id、SK=product_id を持つ個々の明細項目。直接の製品クエリを有効にします
注記
この垂直パーティショニングアプローチは、埋め込み注文項目のシンプルさと引き換えに、クエリの柔軟性を高めます。各注文項目は個別の DynamoDB 項目になり、すべての注文データを同じパーティション内に維持しながら効率的な製品ベースのクエリを実行し、単一のリクエストで効率的に取得できるようになります。
このテーブルには、顧客検索を必要とせずに直接アカウント担当者クエリを有効にするための account_rep_id (顧客テーブルから複製) の戦略的な非正規化が含まれています。高スループットの書き込みシナリオの場合、OPEN の注文には、複数のパーティションにわたる書き込みシャーディングを有効にするためのステータスとシャード属性が含まれます。
4 つの GSI は、最適化された射影を使用してさまざまなクエリパターンをサポートします。
OrderByCustomerDate GSI - 注文の概要と項目の詳細を含む INCLUDE 射影を使用して、日付範囲フィルタリングによる顧客の注文履歴をサポートします
OpenOrdersByDate GSI (スパース、シャード) - 5 つのシャードを持つマルチ属性パーティションキー (ステータス + シャード) を使用して、5,000 WPS (1 秒あたりの書き込み) をパーティション全体に分散します (各パーティションに 1,000 WPS、DynamoDB の パーティション制限 1,000 WCU に一致)。OPEN の注文のみをインデックスします (全体の 20%)。これにより、GSI ストレージコストを削減できます。クライアント側で結果をマージし、5 つのシャードすべてにわたって並列クエリを実行する必要があります
OrderByAccountRep GSI - 完全な注文詳細がないアカウント担当者のワークフローをサポートするために、注文概要属性を含む INCLUDE 射影を使用します
ProductInOrders GSI - OrderItem レコード (PK=order_id、SK=product_id) から作成されたこの GSI により、特定の製品を含むすべての注文を検索するクエリが可能になります。製品需要分析のために、注文コンテキスト (customer_id、order_date、数量) を含む INCLUDE 射影を使用します
| PK | SK | customer_id | order_date | ステータス | account_rep_id | quantity | 料金 | シャード |
|---|---|---|---|---|---|---|---|---|
| ord_001 | ord_001 | cust_001 | 2025-11-15 | クローズ | rep_001 | |||
| ord_001 | prod_100 | 5 | 25.00 | |||||
| ord_002 | ord_002 | cust_001 | 2025-12-20 | OPEN | rep_001 | 0 | ||
| ord_002 | prod_101 | 10 | 15.00 | |||||
| ord_003 | ord_003 | cust_002 | 2026-01-05 | OPEN | rep_001 | 2 | ||
| ord_003 | prod_100 | 3 | 25.00 |
| customer_id (GSI-PK) | order_date (GSI-SK) | order_id | ステータス | total_amount | order_items | シャード |
|---|---|---|---|---|---|---|
| cust_001 | 2025-11-15 | ord_001 | クローズ | 225.00 | [{product_id: "prod_100", qty: 5}] | |
| cust_001 | 2025-12-20 | ord_002 | OPEN | 150.00 | [{product_id: "prod_101", qty: 10}] | 0 |
| cust_002 | 2026-01-05 | ord_003 | OPEN | 175.00 | [{product_id: "prod_100", qty: 3}] | 2 |
| cust_003 | 2025-10-10 | ord_004 | クローズ | 250.00 | [{product_id: "prod_101", qty: 5}] | |
| cust_004 | 2026-01-03 | ord_005 | OPEN | 200.00 | [{product_id: "prod_100", qty: 20}] | 1 |
| ステータス (GSI-PK-1) | シャード (GSI-PK-2) | order_date (SK) | order_id | customer_id | account_rep_id | order_items | total_amount |
|---|---|---|---|---|---|---|---|
| OPEN | 0 | 2025-12-20 | ord_002 | cust_001 | rep_001 | [{product_id: "prod_101", qty: 10}] | 150.00 |
| OPEN | 1 | 2026-01-03 | ord_005 | cust_004 | rep_002 | [{product_id: "prod_100", qty: 20}] | 200.00 |
| OPEN | 2 | 2026-01-05 | ord_003 | cust_002 | rep_001 | [{product_id: "prod_100", qty: 3}] | 175.00 |
| account_rep_id (GSI-PK) | order_date (GSI-SK) | order_id | customer_id | ステータス | total_amount |
|---|---|---|---|---|---|
| rep_001 | 2025-11-15 | ord_001 | cust_001 | クローズ | 225.00 |
| rep_001 | 2025-12-20 | ord_002 | cust_001 | OPEN | 150.00 |
| rep_001 | 2026-01-05 | ord_003 | cust_002 | OPEN | 175.00 |
| rep_002 | 2025-10-10 | ord_004 | cust_003 | クローズ | 250.00 |
| rep_002 | 2026-01-03 | ord_005 | cust_004 | OPEN | 200.00 |
| product_id (GSI-PK) | order_id (GSI-SK) | customer_id | order_date | quantity |
|---|---|---|---|---|
| prod_100 | ord_001 | cust_001 | 2025-11-15 | 5 |
| prod_100 | ord_003 | cust_002 | 2026-01-05 | 3 |
| prod_101 | ord_002 | cust_001 | 2025-12-20 | 10 |
製品テーブルの設計
製品テーブルは、項目コレクションパターンを使用して、製品メタデータと在庫データの両方を同じパーティション内に保存します。この設計では、製品と在庫間の識別関係を活用します。親製品がないと在庫は存在できません。製品メタデータに SK=product_id を、在庫項目に SK=warehouse_id を使用し、PK=product_id を設定すると、個別の在庫テーブルと GSI が不要になり、コストが約 50% 削減されます。
このパターンにより、個々の倉庫在庫 (複合キーを持つ GetItem) と製品のすべての倉庫在庫 (パーティションキーに対するクエリ) の両方に対する効率的なクエリが可能になります。製品メタデータ項目の total_inventory 属性は、合計在庫数をすばやく検索するための非正規化集計を提供します。
| product_id (PK) | Warehouse_id (SK) | product_name | category | unit_price | inventory_quantity | total_inventory |
|---|---|---|---|---|---|---|
| prod_100 | prod_100 | ウィジェット A | ハードウェア | 25.00 | 500 | |
| prod_100 | wh_sea | 200 | ||||
| prod_100 | wh_pdx | 150 | ||||
| prod_100 | wh_atl | 150 | ||||
| prod_101 | prod_101 | ガジェット B | 電子機器 | 50.00 | 300 | |
| prod_101 | wh_sea | 100 | ||||
| prod_101 | wh_pdx | 200 |
各テーブルは、必要なアクセスパターンを効率的にサポートするように、特定のグローバルセカンダリインデックス (GSI) で設計されています。この設計では、戦略的な非正規化とスパースインデックス作成を備えた集約指向の原則を使用して、パフォーマンスとコストの両方を最適化します。
主要な設計の最適化には以下が含まれます。
-
スパース GSI - OpenOrdersByDate は OPEN の注文 (全体の 20%) のみをインデックス化するため、GSI ストレージコストを削減できます。
-
項目コレクションパターン - 製品テーブルは PK=product_id、SK=warehouse_id を使用して在庫を保存し、個別の在庫テーブルを排除します。
-
Order + OrderItems 集約 - アクセス相関が 100% であるため、単一の項目として埋め込まれます
-
戦略的な非正規化 - 効率的なクエリのために注文テーブルで account_rep_id を重複させる
最後に、前に定義したアクセスパターンを再度使用することができます。次のテーブルは、戦略的 GSIs を備えたマルチテーブル設計を使用して、各アクセスパターンを効率的にサポートする方法を示しています。各パターンは、直接キー検索または単一の GSI クエリのいずれかを使用するため、コストのかかるスキャンを回避し、あらゆる規模で一貫したパフォーマンスを提供します。
| S 番号 | アクセスパターン | クエリの状態 |
|---|---|---|
|
1 |
従業員 ID で従業員の詳細を検索する |
従業員テーブル: GetItem(employee_id="emp_001") |
|
2 |
従業員名で従業員の詳細をクエリする |
EmployeeByName GSI: Query(name="John Smith") |
|
3 |
従業員の電話番号を検索する (複数可) |
従業員テーブル: GetItem(employee_id="emp_001") |
|
4 |
顧客の電話番号を検索する (複数可) |
顧客テーブル: GetItem(customer_id="cust_001") |
|
5 |
日付範囲内の顧客の注文を取得する |
OrderByCustomerDate GSI: Query(customer_id="cust_001", order_date BETWEEN "2025-01-01" AND "2025-12-31") |
|
6 |
日付範囲内のすべての OPEN の注文を表示する |
OpenOrdersByDate GSI: マルチ属性 PK (status="OPEN + shard=0-4) を使用して 5 つのシャードを並列にクエリし、SK=order_date BETWEEN "2025-01-01" AND "2025-12-31"、結果をマージします |
|
7 |
最近雇用したすべての従業員を確認する |
EmployeeByHireDate GSI: Query(entity_type="EMPLOYEE", hire_date >= "2025-01-01") |
|
8 |
倉庫のすべての従業員を検索する |
EmployeeByWarehouse GSI: Query(warehouse_id="wh_sea") |
|
9 |
製品の注文中のすべての項目を取得する |
ProductInOrders GSI: Query(product_id="prod_100") |
|
10 |
すべての倉庫の製品在庫を取得する |
製品テーブル: Query(product_id="prod_100") |
|
11 |
アカウント担当者別に顧客を取得する |
CustomerByAccountRep GSI: Query(account_rep_id="rep_001") |
|
12 |
アカウント担当者別に注文を取得する |
OrderByAccountRep GSI: Query(account_rep_id="rep_001") |
|
13 |
役職を持つ従業員を取得する |
EmployeeByJobTitle GSI: Query(job_title="Manager") |
|
14 |
商品および倉庫別に在庫を取得する |
製品テーブル: GetItem(product_id="prod_100", warehouse_id="wh_sea") |
|
15 |
商品在庫総数を取得する |
製品テーブル: GetItem(product_id="prod_100", warehouse_id="prod_100") |