

# DynamoDB テーブルのデータモデリング
<a name="data-modeling"></a>

データモデリングに進む前に、DynamoDB の基礎を理解することが重要です。DynamoDB は、柔軟なスキーマを可能にする key-value NoSQL データベースです。各項目のキー属性以外の一連のデータ属性は、均一または個別のいずれかにすることができます。DynamoDB のキースキーマは、パーティションキーで項目を一意に識別する単純なプライマリキー形式であるか、パーティションキーとソートキーの組み合わせで項目を一意に定義する複合プライマリキー形式のいずれかです。パーティションキーは、データの物理的な場所を特定して取得するためにハッシュ化されます。そのため、データを均等に分散させるには、カーディナリティが高く、水平方向にスケーラブルな属性をパーティションキーとして選択することが重要です。ソートキー属性は、キースキーマではオプションです。ソートキーを使用すると、1 対多リレーションシップをモデル化し、DynamoDB で項目コレクションを作成できます。ソートキーは範囲キーとも呼ばれます。ソートキーを使用して項目コレクション内の項目をソートし、柔軟な範囲ベースの操作を可能にすることもできます。

DynamoDB のキースキーマの詳細とベストプラクティスについては、以下を参照してください。
+ [DynamoDB におけるパーティションとデータ分散](HowItWorks.Partitions.md) 
+ [DynamoDB でパーティションキーを効率的に設計し、使用するためのベストプラクティス。](bp-partition-key-design.md) 
+ [ソートキーを使用してデータを DynamoDB で整理するためのベストプラクティス](bp-sort-keys.md) 
+ [適切な DynamoDB パーティションキーの選択](https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/) 

DynamoDB で追加のクエリパターンをサポートするには、多くの場合、セカンダリインデックスが必要になります。セカンダリインデックスは、同じデータがベーステーブルとは異なるキースキーマで整理されているシャドウテーブルです。ローカルセカンダリインデックス (LSI) は、ベーステーブルと同じパーティションキーを共有し、代替のソートキーを使用することでベーステーブルの容量を共有できます。グローバルセカンダリインデックス (GSI) では、ベーステーブルとは異なるパーティションキーと異なるソートキー属性を使用できます。つまり、GSI のスループット管理はベーステーブルとは独立しています。

セカンダリインデックスとベストプラクティスの詳細については、以下を参照してください。
+ [DynamoDB でのセカンダリインデックスを使用したデータアクセス性の向上](SecondaryIndexes.md) 
+ [DynamoDB でセカンダリインデックスを使用するためのベストプラクティス。](bp-indexes.md) 

ここで、データモデリングをもう少し詳しく見てみましょう。高度に最適化された柔軟なスキーマを設計するプロセスは、DynamoDB に限らず、どの NoSQL データベースであっても習得が難しいスキルと言えます。このモジュールの目標は、ユースケースから本番環境へと導く、スキーマ設計のメンタルフローチャートの開発を支援することにあります。まず、あらゆる設計の基本となる、シングルテーブル設計とマルチテーブル設計の選択について簡単に説明します。次に、アプリケーションの組織上またはパフォーマンス上のさまざまな結果を達成するために使用できる多様な設計パターン (構成要素) を確認します。最後に、ユースケースや業界別に異なる完全なスキーマ設計パッケージを紹介します。

![\[データ、データ下のブロック、ブロック下の基盤の概念的な相互関係を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesign.png)


**Topics**
+ [項目コレクション - DynamoDB で一対多リレーションシップをモデル化する方法](WorkingWithItemCollections.md)
+ [DynamoDB のデータモデリングの基盤](data-modeling-foundations.md)
+ [DynamoDB のデータモデリングの構成要素](data-modeling-blocks.md)
+ [DynamoDB のデータモデリングスキーマ設計パッケージ](data-modeling-schemas.md)
+ [DynamoDB でリレーショナルデータをモデル化するためのベストプラクティス](bp-relational-modeling.md)

# 項目コレクション - DynamoDB で一対多リレーションシップをモデル化する方法
<a name="WorkingWithItemCollections"></a>

DynamoDB の*項目コレクション*は、同じパーティションキー値を共有する項目のグループです。これは、これらの項目が関連していることを意味します。項目コレクションは、DynamoDB で 1 対多リレーションシップをモデル化する主要なメカニズムです。項目コレクションは、[複合プライマリキー](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey)を使用するように設定されたテーブルまたはインデックスにのみ存在できます。

**注記**  
項目コレクションは、ベーステーブルまたはセカンダリインデックスのいずれかに存在できます。項目コレクションが特にインデックスとやり取りする方法の詳細については、「[ローカルセカンダリインデックス内の項目コレクション](LSI.md#LSI.ItemCollections)」を参照してください。

3 つの異なるユーザーとそのゲーム内インベントリを示す次のテーブルを考えてみます。

![\[異なる属性を持つ 3 つの異なる項目コレクション。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/item_collection.png)


各コレクションの一部の項目において、ソートキーは、`inventory::armor`、`inventory::weapon`、`info` など、データをグループ化するための情報を連結したものです。項目コレクションごとに、これらの属性の異なる組み合わせをソートキーとして使用できます。ユーザー `account1234` には `inventory::weapons` 項目がありますが、ユーザー `account1387` にはありません (まだ見つけていないため)。ユーザー `account1138` は、ソートキーとして 2 つの項目のみ使用しています (まだインベントリがないため)。他のユーザーは 3 つを使用しています。

DynamoDB では、これらの項目コレクションから項目を選択的に取得して、次の操作を実行できます。
+ 特定のユーザーからすべての項目を取得する。
+ 特定のユーザーから 1 つの項目のみを取得する。
+ 特定のユーザーに属する特定タイプのすべての項目を取得する。

## 項目コレクションでデータを整理してクエリを高速化する
<a name="WorkingWithItemCollections.Example"></a>

この例では、これらの 3 つの項目コレクションの各項目は、プレイヤーと選択したデータモデルを表し、ゲームとプレイヤーのアクセスパターンを反映しています。どのようなデータがゲームに必要であるか。いつ必要であるか。どのくらいの頻度で必要であるか。この方法で実行する場合のコストはどれくらいか。これらの質問に対する回答に基づいて、これらのデータモデリングを決定しています。

このゲームでは、武器のインベントリのページと鎧のページが別個にプレイヤーに提示されます。プレイヤーがインベントリを開くと、最初に武器が表示されます。これは、このページを高速にロードしたいためであり、以降のインベントリページはその後にロードできます。これらの各項目タイプは、プレイヤーが獲得するゲーム内項目が増えるに従って肥大化する可能性があるため、データベースでは各インベントリページをプレイヤーの項目コレクションの独立した項目にしています。

次のセクションでは、`Query` オペレーションを使用して項目コレクションとやり取りする方法についてより詳しく説明します。

**Topics**
+ [項目コレクションでデータを整理してクエリを高速化する](#WorkingWithItemCollections.Example)

# DynamoDB のデータモデリングの基盤
<a name="data-modeling-foundations"></a>

このセクションでは、基盤レイヤーについて、シングルテーブルとマルチテーブルという 2 種類のテーブル設計を検討します。

![\[データ、データ下のブロック、ブロック下の基盤の概念的な相互関係を示す画像。基盤を重視します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignFoundation.png)


## シングルテーブル設計の基盤
<a name="data-modeling-foundations-single"></a>

DynamoDB スキーマの基盤の 1 つとして、**シングルテーブル設計**を選択できます。シングルテーブル設計とは、複数のタイプ (エンティティ) のデータを単一の DynamoDB テーブルに保存できるパターンです。複数のテーブルやテーブル間の複雑な関係を維持する必要をなくすことで、データアクセスパターンを最適化し、パフォーマンスを向上させ、コストを削減することを目的としています。これが可能なのは、DynamoDB は、同じパーティションキーを持つ項目 (項目コレクションと呼ばれます) をいずれも同じパーティションに保存するためです。この設計では、異なる種類のデータが同じテーブル内に項目として保存され、各項目は一意のソートキーで識別されます。

![\[テーブルに保存した、同じ UserId の項目コレクション内で、エンティティタイプ別に各項目を区別するためにソートキーを使用する方法を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


**利点**:
+ データの局所性により、1 回のデータベース呼び出しで複数のエンティティタイプのクエリをサポートします。
+ 読み取りにかかる全体的な財務コストとレイテンシーコストを削減します。
  + 合計 4 KB 未満の 2 つの項目に対する単一のクエリは、結果整合性のある 0.5 RCU になります。
  + 合計 4 KB 未満の 2 つの項目に対する 2 つのクエリは、結果整合性のある 1 RCU になります (それぞれ 0.5 RCU)。
  + 2 つの別々のデータベース呼び出しを返すまでの時間は、1 回の呼び出しよりも平均して長くなります。
+ 管理するテーブルの数が減ります。
  + アクセス許可を複数の IAM ロールやポリシーにわたって管理する必要はありません。
  + テーブルの容量管理はすべてのエンティティにわたって平均化され、通常は消費パターンがより予測可能になります。
  + モニタリングに必要なアラームの数が減ります。
  + カスタマーマネージド暗号化キーは、1 つのテーブルでローテーションするだけで済みます
+ テーブルへのトラフィックをスムーズにします。
  + 複数の使用パターンを同じテーブルに集約することで、全体的な使用がよりスムーズになる傾向があり (株価指数のパフォーマンスが個々の株式よりもスムーズになる傾向があるように)、プロビジョニングモードテーブルでより高い使用率を達成するのに適しています。

**欠点**:
+ リレーショナルデータベースと比べて逆説的な設計であるため、ラーニングカーブが急になる場合があります。
+ データ要件はすべてのエンティティタイプにわたって一貫している必要があります。
  + バックアップは全部かゼロかのどちらかであるため、ミッションクリティカルでないデータは、別のテーブルに保存することを検討してください。
  + テーブルの暗号化はすべての項目間で共有されます。テナントごとに暗号化要件が異なるマルチテナントアプリケーションでは、クライアント側の暗号化が必要になります
  + 履歴データと運用データが混在するテーブルでは、低頻度アクセスストレージクラスを有効にしてもあまりメリットがありません。詳細については、[DynamoDB テーブルクラス](HowItWorks.TableClasses.md)を参照してください。
+ エンティティのサブセットのみを処理する必要がある場合でも、すべての変更されたデータは DynamoDB Streams に伝播されます。
  + Lambda を使用する場合は、Lambda イベントフィルターのおかげで請求額に影響しませんが、Kinesis Consumer Libary を使用する場合は、追加費用が発生します。
+ GraphQL を使用する場合、シングルテーブル設計の実装はより難しくなります。
+ Java の [`DynamoDBMapper`](DynamoDBMapper.md) や [Enhanced Client](DynamoDBEnhanced.md) などの上位レベルの SDK クライアントを使用する場合、同じ応答内の項目が異なるクラスに関連付けられる可能性があるため、結果の処理が難しくなりがちです。

**どのようなときに使うか**

単一テーブル設計は、複数のエンティティタイプを一緒に頻繁にクエリするアプリケーションや、異なるデータ型間の関係を維持する必要があるアプリケーションに適しています。これは、アクセスパターンがデータの局所性の恩恵を受ける場合や、複数のテーブルを管理するオーバーヘッドを最小限に抑えたい場合に特に効果的です。

## マルチテーブル設計の基盤
<a name="data-modeling-foundations-multi"></a>

DynamoDB スキーマの別の基盤として、マルチテーブル設計を選択できます****。マルチテーブル設計は、DynamoDB のテーブルごとに 1 つのタイプ (エンティティ) のデータを保存する従来のデータベース設計に近いパターンです。各テーブル内のデータは引き続きパーティションキー別に整理されるため、単一のエンティティタイプ内のパフォーマンスとスケーラビリティは最適化されますが、複数のテーブルにわたるクエリは個別に実行する必要があります。

![\[フォーラムのリストといくつかの集計データを含むフォーラムテーブルを示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable1.png)


![\[所属する特定のフォーラムごとに分割されたスレッドのリストを含むスレッドテーブルを示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/MultipleTable2.png)


**利点**:
+ シングルテーブル設計に慣れていないユーザーでも簡単に設計できます。
+ 各リゾルバーが単一のエンティティ (テーブル) にマッピングされるため、GraphQL リゾルバーの実装が容易になります。
+ エンティティタイプ別に異なるデータ要件が許容されます。
  + ミッションクリティカルな個々のテーブルのバックアップを作成できます 
  + テーブル暗号化はテーブルごとに管理できます。テナントごとに暗号化要件が異なるマルチテナントアプリケーションでは、個別のテナントテーブルにより、お客様ごとに独自の暗号化キーを使用できます。
  + 低頻度アクセスストレージクラスは、履歴データを含むテーブルでのみ有効にして、コスト削減のメリットを最大限に実現できます。詳細については、[DynamoDB テーブルクラス](HowItWorks.TableClasses.md)を参照してください。
+ 各テーブルには独自の変更データストリームがあり、単一のモノリシックプロセッサではなく、項目の種類ごとに専用の Lambda 関数を設計できます。

**欠点**:
+ 複数のテーブルにわたるデータを必要とするアクセスパターンの場合は、DynamoDB からの複数回の読み取りが必要になり、クライアントコードでのデータの処理/結合が必要になることがあります。
+ 複数のテーブルの操作とモニタリングには、より多くの CloudWatch アラームが必要であり、各テーブルを個別にスケールする必要があります
+ 各テーブルのアクセス許可を個別に管理する必要があります。今後テーブルを追加する場合は、必要な IAM ロールやポリシーの変更が必要になります。

**どのようなときに使うか**

アプリケーションのアクセスパターンで複数のエンティティやテーブルをまとめてクエリする必要がない場合は、マルチテーブル設計が適切かつ十分なアプローチです。

# DynamoDB のデータモデリングの構成要素
<a name="data-modeling-blocks"></a>

このセクションでは、アプリケーションで使用できる設計パターンを提供するビルディングブロックレイヤーについて説明します。

![\[データ、データ下のブロック、ブロック下の基盤の概念的な相互関係を示す画像。基盤を重視します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignBlocks.png)


**Topics**
+ [複合ソートキー構成要素](#data-modeling-blocks-composite)
+ [マルチテナンシー構成要素](#data-modeling-blocks-multi-tenancy)
+ [スパースインデックスの構成要素](#data-modeling-blocks-sparse-index)
+ [Time to Live  (有効期限) 構成要素](#data-modeling-blocks-ttl)
+ [Time to Live (アーカイブ用) 構成要素](#data-modeling-blocks-ttl-archival)
+ [垂直パーティショニング構成要素](#data-modeling-blocks-vertical-partitioning)
+ [書き込みシャーディング構成要素](#data-modeling-blocks-write-sharding)

## 複合ソートキー構成要素
<a name="data-modeling-blocks-composite"></a>

NoSQL と言えば、非リレーショナルと考えがちです。ただし、DynamoDB の場合は、スキーマにリレーションシップを配置できない理由はなく、リレーショナルデータベースやその外部キーと見た目が異なるだけです。DynamoDB でデータの論理階層を構築するために使用できる最も重要なパターンの 1 つは、複合ソートキーです。最も一般的な設計スタイルは、階層の各レイヤー (親レイヤー > 子レイヤー > 孫レイヤー) をハッシュタグで区切ることです。例えば、`PARENT#CHILD#GRANDCHILD#ETC`。

![\[userID をプライマリキーとし、他の属性の組み合わせをソートキーとするテーブル内の項目を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ShoppingCart.png)


DynamoDB のパーティションキーには、データをクエリするために常に正確な値が必要です。一方、ソートキーには、バイナリツリーをたどる場合と同じように、左から右に部分条件を適用できます。

上の例では、e コマースストアのショッピングカートを、ユーザーセッションをまたいで維持する必要があります。ユーザーはログインするたびに、あとで買うために保存した商品を含むショッピングカート全体を確認したい場合があります。ただし、レジに進むときは、アクティブなカート内の商品のみをロードして購入できるようにする必要があります。これらの `KeyConditions` は、どちらもカートのソートキーを明示的に要求するため、追加のウィッシュリストのデータは読み取り時に DynamoDB によって単に無視されます。保存した商品とアクティブな商品はどちらも同じカートの一部ですが、アプリケーションの異なる部分ごとに異なる方法で扱う必要があります。この場合、アプリケーションの部分ごとに必要なデータのみを取得する最適な方法は、ソートキーのプレフィックスに `KeyCondition` を適用することです。

**この構成要素の主な特徴**
+ 関連項目は相互にローカルに保存されるため、データへのアクセスが効率的になります。
+ `KeyCondition` 式を使用すると、階層のサブセットを選択的に取得できるため、無駄な RCU がなくなります。
+ アプリケーションの異なる部分ごとに異なるプレフィックスを適用して項目を保存できるため、項目の上書きや書き込みの競合を防ぐことができます。

## マルチテナンシー構成要素
<a name="data-modeling-blocks-multi-tenancy"></a>

多くのお客様が DynamoDB を使用してマルチテナントアプリケーションのデータをホストしています。このようなシナリオでは、1 つのテナントからのすべてのデータをテーブルの独自の論理パーティションに保持するようにスキーマを設計する必要があります。これには、項目コレクションという概念を活用します。項目コレクションとは、DynamoDB テーブル内で同じパーティションキーを持つすべての項目を表す用語です。DynamoDB におけるマルチテナンシーへのアプローチの詳細については、「[DynamoDB のマルチテナンシー](https://docs.aws.amazon.com/whitepapers/latest/multi-tenant-saas-storage-strategies/multitenancy-on-dynamodb.html)」を参照してください。

![\[マルチテナントの写真サイトを表すテーブルを示す画像。プライマリキーは、ユーザーを表すパーティションキーと、さまざまな写真を表すソートキーで構成されます。各項目の属性は、写真をホストしている URL を示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/MultiTenant.png)


この例では、ユーザー数が数千人に及ぶ可能性がある写真ホスティングサイトを運営しています。各ユーザーは、最初は自分のプロファイルにのみ写真をアップロードし、デフォルトでは他のユーザーの写真を見ることはできません。各ユーザーからの API コールの承認に分離レベルを追加して、各自のパーティションのデータのみを要求していることを確認するのが理想的ですが、スキーマレベルでは一意のパーティションキーで十分です。

**この構成要素の主な特徴**
+ 1 人のユーザーやテナントが読み取ることができるデータ量は、各自のパーティション内の項目の合計量と同じ量に制限されます。
+ アカウントの閉鎖やコンプライアンス要求に伴うテナントのデータの削除は、適切かつ安価に行うことができます。単に、パーティションキーがテナント ID と等しいクエリを実行し、返されたプライマリキーごとに `DeleteItem` 操作を実行します。

**注記**  
マルチテナンシーを念頭に置いて設計されているため、1 つのテーブル全体でさまざまな暗号化キープロバイダーを使用してデータを安全に分離できます。[AWSAmazon DynamoDB 用の Database Encryption SDK](https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/what-is-database-encryption-sdk.html) を使用すると、DynamoDB ワークロードにクライアント側の暗号化を組み込むことができます。属性レベルの暗号化を実行すると、特定の属性値を DynamoDB テーブルに保存する前に暗号化したり、データベース全体を事前に復号せずに暗号化された属性を検索したりできます。

## スパースインデックスの構成要素
<a name="data-modeling-blocks-sparse-index"></a>

アクセスパターンによっては、まれな項目や、ステータスを受け取る項目 (応答のエスカレーションが必要) に一致する項目の検索が必要になる場合があります。これらの項目については、データセット全体を定期的にクエリするのではなく、**グローバルセカンダリインデックス (GSI)** を利用できます。GSI ではデータがまばらに読み込まれるという事実が役立ちます。つまり、インデックスに定義されている属性を持つベーステーブルの項目だけがインデックスにレプリケートされます。

![\[大量の定常状態のデータを受け取るベーステーブルを示す画像\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SparseBaseTable.png)


![\[エスカレーションされた項目のみを受け取るグローバルセカンダリインデックスを示す画像\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SparseGSI.png)


この例は、現場の各デバイスが定期的にステータスを報告している IOT のユースケースを示しています。ほとんどのレポートでは、デバイスから何の問題もないことが報告されると予想されますが、場合によっては障害が発生し、修理技術者へのエスカレーションが必要になる場合があります。エスカレーションを含むレポートでは、`EscalatedTo` 属性が項目に追加されますが、それ以外の場合は追加されません。この例の GSI は `EscalatedTo` でパーティション化されており、GSI はベーステーブルからキーを引き継ぐため、どの DeviceID がいつ障害を報告したかは依然として確認できます。

DynamoDB の場合、読み取りは書き込みよりも安価であり、スパースインデックスは、特定の種類の項目のインスタンスがめったに発生しないが、これらを見つけるための読み取りをよく行うユースケースでは非常に強力なツールです。

**この構成要素の主な特徴**
+ スパース GSI の書き込みコストとストレージコストは、キーパターンに一致する項目にのみ適用されるため、GSI のコストは、すべての項目をレプリケートする他の GSI よりも大幅に低くなる可能性があります。
+ 複合ソートキーを引き続き使用して、目的のクエリに一致する項目をさらに絞り込むこともできます。例えば、ソートキーにタイムスタンプを使用して、過去 X 分間 (`SK > 5 minutes ago, ScanIndexForward: False`) に報告された障害のみを表示できます。

## Time to Live  (有効期限) 構成要素
<a name="data-modeling-blocks-ttl"></a>

ほとんどのデータは、特定の期限まで、プライマリデータストアに保持する価値があると考えられます。DynamoDB からのデータのエージングアウトを容易にするために、**Time to Live (TTL)** と呼ばれる機能があります。[TTL](TTL.md) 機能を使用すると、エポックタイムスタンプ (経過済み) を持つ項目のために、モニタリングする必要がある特定の属性をテーブルレベルで定義できます。これにより、期限切れのレコードをテーブルから無料で削除できます。

**注記**  
[グローバルテーブルバージョン 2019.11.21 (現行)](GlobalTables.md) を使用しており、[Time to Live](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) 機能も使用している場合、DynamoDB は TTL による削除をすべてのレプリカテーブルにレプリケートします。最初の TTL による削除の場合、TTL の有効期限切れが発生したリージョンでは、書き込みキャパシティが消費されません。ただし、レプリカテーブルにレプリケートされた TTL による削除の場合、各レプリカリージョンではレプリケートされた書き込みキャパシティが消費され、該当する料金が適用されます。

![\[Time to Live 属性とユーザーのメッセージを含むテーブルを示す画像\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/TTL.png)


この例は、存続期間の短いメッセージをユーザーが作成できるように設計したアプリケーションを示しています。DynamoDB でメッセージを作成すると、アプリケーションコードによって TTL 属性が 7 日後の日付に設定されます。約 7 日後に、DynamoDB はこれらの項目のエポックタイムスタンプが過去のものであることを確認し、削除します。

TTL による削除は無料であるため、この機能を使用してテーブルから履歴データを削除することを強くお勧めします。これにより、毎月の全体的なストレージ料金が削減され、クエリによって取得されるデータが少なくなるため、ユーザーの読み取りコストも削減される可能性があります。TTL はテーブルレベルで有効になっていますが、どの項目またはエンティティに対して TTL 属性を作成するか、またエポックタイムスタンプをどれだけ将来に設定するかはユーザーが指定できます。

**この構成要素の主な特徴**
+ TTL による削除はバックグラウンドで実行され、テーブルのパフォーマンスには影響しません 
+ TTL は約 6 時間ごとに実行される非同期プロセスですが、期限切れのレコードの削除には 48 時間超かかる場合があります。
  + 古いデータを 48 時間以内にクリーンアップする必要がある場合は、ロックレコードや状態管理などのユースケースで TTL による削除に依存しないでください。
+ TTL 属性には有効な属性名を指定できますが、値は数値型でなければなりません。

## Time to Live (アーカイブ用) 構成要素
<a name="data-modeling-blocks-ttl-archival"></a>

TTL は DynamoDB から古いデータを削除する効果的なツールですが、多くのユースケースでは、データのアーカイブをプライマリデータストアよりも長期間保存する必要があります。この場合、TTL によるレコードの時間指定削除を利用して、期限切れのレコードを長期データストアにプッシュできます。

![\[Time to Live による削除ジョブを DynamoDB Streams に送信し、その後に長期データストアにアーカイブするテーブルの画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/TTLArchive.png)


DynamoDB が TTL を使用して削除を実行すると、その操作は引き続き `Delete` イベントとして DynamoDB Stream にプッシュされます。ただし、DynamoDB の TTL 自体が削除を実行する場合は、`principal:dynamodb` のストリームレコードの属性を利用できます。DynamoDB Stream への Lambda サブスクライバーを使用すると、DynamoDB プリンシパル属性にのみイベントフィルターを適用することで、このフィルターに一致するすべてのレコードが Amazon Glacier などのアーカイブストアにプッシュされるようになります。

**この構成要素の主な特徴**
+  履歴項目に対する DynamoDB の低レイテンシーの読み取りが不要になったら、それらを Amazon Glacier などのコールドストレージサービスに移行することで、ストレージコストを大幅に削減すると同時に、ユースケースのデータコンプライアンスのニーズを満たすことができます。
+ データが Amazon S3 に保存されている場合、Amazon Athena や Redshift Spectrum などのコスト効率の高い分析ツールを使用してデータの履歴分析を行うことができます。

## 垂直パーティショニング構成要素
<a name="data-modeling-blocks-vertical-partitioning"></a>

ドキュメントモデルデータベースに精通しているユーザーなら、すべての関連データを 1 つの JSON ドキュメントに保存するという考え方に慣れているでしょう。DynamoDB は JSON データ型をサポートしていますが、ネストされた JSON に対する `KeyConditions` の実行はサポートしていません。`KeyConditions` は、ディスクから読み取るデータの量とクエリが実際に消費する RCU の数を決定するものであるため、これに伴って大規模な非効率が生じる可能性があります。DynamoDB の書き込みと読み取りをより適切に最適化するには、ドキュメントの個々のエンティティを個別の DynamoDB 項目に分割することをお勧めします。これは**垂直パーティショニング**とも呼ばれます。

![\[ネストされた JSON オブジェクトとしてフォーマットされた大きなデータ構造を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DocumentBlob.png)


![\[項目のソートキーで DynamoDB の使用を継続的に最適化している項目コレクションを示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SingleTableSchema.png)


上の垂直パーティショニングは、シングルテーブル設計を示す重要な実例ですが、必要に応じて複数のテーブルに実装することもできます。DynamoDB は書き込みを 1 KB 単位で請求するため、理想的には各項目が 1 KB 未満になるようにドキュメントを分割します。

**この構成要素の主な特徴**
+ データ関係の階層はソートキーのプレフィックスによって維持されるため、必要に応じて単一のドキュメント構造をクライアント側で再構築できます。
+ データ構造の個々のコンポーネントを個別に更新できるため、小さな項目の更新はわずか 1 WCUで済みます。
+ ソートキー `BeginsWith` を使用すると、アプリケーションは 1 回のクエリで類似したデータを取得し、読み取りコストを集約して総コスト/レイテンシーを削減できます。
+ サイズの大きいドキュメントは、DynamoDB の個別項目のサイズ制限である 400 KB を簡単に超えてしまう可能性があり、垂直パーティショニングはこの制限を回避するのに役立ちます。

## 書き込みシャーディング構成要素
<a name="data-modeling-blocks-write-sharding"></a>

DynamoDB に設定されている数少ないハード制限の 1 つは、単一の物理パーティションが 1 秒あたりに維持できるスループットに対する制限です (必ずしも単一のパーティションキーとは限りません)。現在、これらの制限は以下のとおりです。
+ 1000 WCU (または 1 秒あたり 1000 <=1KB 項目の書き込み) および 3000 RCU (または 1 秒あたり 3000 <=4KB の読み取り) の強力な整合性のある読み込み、**または 
+ 1 秒あたり 6000 <=4KB の結果整合性のある読み込み**

テーブルに対するリクエストがこれらの制限のいずれかを超えると、エラーが `ThroughputExceededException` のクライアント SDK に送り返されます。これは一般的にスロットリングと呼ばれます。この制限を超える読み取り操作を必要とするユースケースは、ほとんどの場合、DynamoDB の前に読み取りキャッシュを配置することで最適に処理されます。ただし、書き込み操作には**書き込みシャーディング**と呼ばれるスキーマレベルの設計が必要です。

![\[DynamoDB が複数のパーティションにわたってパーティションキーをシャードして、トラフィックの急増によるスロットリングを防ぐ方法を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingProblem.png)


![\[DynamoDB が複数のパーティションにわたってパーティションキーをシャードして、トラフィックの急増によるスロットリングを防ぐ方法を示す画像。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/WriteShardingSolution.png)


この問題を解決するには、アプリケーションの `UpdateItem` コードで各候補者のパーティションキーの末尾にランダムな整数を追加します。ランダム整数ジェネレータの範囲の上限は、特定の候補者の 1 秒あたりの予想書き込み量を 1000 で割った値と一致するか、それを超える必要があります。1 秒あたり 20,000 の投票数をサポートするには、rand(0,19) のようになります。データは個別の論理パーティションに保存されているため、読み取り時にデータを結合し直す必要があります。投票総数はリアルタイムである必要はないため、X 分ごとにすべての投票パーティションを読み取るようにスケジュールした Lambda 関数では、候補者ごとに集計をときどき行い、ライブ読み取り用の 1 つの投票集計レコードに書き戻すことができます。

**この構成要素の主な特徴**
+ 特定のパーティションキーに対する書き込みスループットがきわめて高く、これを避けられないユースケースでは、書き込み操作を複数の DynamoDB パーティションに人為的に分散できます。
+ GSI でのスロットリングはベーステーブルへの書き込み操作にバックプレッシャーを与えるため、カーディナリティの低いパーティションキーを持つ GSI でも、このパターンを利用する必要があります。

# DynamoDB のデータモデリングスキーマ設計パッケージ
<a name="data-modeling-schemas"></a>

DynamoDB のデータモデリングスキーマ設計パッケージについて説明します。これには、ソーシャルネットワーク、ゲームプロファイル、苦情管理、定期払い、デバイスステータス、オンラインショップのユースケース、アクセスパターン、最終スキーマ設計が含まれます。

![\[データ、データ下のブロック、ブロック下の基盤の概念的な相互関係を示す画像。基盤を重視します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SchemaDesignData.png)


## 前提条件
<a name="data-modeling-prereqs"></a>

DynamoDB のスキーマを設計する前に、まずスキーマがサポートする必要のあるユースケースに関する前提条件となるデータを収集する必要があります。リレーショナルデータベースとは異なり、DynamoDB はデフォルトでシャーディングされます。つまり、データはバックグラウンドで複数のサーバーに保存されるため、データの局所性を考慮して設計することが重要です。スキーマ設計ごとに次のリストを作成する必要があります。
+ エンティティのリスト (ER 図)
+ 各エンティティの推定ボリュームとスループット
+ サポートが必要なアクセスパターン (クエリと書き込み)
+ データ保持要件

**Topics**
+ [前提条件](#data-modeling-prereqs)
+ [DynamoDB でのソーシャルネットワークのスキーマ設計](data-modeling-schema-social-network.md)
+ [DynamoDB でのゲームプロファイルスキーマの設計](data-modeling-schema-gaming-profile.md)
+ [DynamoDB の苦情管理システムのスキーマ設計](data-modeling-complaint-management.md)
+ [DynamoDB での定期払いスキーマの設計](data-modeling-schema-recurring-payments.md)
+ [DynamoDB でのデバイスステータス更新のモニタリング](data-modeling-device-status.md)
+ [DynamoDB をオンラインショップのデータストアとして使用する](data-modeling-online-shop.md)

# DynamoDB でのソーシャルネットワークのスキーマ設計
<a name="data-modeling-schema-social-network"></a>

## ソーシャルネットワークのビジネスユースケース
<a name="data-modeling-schema-social-network-use-case"></a>

このユースケースでは、DynamoDB をソーシャルネットワークとして使用する方法について説明します。ソーシャルネットワークは、さまざまなユーザーが相互に交流できるオンラインサービスです。これから設計するソーシャルネットワークでは、ユーザーの投稿、フォロワー、フォローしている相手、およびフォローしている相手による投稿で構成されるタイムラインをユーザーが表示できるようにします。このスキーマ設計のアクセスパターンは以下のとおりです。
+ 特定のユーザー ID のユーザー情報を取得する。
+ 特定のユーザー ID のフォロワーリストを取得する。
+ 特定のユーザー ID のフォローリストを取得する。
+ 特定のユーザー ID の投稿リストを取得する。
+ 特定の投稿 ID による投稿に「いいね\$1」したユーザーのリストを取得する。
+ 特定の投稿 ID の「いいね\$1」数を取得する。
+ 特定のユーザー ID のタイムラインを取得する。

## ソーシャルネットワークエンティティ関係図
<a name="data-modeling-schema-social-network-erd"></a>

これは、ソーシャルネットワークのスキーマ設計に使用するエンティティ関係図 (ERD) です。

![\[ユーザー、投稿、フォロワーなどのエンティティを示すソーシャルネットワークアプリケーションの ERD。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetworkERD.png)


## ソーシャルネットワークのアクセスパターン
<a name="data-modeling-schema-social-network-access-patterns"></a>

これらは、ソーシャルネットワークのスキーマ設計のために検討するアクセスパターンです。
+ `getUserInfoByUserID`
+ `getFollowerListByUserID`
+ `getFollowingListByUserID`
+ `getPostListByUserID`
+ `getUserLikesByPostID`
+ `getLikeCountByPostID`
+ `getTimelineByUserID`

## ソーシャルネットワークのスキーマ設計の進化
<a name="data-modeling-schema-social-network-design-evolution"></a>

DynamoDB は NoSQL データベースであるため、結合 (複数のデータベースのデータを結合する操作) は実行できません。DynamoDB に慣れていないお客様は、必要がない場合でも、リレーショナルデータベース管理システム (RDBMS) の設計理念 (エンティティごとにテーブルを作成するなど) を DynamoDB に適用する可能性があります。DynamoDB のシングルテーブル設計の目的は、アプリケーションのアクセスパターンに従って事前に結合された形式でデータを書き込み、追加の計算を行わずにそのデータをすぐに使用することにあります。詳細については、「[DynamoDB のシングルテーブル設計とマルチテーブル設計](https://aws.amazon.com/blogs/database/single-table-vs-multi-table-design-in-amazon-dynamodb/)」を参照してください。

それでは、すべてのアクセスパターンに対処するためにスキーマ設計をどのように進化させるかをステップ別に見ていきましょう。

**ステップ 1: アクセスパターン 1 (`getUserInfoByUserID`) に対処する**

特定のユーザーに関する情報を取得するには、キー条件を `PK=<userID>` にしてベーステーブルに [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) を実行する必要があります。クエリ操作では、結果をページ分割できます。これは、ユーザーに多数のフォロワーがいる場合に便利です。Query の詳細については、「[DynamoDB のテーブルに対するクエリの実行](Query.md)」を参照してください。

この例では、ユーザーの「count」と「info」の 2 種類のデータを追跡します。ユーザーの「count」は、ユーザーのフォロワー数、フォローしている相手の数、相手が作成した投稿の数を反映します。ユーザーの「info」は、名前などの個人情報を反映します。

これら 2 種類のデータは、以下の 2 つの項目で表されます。ソートキー (SK) に「count」が含まれている項目は、「info」が含まれている項目よりも変更される可能性が高くなります。DynamoDB は、更新前と更新後に表示される項目のサイズを考慮し、消費されるプロビジョニングされたスループットに、これらの項目サイズの大きい方を反映します。したがって、項目の属性の一部だけを更新した場合でも、[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) は、プロビジョニングされたスループットの総量 (前後の項目サイズの大きい方) を消費します。単一の `Query` 操作で項目を取得し、`UpdateItem` を使用して既存の数値属性の加算または減算ができます。

![\[ID が u#12345 であるユーザーの Query オペレーションの結果、カウントおよび情報データ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork1.png)


**ステップ 2: アクセスパターン 2 (`getFollowerListByUserID`) に対処する**

特定のユーザーのフォロワーリストを取得するには、キー条件を `PK=<userID>#follower` にしてベーステーブルに `Query` を実行する必要があります。

![\[ID が u#12345 であるユーザーのフォロワーを一覧表示するテーブルへの Query オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork2.png)


**ステップ 3: アクセスパターン 3 (`getFollowingListByUserID`) に対処する**

特定のユーザーがフォローしている相手のリストを取得するには、キー条件を `PK=<userID>#following` にしてベーステーブルに `Query` を実行する必要があります。次に、[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) オペレーションを使用して複数のリクエストをグループ化し、以下のことを実行できます。
+ ユーザー A をユーザー B のフォロワーリストに追加し、ユーザー B のフォロワー数を 1 つ増やします。
+ ユーザー B をユーザー A のフォロワーリストに追加し、ユーザー A のフォロワー数を 1 つ増やします

![\[ID が u#12345 であるユーザーがフォローしているすべてのユーザーを一覧表示するテーブルへの Query オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork3.png)


**ステップ 4: アクセスパターン 4 (`getPostListByUserID`) に対処する**

特定のユーザーが作成した投稿のリストを取得するには、キー条件を `PK=<userID>#post` にしてベーステーブルに `Query` を実行する必要があります。ここで注意すべき重要な点の 1 つは、ユーザーの postID はインクリメンタルでなければならないということです。つまり、2 番目の PostID 値は 1 番目の PostID 値よりも大きくなければなりません (ユーザーは自分の投稿を並べ替えて表示したいはずです)。これを行うには、Universally Unique Lexicographically Sortable Identifier (ULID) などの時間値に基づいて postID を生成できます。

![\[特定のユーザーが作成した投稿のリストを取得するためのキー条件を使用した Query オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork4.png)


**ステップ 5: アクセスパターン 5 (`getUserLikesByPostID`) に対処する**

特定のユーザーの投稿に「いいね\$1」を付けた人のリストを取得するには、キー条件を `PK=<postID>#likelist` にしてベーステーブルに `Query` を実行する必要があります。このアプローチは、アクセスパターン 2 (`getFollowerListByUserID`) とアクセスパターン 3 (`getFollowingListByUserID`) でフォロワーリストとフォローしている相手のリストを取得するときに使用したのと同じパターンです。

![\[特定のユーザーの投稿に「いいね!」をしたユーザーのリストを取得するためのキー条件を使用した Query オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork5.png)


**ステップ 6: アクセスパターン 6 (`getLikeCountByPostID`) に対処する**

特定の投稿の「いいね\$1」の数を取得するには、キー条件を `PK=<postID>#likecount` にしてベーステーブルに [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) 操作を実行する必要があります。このアクセスパターンでは、パーティションのスループットが 1 秒あたり 1000 WCU を超えるとスロットリングが発生するため、フォロワーの多いユーザー (有名人など) が投稿を作成するたびにスロットリングの問題が発生する可能性があります。この問題は DynamoDB が原因ではなく、DynamoDB  がソフトウェアスタックの最後にあるため、DynamoDB に現れるだけです。

すべてのユーザーに「いいね\$1」の数を同時に表示することが本当に必要か、それとも時間の経過と共に徐々に表示できるかを評価する必要があります。一般的に、投稿の「いいね\$1」の数はすぐに 100% 正確である必要はありません。この戦略を実装するには、アプリケーションと DynamoDB の間にキューを置き、更新が定期的に行われるようにします。

![\[特定の投稿の「いいね!」の数を取得するためのキー条件を使用した GetItem オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork6.png)


**ステップ 7: アクセスパターン 7 (`getTimelineByUserID`) に対処する**

特定のユーザーのタイムラインを取得するには、キー条件を `PK=<userID>#timeline` にしてベーステーブルに `Query` 操作を実行する必要があります。ユーザーのフォロワーが自分の投稿を同期的に表示する必要があるシナリオを考えてみましょう。ユーザーが投稿を書くたびに、フォロワーリストが読み取られ、useIDと postID がすべてのフォロワーのタイムラインキーにゆっくりと入力されます。次に、アプリケーションを起動すると、`Query` 操作でタイムラインキーを読み取り、新しい項目に対して [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) 操作を使用して userID と postID の組み合わせをタイムライン画面に入力できます。API コールでタイムラインを読み取ることはできませんが、投稿が頻繁に編集される可能性がある場合、これはより費用対効果の高いソリューションです。

タイムラインは最近の投稿を表示する場所なので、古い投稿をクリーンアップする方法が必要です。WCU を使用して削除する代わりに、DynamoDB の [TTL](TTL.md) 機能を使用して無料で削除できます。

![\[特定のユーザーの最近の投稿を示すタイムラインを取得するためのキー条件を使用した Query オペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork7.png)


すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | その他の条件/フィルター | 
| --- | --- | --- | --- | --- | --- | 
| getUserInfoByUserID | ベーステーブル | クエリ | PK=<userID> |  |  | 
| getFollowerListByUserID | ベーステーブル | クエリ | PK=<userID>\$1follower |  |  | 
| getFollowingListByUserID | ベーステーブル | クエリ | PK=<userID>\$1following |  |  | 
| getPostListByUserID | ベーステーブル | クエリ | PK=<userID>\$1post |  |  | 
| getUserLikesByPostID | ベーステーブル | クエリ | PK=<postID>\$1likelist |  |  | 
| getLikeCountByPostID | ベーステーブル | GetItem | PK=<postID>\$1likecount |  |  | 
| getTimelineByUserID | ベーステーブル | クエリ | PK=<userID>\$1timeline |  |  | 

## ソーシャルネットワークの最終スキーマ
<a name="data-modeling-schema-social-network-final-schema"></a>

これが最終的なスキーマ設計です。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/SocialNetwork/SocialNetworkSchema.json)を参照してください。

**ベーステーブル:**

![\[前述の Query および GetItem オペレーションの結果を含むテーブルの最終スキーマ設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/SocialNetwork8.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-social-network-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB でのゲームプロファイルスキーマの設計
<a name="data-modeling-schema-gaming-profile"></a>

## ゲームプロファイルのビジネスユースケース
<a name="data-modeling-schema-gaming-profile-use-case"></a>

このユースケースでは、DynamoDB を使用してゲームシステムのプレイヤープロファイルを保存する方法について説明します。ユーザー (この場合はプレイヤー) は、多くの最新のゲーム、特にオンラインゲームを操作する前に、プロファイルを作成する必要があります。ゲームプロファイルには通常、以下が含まれます。
+ ユーザー名などの基本情報
+ 項目や装備などのゲームデータ
+ タスクやアクティビティなどのゲーム記録
+ 友達リストなどのソーシャル情報

このアプリケーションのきめ細かなデータクエリアクセス要件を満たすため、プライマリキー (パーティションキーとソートキー) には汎用名 (PK と SK) を使用するため、以下に示すように、さまざまなタイプの値でオーバーロードできます。

このスキーマ設計のアクセスパターンは以下のとおりです。
+ ユーザーの友達リストを取得する。
+ プレイヤーのすべての情報を取得する。
+ ユーザーの項目リストを取得する。
+ ユーザーの項目リストから特定の項目を取得する。
+ ユーザーのキャラクターを更新する。
+ ユーザーの項目数を更新する。

ゲームプロファイルのサイズは、ゲームによって異なります。[大きな属性値は圧縮する](bp-use-s3-too.md)ことで、DynamoDB の項目制限内に収め、コストを削減できます。スループット管理戦略は、プレイヤー数、1 秒あたりにプレイされるゲーム数、ワークロードの季節性などのさまざまな要因によって異なります。新しくリリースされたゲームの場合、通常、プレイヤーの数や人気度は不明であるため、[オンデマンドスループットモード](capacity-mode.md#capacity-mode-on-demand)から始めます。

## ゲームプロファイルエンティティ関係図
<a name="data-modeling-schema-gaming-profile-erd"></a>

次に示すのは、ゲームプロファイルのスキーマ設計に使用するエンティティ関係図 (ERD) です。

![\[ゲームプロファイルの ER 図。ユーザー、ゲーム、スコアなどのエンティティ間の関係を示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfileERD.png)


## ゲームプロファイルのアクセスパターン
<a name="data-modeling-schema-gaming-profile-access-patterns"></a>

これらは、ソーシャルネットワークのスキーマ設計のために検討するアクセスパターンです。
+ `getPlayerFriends`
+ `getPlayerAllProfile`
+ `getPlayerAllItems`
+ `getPlayerSpecificItem`
+ `updateCharacterAttributes`
+ `updateItemCount`

## ゲームプロファイルスキーマ設計の進化
<a name="data-modeling-schema-social-network-design-evolution"></a>

上記の ERD から、これは 1 対多リレーションシップタイプのデータモデリングであることがわかります。DynamoDB では、1 対多のデータモデルを項目コレクションに整理できます。これは、複数のテーブルを作成して外部キーでリンクする従来のリレーショナルデータベースとは異なります。[項目コレクション](WorkingWithItemCollections.md)は、同じパーティションキー値を共有していてもソートキー値が異なる項目のグループです。項目コレクション内の各項目には、他の項目と区別する固有のソートキー値があります。これを念頭に置いて、エンティティタイプごとに `HASH` 値と `RANGE` 値のパターンを使用してみましょう。

まず、`PK` や `SK` などの汎用名を使用して、さまざまなタイプのエンティティを同じテーブルに保存し、モデルを将来にわたって使用できるようにします。読みやすくするために、データのタイプを示すプレフィックスを含めたり、`Entity_type` または `Type` という任意の属性を含めたりできます。現在の例では、`player` で始まる文字列を `PK` として `player_ID` を保存し、`SK` のプレフィックスとして `entity name#` を使用します。さらに、このデータがどのエンティティタイプであるかを示す `Type` 属性を追加します。これにより、将来的にはより多くのエンティティタイプの保存をサポートできるようになり、GSI オーバーローディングやスパース GSI などの高度なテクノロジーを使用して、より多くのアクセスパターンに対応できるようになります。

アクセスパターンの実装を始めましょう。プレイヤーの追加や装備の追加などのアクセスパターンは [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 操作を通じて実現できるため、無視して構いません。このドキュメントでは、上記の一般的なアクセスパターンに焦点を当てます。

**ステップ 1: アクセスパターン 1 (`getPlayerFriends`) に対処する**

このステップではアクセスパターン 1 (`getPlayerFriends`) に対処します。現在の設計の場合、友達の関係はシンプルで、ゲーム内の友達の数は少なくなっています。わかりやすくするために、リストデータ型を使用して友達リストを保存します (1:1 モデリング)。この設計では、[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) を使用してこのアクセスパターンに対処します。`GetItem` 操作では、特定の項目を取得するためのパーティションキーとソートキーの値を明示的に指定します。

ただし、ゲームに多数の友達がいて、友達間の関係が複雑な場合 (招待コンポーネントと承諾コンポーネントの両方で友達の関係が双方向であるなど)、友達リストのサイズを無制限にスケールするには、多対多リレーションシップを使用して各友達を個別に保存する必要があります。また、友達関係の変更に伴って複数の項目を同時に操作する必要がある場合は、DynamoDB トランザクションを使用して複数のアクションをグループ化し、単一のオールオアナッシングの [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 操作または [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html) 操作として送信できます。

![\[Friends エンティティのゲームプロファイルの複雑な多対多リレーションシップ図。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile1.png)


**ステップ 2: アクセスパターン 2 (`getPlayerAllProfile`),3 (`getPlayerAllItems`)、4 (`getPlayerSpecificItem`) に対処する**

このステップを使用して、アクセスパターン 2 (`getPlayerAllProfile`)、3 (`getPlayerAllItems`)、4 (`getPlayerSpecificItem`) に対処します。これら 3 つのアクセスパターンに共通しているのは、[`Query`](Query.md) 操作を使用する範囲クエリです。クエリの範囲によっては、実際の開発でよく使用される[キー条件](Query.KeyConditionExpressions.md)および[フィルター式](Query.FilterExpression.md)を使用します。

Query 操作では、パーティションキーとして 1 つの値を指定し、このパーティションキー値を持つすべての項目を取得します。このようにしてアクセスパターン 2 (`getPlayerAllProfile`) を実装します。オプションで、ソートキー条件式、つまりテーブルから読み取る項目を決定する文字列を追加できます。アクセスパターン 3 (`getPlayerAllItems`) は、ソートキー begins\$1with `ITEMS#` というキー条件を追加することで実装します。さらに、アプリケーション側の開発を簡略化するために、フィルター式を使用してアクセスパターン 4 (`getPlayerSpecificItem`) を実装できます。

`Weapon` カテゴリの項目をフィルタリングするフィルター式を使用した疑似コードの例を次に示します。

```
filterExpression: "ItemType = :itemType"
expressionAttributeValues: {":itemType": "Weapon"}
```

![\[パーティションキーとソートキー条件でクエリオペレーションを使用して、さまざまなアクセスパターンを実装します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile2.png)


**注記**  
フィルター式は、クエリの完了後、結果がクライアントに返される前に適用されます。したがって、Query は、フィルター式の有無にかかわらず、同じ量の読み取りキャパシティを消費します。

アクセスパターンとして、大きなデータセットをクエリし、大量のデータをフィルタリングして一部のデータのみを保持する場合、適切なアプローチは DynamoDB パーティションキーとソートキーをより効果的に設計することです。例えば、前述の特定の `ItemType` を取得する例で、各プレイヤーに多数の項目があり、特定の `ItemType` をクエリするのが一般的なアクセスパターンである場合は、`ItemType` を複合キーとして `SK` に取り込む方が効率的です。データモデルは `ITEMS#ItemType#ItemId` のようになります。

**ステップ 3: アクセスパターン 5 (`updateCharacterAttributes`) および 6 (`updateItemCount`) に対処する**

このステップを使用して、アクセスパターン 5 (`updateCharacterAttributes`) と 6 (`updateItemCount`) に対処します。プレイヤーが通貨を減らしたり、項目内の特定の武器の数量を変更したりするなど、キャラクターを変更する必要がある場合は、[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) を使用してこれらのアクセスパターンを実装します。プレイヤーの通貨を更新しながら最低額を下回らないようにするには、[DynamoDB 条件式 CLI の例](Expressions.ConditionExpressions.md) を追加して残高が最低額以上である場合にのみ残高を減らすことができます。擬似コードの例を次に示します。

```
UpdateExpression: "SET currency = currency - :amount"
ConditionExpression: "currency >= :minAmount"
```

![\[条件式で UpdateItem を使用してプレイヤーの通貨を変更し、設定額を下回らないようにします。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile4-Update-player-Currency.png)


DynamoDB で開発し、[アトミックカウンタ](WorkingWithItems.md#WorkingWithItems.AtomicCounters)を使用してインベントリを減らす場合、楽観的ロックを使用することで冪等性を確保できます。アトミックカウンタの擬似コードの例を次に示します。

```
UpdateExpression: "SET ItemCount = ItemCount - :incr"
expression-attribute-values: '{":incr":{"N":"1"}}'
```

![\[アトミックカウンターを使用して ItemCount 属性値を 5 から 4 に減らします。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile5-Update-Item-Count.png)


さらに、プレイヤーが通貨で項目を購入するシナリオでは、プロセス全体で通貨の差し引きと項目の追加を同時に行う必要があります。DynamoDB トランザクションを使用すると、複数のアクションをグループ化し、単一の全部かゼロかの `TransactWriteItems` 操作または `TransactGetItems` 操作として送信できます。`TransactWriteItems` は、単一の全部かゼロかの操作で最大 100 の書き込みアクションをグループ化する、同期かつ冪等性の書き込み操作です。アクションはアトミックに完了するため、すべてが成功するか、どれも成功しません。トランザクションは、通貨の重複や消滅のリスクを排除するのに役立ちます。トランザクションの詳細については、「[DynamoDB トランザクションの例](transaction-example.md)」を参照してください。

すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | その他の条件/フィルター | 
| --- | --- | --- | --- | --- | --- | 
| getPlayerFriends | ベーステーブル | GetItem | PK=PlayerID | SK=“FRIENDS\$1playerID” |  | 
| getPlayerAllProfile | ベーステーブル | クエリ | PK=PlayerID |  |  | 
| getPlayerAllItems | ベーステーブル | クエリ | PK=PlayerID | SK begins\$1with “ITEMS\$1” |  | 
| getPlayerSpecificItem | ベーステーブル | クエリ | PK=PlayerID | SK begins\$1with “ITEMS\$1” | filterExpression: "ItemType = :itemType" expressionAttributeValues: \$1 ":itemType": "Weapon" \$1 | 
| updateCharacterAttributes | ベーステーブル | UpdateItem | PK=PlayerID | SK=“\$1METADATA\$1playerID” | UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount" | 
| updateItemCount | ベーステーブル | UpdateItem | PK=PlayerID | SK =“ITEMS\$1ItemID” | update-expression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '\$1":incr":\$1"N":"1"\$1\$1' | 

## ゲームプロファイルの最終スキーマ
<a name="data-modeling-schema-gaming-profile-final-schema"></a>

これが最終的なスキーマ設計です。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/GamingPlayerProfiles/GamePlayerProfilesSchema.json)を参照してください。

**ベーステーブル:**

![\[前述のアクセスパターン実装の結果を含むテーブルの最終スキーマ設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/GamingProfile6-FinalSchema.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-gaming-profile-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB の苦情管理システムのスキーマ設計
<a name="data-modeling-complaint-management"></a>

## 苦情管理システムのビジネスユースケース
<a name="data-modeling-schema-complaint-management-use-case"></a>

DynamoDB は、苦情管理システム (またはコンタクトセンター) のユースケースに最適なデータベースです。関連するほとんどのアクセスパターンが key-value ベースのトランザクションルックアップであるためです。このシナリオの一般的なアクセスパターンは、次のとおりです。
+ 苦情を作成および更新する
+ 苦情をエスカレートする
+ 苦情に関するコメントを作成および閲覧する
+ 顧客からのすべての苦情を取得する
+ エージェントからのすべてのコメントを取得し、すべてのエスカレーションを取得する 

一部のコメントには、苦情または解決策を説明する添付ファイルが付いている場合があります。これらはすべて key-value アクセスパターンですが、苦情に新しいコメントが追加されたときに通知を送信したり、分析クエリを実行して重大度 (またはエージェントのパフォーマンス) 別に苦情の分布を毎週調べたりするなど、追加の要件が伴う場合があります。ライフサイクル管理やコンプライアンスに関連するその他の要件としては、苦情を 3 年間記録した後に苦情データをアーカイブすることが挙げられます。

## 苦情管理システムのアーキテクチャ図
<a name="data-modeling-schema-complaint-management-ad"></a>

次の図は、苦情管理システムのアーキテクチャ図を示しています。この図は、苦情管理システムが使用するさまざまな AWS のサービス統合を示しています。

![\[複数の AWS のサービスとの統合を使用して、非トランザクション要件を満たすための結合ワークフロー。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-1-AD.jpg)


後述の DynamoDB データモデリングセクションで扱う key-value トランザクションアクセスパターンとは別に、非トランザクション要件が 3 つあります。上のアーキテクチャ図は、次の 3 つのワークフローに分けることができます。

1. 苦情に新しいコメントが追加されたときに通知を送信する

1. 週次データに対して分析クエリを実行する

1. 3 年より前のデータをアーカイブする

それぞれを詳しく見ていきましょう。

**苦情に新しいコメントが追加されたときに通知を送信する**

この要件を満たすには、次のワークフローを使用できます。

![\[Lambda 関数を呼び出して、DynamoDB Streams が記録した変更に基づいて通知を送信するワークフロー。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-2-Workflow1.jpg)


[DynamoDB ストリーム](Streams.md)は、DynamoDB テーブルに対するすべての書き込みアクティビティを記録する変更データキャプチャメカニズムです。これらの変更の一部またはすべてに応じてトリガーされるように Lambda 関数を設定できます。Lambda トリガーに[イベントフィルター](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)を設定して、ユースケースに関係のないイベントを除外できます。この例では、フィルターを使用して、新しいコメントが追加されたときにのみ Lambda をトリガーし、関連する E メール ID ([AWS Secret Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) またはその他の認証情報ストアから取得可能) に通知を送信できます。

**週次データに対して分析クエリを実行する**

DynamoDB は、オンライントランザクション処理 (OLTP) を主眼とするワークロードに適しています。分析が必要な他の 10～20% のアクセスパターンについては、マネージド機能の [Amazon S3 へのエクスポート](S3DataExport.HowItWorks.md)を使用してデータを S3 にエクスポートできます。この機能を使用しても、DynamoDB テーブルのライブトラフィックには影響がありません。次のワークフローをご覧ください。

![\[Lambda 関数を定期的に呼び出して DynamoDB データを Amazon S3 バケットに保存するワークフロー。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-3-Workflow2.jpg)


[Amazon EventBridge](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is) を使用すると、AWS Lambda をスケジュールどおりにトリガーできます。これにより、Lambda を定期的に呼び出すように cron 式を設定できます。Lambda は `ExportToS3` API コールを呼び出し、DynamoDB データを S3 に保存します。この S3 データに [Amazon Athena](https://docs.aws.amazon.com/athena/latest/ug/what-is) などの SQL エンジンからアクセスし、テーブルのライブトランザクションワークロードには影響を与えることなく、DynamoDB データに対して分析クエリを実行できます。重大度レベル別に苦情数を調べる Athena クエリの例は、次のようになります。

```
SELECT Item.severity.S as "Severity", COUNT(Item) as "Count"
FROM "complaint_management"."data"
WHERE NOT Item.severity.S = ''
GROUP BY Item.severity.S ;
```

この Athena クエリは次の結果を返します。

![\[重要度レベル P3、P2、P1 の苦情数を示す Athena クエリ結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-4-Athena.png)


**3 年より前のデータをアーカイブする**

DynamoDB の [Time to Live (TTL)](TTL.md) 機能を使用して、追加費用なしで、DynamoDB テーブルから古いデータを削除できます (ただし、2019.11.21 (現行) バージョンのグローバルテーブルレプリカは除きます。TTL 削除が他のリージョンにレプリケートされると書き込み容量が消費されるためです)。このデータを DynamoDB ストリームで表示し、使用できます。その後に、Amazon S3 にアーカイブできます。この要件のワークフローは次のとおりです。

![\[TTL 機能と DynamoDB Streams を使用して Amazon S3 バケットに古いデータをアーカイブするワークフロー。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-5-Workflow3.jpg)


## 苦情管理システムのエンティティ関係図
<a name="data-modeling-schema-complaint-management-erd"></a>

次に示すのは、苦情管理システムのスキーマ設計に使用するエンティティ関係図 (ERD) です。

![\[エンティティとしての顧客、苦情、コメント、エージェントを示す苦情管理システム ERD。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-6-ERD.jpg)


## 苦情管理システムのアクセスパターン
<a name="data-modeling-schema-complaint-management-access-patterns"></a>

以下は、苦情管理システムのスキーマ設計で検討するアクセスパターンです。

1. createComplaint

1. updateComplaint

1. updateSeveritybyComplaintID

1. getComplaintByComplaintID

1. addCommentByComplaintID

1. getAllCommentsByComplaintID

1. getLatestCommentByComplaintID

1. getAComplaintbyCustomerIDAndComplaintID

1. getAllComplaintsByCustomerID

1. escalateComplaintByComplaintID

1. getAllEscalatedComplaints

1. getEscalatedComplaintsByAgentID (新しいものから古いものへの順)

1. getCommentsByAgentID (2 つの日付間)

## 苦情管理システムのスキーマ設計の進化
<a name="data-modeling-schema-complaint-management-design-evolution"></a>

これは苦情管理システムであるため、ほとんどのアクセスパターンはプライマリエンティティとしての苦情を中心に展開します。`ComplaintID` のカーディナリティが高いと、基盤となるパーティションにデータが均等に分散されます。また、特定したアクセスパターンの最も一般的な検索条件となります。したがって、`ComplaintID` は、このデータセットのパーティションキー候補として適しています。

**ステップ 1: アクセスパターン 1 (`createComplaint`)、2 (`updateComplaint`)、3 (`updateSeveritybyComplaintID`)、4 (`getComplaintByComplaintID`) に対処する**

「metadata」(または「AA」) という汎用ソートキー値を使用して、`CustomerID`、`State`、`Severity`、`CreationDate` などの苦情固有の情報を保存できます。`PK=ComplaintID` と `SK=“metadata”` でシングルトンオペレーションを使用して、以下を行います。

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) で新しい苦情を作成する

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) で苦情メタデータの重大度やその他のフィールドを更新する

1. [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) で苦情のメタデータを取得する

![\[苦情項目のプライマリキー、ソートキー、および属性値 (customer_id や重要度など)。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-7-Step1.png)


**ステップ 2: アクセスパターン 5 (`addCommentByComplaintID`) に対処する**

このアクセスパターンでは、苦情と苦情に関するコメントとの間に 1 対多リレーションシップモデルが必要です。ここでは、[垂直パーティショニング](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)手法を通じてソートキーを使用し、さまざまなタイプのデータを含む項目コレクションを作成します。アクセスパターン 6 (`getAllCommentsByComplaintID`) と 7 (`getLatestCommentByComplaintID`) に注目すると、コメントを時間順に並べ替える必要があることがわかります。また、複数のコメントを同時に受け取ることができるため、[複合ソートキー](data-modeling-blocks.md#data-modeling-blocks-composite)手法を使用して時間と `CommentID` をソートキー属性に追加できます。

このようなコメント競合の可能性に対処する他のオプションとしては、タイムスタンプの粒度を増やすか、`Comment_ID` を使用する代わりに増分数値をサフィックスとして追加します。この場合、範囲ベースの操作を可能にするために、コメントに対応する項目のソートキー値の前に「comm\$1」を付けます。

また、苦情メタデータの `currentState` に、新しいコメントを追加したときの状態が反映されることを確認する必要があります。コメントの追加は、エージェントへの苦情の割り当てや苦情の解決などを示す場合があります。コメントの追加と現在の状態の更新をオールオアナッシング方式で苦情メタデータにバンドルするには、[TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) API を使用します。結果として得られるテーブルの状態は次のようになります。

![\[複合ソートキーを使用して苦情とそのコメントを 1 対多リレーションシップとして保存するテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-8-Step2.png)


テーブルにさらにデータを追加し、`PK` とは別のフィールドとして `ComplaintID` も追加しましょう。`ComplaintID` に追加のインデックスが必要な場合に備えて、モデルを将来的に保証するためです。また、一部のコメントには添付ファイルが含まれている場合があることに注意してください。添付ファイルは、Amazon Simple Storage Service に保存し、その参照または URL のみを DynamoDB で管理します。コストとパフォーマンスを最適化するために、トランザクションデータベースはできるだけリーンな状態に保つことがベストプラクティスです。これで、データは次のようになります。

![\[苦情メタデータと、各苦情に関連するすべてのコメントのデータを含むテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-9-Step3.png)


**ステップ 3: アクセスパターン 6 (`getAllCommentsByComplaintID`) と 7 (`getLatestCommentByComplaintID`) に対処する**

苦情に関するすべてのコメントを取得するには、ソートキーに対する [`query`](Query.md) オペレーションで `begins_with` 条件を使用できます。メタデータエントリを読み取るために追加の読み取り容量を消費したり、関連する結果をフィルタリングするためのオーバーヘッドを生じたりせずに、このようなソートキー条件を設定することで、必要なものだけを読み取ることができます。例えば、`SK` および `PK=Complaint123` begins\$1with `comm#` を使用したクエリオペレーションは、メタデータエントリをスキップして、以下を返します。

![\[苦情のコメントのみを表示するソートキー条件を使用した、クエリオペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-10-Step4.png)


パターン 7 (`getLatestCommentByComplaintID`) では苦情に関する最新のコメントが必要であるため、次の 2 つのクエリパラメータを追加しましょう。

1. 結果を降順にソートするために False に設定する `ScanIndexForward`

1. 最新のコメントを (1つだけ) 取得するために 1 に設定する `Limit`

アクセスパターン 6 (`getAllCommentsByComplaintID`) と同じように、ソートキー条件として `begins_with` `comm#` を使用してメタデータエントリを省略します。これで、クエリオペレーションで `PK=Complaint123` と `SK=begins_with comm#` に加えて、`ScanIndexForward=False` と `Limit` を使用し、この設計でアクセスパターン 7 を実行できるようになりました。結果として、次のターゲット項目が返されます。

![\[苦情の最後のコメントを取得するためのソートキー条件を使用したクエリオペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-11-Step5.png)


さらにダミーデータをテーブルに追加してみましょう。

![\[受信した苦情に関する最新のコメントを取得するための、ダミーデータを含むテーブル。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-12-Step6.png)


**ステップ 4: アクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) と 9 (`getAllComplaintsByCustomerID`) に対処する**

アクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) と 9 (`getAllComplaintsByCustomerID`) は、新しい検索条件 `CustomerID` を導入します。これを既存のテーブルから取得するには、すべてのデータを読み取って当の `CustomerID` の関連項目をフィルタリングするために、コストの高い [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) が必要になります。この検索をより効率的にするには、`CustomerID` をパーティションキーとして[グローバルセカンダリインデックス (GSI)](GSI.md) を作成できます。顧客と苦情の 1 対多リレーションシップおよびアクセスパターン 9 (`getAllComplaintsByCustomerID`) を考慮すると、`ComplaintID` がソートキーの適切な候補となります。

GSI のデータは次のようになります。

![\[特定の CustomerID ですべての苦情を取得するための 1 対多リレーションシップモデルを含む GSI。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-13-Step4-GSI.png)


 この GSI のクエリ例はアクセスパターン 8 (`getAComplaintbyCustomerIDAndComplaintID`) で `customer_id=custXYZ`、`sort key=Complaint1321` となります。結果は次のようになります。

![\[特定の顧客からの特定の苦情データを取得するための GSI に対するクエリオペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-14-Step4-8.png)


アクセスパターン 9 (`getAllComplaintsByCustomerID`) でお客様の苦情をすべて取得する場合、GSI のクエリはパーティションキー条件が `customer_id=custXYZ` になります。結果は次のようになります。

![\[特定の顧客からのすべての苦情を取得するためにパーティションキー条件を使用したクエリオペレーションの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-15-Step4-9.png)


**ステップ 5: アクセスパターン 10 (`escalateComplaintByComplaintID`) に対処する**

このアクセスでは、エスカレーションのディメンションを導入します。苦情をエスカレーションするには、`UpdateItem` を使用して `escalated_to` や `escalation_time` などの属性を既存の苦情メタデータ項目に追加します。DynamoDB のスキーマ設計は柔軟であるため、キー以外の属性のセットをさまざまな項目間で均一にすることも、個別にすることもできます。次の例を参照してください。

`UpdateItem with PK=Complaint1444, SK=metadata`

![\[UpdateItem API オペレーションを使用して苦情メタデータを更新した結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-16-Step5.png)


**ステップ 6: アクセスパターン 11 (`getAllEscalatedComplaints`) と 12 (`getEscalatedComplaintsByAgentID`) に対処する**

データセット全体からエスカレーションされると予想される苦情はほんの一握りです。したがって、エスカレーション関連の属性にインデックスを作成すると、効率的な検索と費用対効果の高い GSI ストレージが実現します。これを実現するには、[スパースインデックス](data-modeling-blocks.md#data-modeling-blocks-sparse-index)手法を活用できます。パーティションキーを `escalated_to`、ソートキーを `escalation_time` とする GSI は、次のようになります。

![\[エスカレーション関連の属性 (escalated_to と escalation_time) を使用した GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-17-Step6.png)


アクセスパターン 11 (`getAllEscalatedComplaints`) ですべてのエスカレーションされた苦情を取得するには、この GSI を単にスキャンします。このスキャンは、GSI のサイズにより、パフォーマンスとコスト効率が向上することに注意してください。特定のエージェント (アクセスパターン 12 (`getEscalatedComplaintsByAgentID`)) でエスカレーションされた苦情を取得する場合は、パーティションキーを `escalated_to=agentID` とし、`ScanIndexForward` を `False` に設定して新しいものから古いものへの順に取得します。

**ステップ 7: アクセスパターン 13 (`getCommentsByAgentID`) に対処する**

最後のアクセスパターンでは、新しいディメンション `AgentID` によるルックアップを実行する必要があります。また、2 つの日付間のコメントを読み取るには時間ベースの順序付けも必要であるため、パーティションキーを `agent_id`、ソートキーを `comm_date` として GSI を作成します。この GSI のデータは次のようになります。

![\[コメント日付を使用してソートした、特定のエージェントからのコメントを検索する GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-18.png)


この GSI のクエリの例は `partition key agentID=AgentA` および `sort key=comm_date between (2023-04-30T12:30:00, 2023-05-01T09:00:00)` となり、その結果は次のようになります。

![\[GSI でパーティションキーとソートキーを使用したクエリの結果。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-19.png)


すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | その他の条件/フィルター | 
| --- | --- | --- | --- | --- | --- | 
| createComplaint | ベーステーブル | PutItem | PK=complaint\$1id | SK=metadata |  | 
| updateComplaint | ベーステーブル | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| updateSeveritybyComplaintID | ベーステーブル | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getComplaintByComplaintID | ベーステーブル | GetItem | PK=complaint\$1id | SK=metadata |  | 
| addCommentByComplaintID | ベーステーブル | TransactWriteItems | PK=complaint\$1id | SK=metadata, SK=comm\$1comm\$1date\$1comm\$1id |  | 
| getAllCommentsByComplaintID | ベーステーブル | クエリ | PK=complaint\$1id | SK begins\$1with "comm\$1" |  | 
| getLatestCommentByComplaintID | ベーステーブル | クエリ | PK=complaint\$1id | SK begins\$1with "comm\$1" | scan\$1index\$1forward=False, Limit 1 | 
| getAComplaintbyCustomerIDAndComplaintID | Customer\$1complaint\$1GSI | クエリ | customer\$1id=customer\$1id | complaint\$1id = complaint\$1id |  | 
| getAllComplaintsByCustomerID | Customer\$1complaint\$1GSI | クエリ | customer\$1id=customer\$1id | 該当なし |  | 
| escalateComplaintByComplaintID | ベーステーブル | UpdateItem | PK=complaint\$1id | SK=metadata |  | 
| getAllEscalatedComplaints | Escalations\$1GSI | Scan | 該当なし | 該当なし |  | 
| getEscalatedComplaintsByAgentID (新しいものから古いものへの順) | Escalations\$1GSI | クエリ | escalated\$1to=agent\$1id | 該当なし | scan\$1index\$1forward=False | 
| getCommentsByAgentID (2 つの日付間) | Agents\$1Comments\$1GSI | クエリ | agent\$1id=agent\$1id | SK between (date1, date2) |  | 

## 苦情管理システムの最終スキーマ
<a name="data-modeling-schema-complaint-management-final-schema"></a>

最終的なスキーマ設計は次のとおりです。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ComplainManagement/ComplaintManagementSchema.json)を参照してください。

**ベーステーブル**

![\[苦情メタデータを含むベーステーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-20-Complaint_management_system.png)


**Customer\$1Complaint\$1GSI**

![\[特定の顧客からの苦情を示す GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-21-Customer_Complaint_GSI.png)


**Escalations\$1GSI**

![\[エスカレーション関連の属性を示す GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-22-Escalations_GSI.png)


**Agents\$1Comments\$1GSI**

![\[特定のエージェントからのコメントを示す GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ComplaintManagement-23-Comments_GSI.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-complaint-management-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB での定期払いスキーマの設計
<a name="data-modeling-schema-recurring-payments"></a>

## 定期払いのビジネスユースケース
<a name="data-modeling-schema-recurring-payments-use-case"></a>

このユースケースでは、DynamoDB を使用して定期払いシステムを実装する方法について説明します。データモデルには、アカウント、サブスクリプション、領収書のエンティティがあります。******このユースケースの詳細は次のとおりです。
+ アカウントごとに複数のサブスクリプションを持つことができます。****
+ サブスクリプションは、次の支払いを処理する必要があるときは `NextPaymentDate`、E メールによるリマインダーをお客様に送信するときは `NextReminderDate` です。**
+ サブスクリプションには、支払いが処理されると、保存および更新される項目があります (平均の項目サイズは約 1 KB で、スループットはアカウントとサブスクリプションの数によって異なります)。******
+ 支払いプロセッサは、処理の一環として領収書も作成します。領収書は、テーブルに保存され、[TTL](TTL.md) 属性を使用して一定期間後に期限切れになるように設定されます。****

## 定期払いエンティティ関係図
<a name="data-modeling-schema-recurring-payments-erd"></a>

これは、定期払いシステムのスキーマ設計に使用するエンティティ関係図 (ERD) です。

![\[エンティティとしてアカウント、サブスクリプション、領収書を示す定期払いシステムの ERD。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-1-ERD.png)


## 定期払いシステムのアクセスパターン
<a name="data-modeling-schema-recurring-payments-access-patterns"></a>

これらは、定期払いシステムのスキーマ設計で検討するアクセスパターンです。

1. `createSubscription`

1. `createReceipt`

1. `updateSubscription`

1. `getDueRemindersByDate`

1. `getDuePaymentsByDate`

1. `getSubscriptionsByAccount`

1. `getReceiptsByAccount`

## 定期払いのスキーマ設計
<a name="data-modeling-schema-recurring-payments-design-evolution"></a>

一般的な名前である `PK` や `SK` は、アカウント、サブスクリプション、領収書などの異なる種類のエンティティを同じテーブルに保存するためのキー属性に使用します。ユーザーは最初にサブスクリプションを作成します。サブスクリプションにより、ユーザーは製品の代金として毎月同じ日に金額を支払うことに同意します。月内のどの日に支払いを処理するかは、ユーザーが選択できます。支払いを処理する前にリマインダーも送信されます。このアプリケーションは、毎日 2 つのバッチジョブを実行することで機能します。1 つのバッチジョブでは、当日を期限とするリマインダーを送信し、もう 1 つのバッチジョブでは、当日を支払期日とするすべての支払いを処理します。

**ステップ 1: アクセスパターン 1 (`createSubscription`) に対処する**

アクセスパターン 1 (`createSubscription`) を使用してサブスクリプションを最初に作成し、`SKU`、`NextPaymentDate`、`NextReminderDate`、`PaymentDetails` などの詳細を設定します。このステップでは、1 つのサブスクリプションで 1 つのアカウントに限り、テーブルの状態を表示します。項目コレクションには複数のサブスクリプションを含めることができるため、これは 1 対多リレーションシップになります。

![\[アカウントのサブスクリプションの詳細を示すテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-2-Step1.png)


**ステップ 2: アクセスパターン 2 (`createReceipt`) と 3 (`updateSubscription`) に対処する**

アクセスパターン 2 (`createReceipt`) を使用して領収書項目を作成します。毎月支払いを処理すると、支払いプロセッサは領収書をベーステーブルに書き戻します。項目コレクションには複数の領収書を含めることができるため、これは 1 対多リレーションシップです。支払いプロセッサは、サブスクリプション項目 (アクセスパターン 3 (`updateSubscription`)) も更新し、翌月の `NextReminderDate` または `NextPaymentDate` に変更します。

![\[次のサブスクリプションのリマインダー日付を示す領収書の詳細とサブスクリプション項目の更新。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-3-Step2.png)


**ステップ 3: アクセスパターン 4 (`getDueRemindersByDate`) に対処する**

アプリケーションは、当日の支払いのリマインダーをバッチで処理します。そのため、アプリケーションは別のディメンション (アカウントではなく日付) でサブスクリプションにアクセスする必要があります。これは、[グローバルセカンダリインデックス (GSI)](GSI.md) に適したユースケースです このステップでは、インデックス `GSI-1` を追加し、`NextReminderDate` を GSI パーティションキーとして使用します。すべての項目をレプリケートする必要はありません。この GSI は[スパースインデックス](data-modeling-blocks.md#data-modeling-blocks-sparse-index)であり、領収書項目はレプリケートされません。また、すべての属性を射影する必要はなく、属性の一部を含めるだけで済みます。次の図に示す `GSI-1` のスキーマは、アプリケーションがリマインダー E メールを送信するために必要な情報を提供します。

![\[アプリケーションからリマインダー E メールを送信する先の E メールアドレスなどの詳細を示す GSI-1 スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-4-Step3.png)


**ステップ 4: アクセスパターン 5 (`getDuePaymentsByDate`) に対処する**

アプリケーションは、リマインダーの場合と同じように、当日の支払いをバッチで処理します。このステップでは `GSI-2` を追加し、`NextPaymentDate` を GSI パーティションキーとして使用します。すべての項目をレプリケートする必要はありません。GSI はスパースインデックスであり、領収書項目はレプリケートされません。次の図は `GSI-2` のスキーマを示しています。

![\[支払いを処理するための詳細を示す GSI-2 スキーマ。NextPaymentDate は GSI-2 のパーティションキーです。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-5-Step4.png)


**ステップ 5: アクセスパターン 6 (`getSubscriptionsByAccount`) と 7 (`getReceiptsByAccount`) に対処する**

アプリケーションは、ベーステーブルでアカウント識別子 (`PK`) をターゲットとする[クエリ](Query.md)を使用して、アカウントのすべてのサブスクリプションを取得できます。また、範囲演算子を使用して `SK` が「SUB\$1」で始まるすべての項目を取得できます。また、アプリケーションは、同じクエリ構造を使用してすべての領収書を取得し、範囲演算子を使用して `SK` が「REC\$1」で始まるすべての項目を取得できます。これにより、アクセスパターン 6 (`getSubscriptionsByAccount`) と 7 (`getReceiptsByAccount`) に対処できます。これらのアクセスパターンをアプリケーションで使用することで、ユーザーは現在のサブスクリプションと過去 6 か月間の領収書を確認できます。このステップでは、テーブルスキーマに変更はありません。アクセスパターン 6 (`getSubscriptionsByAccount`) でサブスクリプション項目だけをターゲットにする方法は次の表で確認できます。

![\[ベーステーブルに対するクエリオペレーションの結果。特定のアカウントのサブスクリプションを示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-6-Step5.png)


すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | 
| --- | --- | --- | --- | --- | 
| CreateSubscription | ベーステーブル | PutItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| createReceipt | ベーステーブル | PutItem | ACC\$1account\$1id | REC\$1<RecieptDate>\$1SKU<SKUID> | 
| updateSubscription | ベーステーブル | UpdateItem | ACC\$1account\$1id | SUB\$1<SUBID>\$1SKU<SKUID> | 
| getDueRemindersByDate | GSI-1 | クエリ | <NextReminderDate> |  | 
| getDuePaymentsByDate | GSI-2 | クエリ | <NextPaymentDate> |  | 
| getSubscriptionsByAccount | ベーステーブル | クエリ | ACC\$1account\$1id | SK begins\$1with “SUB\$1” | 
| getReceiptsByAccount | ベーステーブル | クエリ | ACC\$1account\$1id | SK begins\$1with “REC\$1” | 

## Recurring payments final schema
<a name="data-modeling-schema-recurring-payments-final-schema"></a>

最終的なスキーマ設計は次のとおりです。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/blob/master/schema_design/SchemaExamples/ReocurringPayments/ReocurringPaymentsSchema.json)を参照してください。

**ベーステーブル**

![\[アカウント情報と、そのサブスクリプションや領収書の詳細を示すベーステーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-7-Base.png)


**GSI-1**

![\[E メールアドレスや NextPaymentDate などのサブスクリプションの詳細を示す GSI-1 スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-8-GSI1.png)


**GSI-2**

![\[PaymentAmount や PaymentDay などの支払いの詳細を示す GSI-2 スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/ReoccurringPayments-9-GSI2.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-recurring-payments-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB でのデバイスステータス更新のモニタリング
<a name="data-modeling-device-status"></a>

このユースケースでは、DynamoDB を使用して、DynamoDB 内でデバイスのステータスの更新 (またはデバイスのステータスの変更) をモニタリングする方法について説明しています。

## ユースケース
<a name="data-modeling-schema-device-status-use-case"></a>

IoT のユースケース (スマートファクトリーなど) では、多くのデバイスをオペレーターがモニタリングする必要があり、オペレーターはステータスやログをモニタリングシステムに定期的に送信します。デバイスに問題が発生すると、デバイスのステータスが [正常] から [警告] に変わります。デバイスの異常な動作の重大度やタイプによって、さまざまなログレベルまたはステータスがあります。その後、システムはデバイスをチェックするオペレーターを割り当て、オペレーターは必要に応じて問題を上司にエスカレーションします。

このシステムの典型的なアクセスパターンには次のものがあります。
+ デバイスのログエントリを作成する
+ 特定のデバイスステータスのすべてのログを取得し、最新のログを最初に表示する
+ 2 つの日付間の特定のオペレーターのすべてのログを取得する
+ 特定のスーパーバイザーのエスカレーションされたログをすべて取得する
+ 特定のスーパーバイザーの特定のデバイスステータスに関するすべてのエスカレーションログを取得
+ 特定の日付における特定のスーパーバイザーの特定のデバイスステータスを含むすべてのエスカレーションされたログを取得する

## エンティティ関係図
<a name="data-modeling-schema-device-status-erd"></a>

これは、デバイスステータスの更新をモニタリングするために使用するエンティティ関係図 (ERD) です。

![\[デバイスステータスの更新を示す ERD。エンティティ (デバイスとオペレーター) を示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-1-ERD.jpg)


## アクセスパターン
<a name="data-modeling-schema-device-status-access-patterns"></a>

デバイスのステータス更新をモニタリングする方法について説明します。

1. `createLogEntryForSpecificDevice`

1. `getLogsForSpecificDevice`

1. `getWarningLogsForSpecificDevice`

1. `getLogsForOperatorBetweenTwoDates`

1. `getEscalatedLogsForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisor`

1. `getEscalatedLogsWithSpecificStatusForSupervisorForDate`

## スキーマ設計の進化
<a name="data-modeling-schema-device-status-design-evolution"></a>

**ステップ 1: アクセスパターン 1 (`createLogEntryForSpecificDevice`) と 2 (`getLogsForSpecificDevice`) に対処する**

デバイス追跡システムのスケーリングの単位は、個々のデバイスです。このシステムでは、`deviceID` はデバイスを一意に識別します。これにより、`deviceID` がパーティションキーの適切な候補となります。各デバイスは定期的に (例えば 5 分おきに) 情報を追跡システムに送信します。この順序によって、日付が論理的なソート基準となり、したがってソートキーになります。この場合のサンプルデータは次のようになります。

![\[複数のデバイスのステータスを保存するテーブル。DeviceID はプライマリキー、ステータス更新日はソートキーです。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-2-Step1.png)


特定のデバイスのログエントリを取得するには、パーティションキー `DeviceID="d#12345"` を使用して[クエリ](Query.md)オペレーションを実行します。

**ステップ 2: アクセスパターン 3 (`getWarningLogsForSpecificDevice`) に対処する**

`State` は非キー属性であるため、現在のスキーマでアクセスパターン 3 に対処するには[フィルター式](Query.FilterExpression.md)が必要になります。DynamoDB では、フィルター式はキー条件式を使用してデータを読み取った後に適用されます。例えば、`d#12345` の警告ログを取得する場合、パーティションキー `DeviceID="d#12345"` を使用したクエリオペレーションは、上記のテーブルから 4 つの項目を読み取り、*警告*ステータスなしで 1 つの項目をフィルターで除外します。このアプローチは大規模な場合は効率的ではありません。フィルター式は、除外される項目の割合が低い場合や、クエリの実行頻度が低い場合に、クエリ対象の項目を除外するのに適した方法です。ただし、テーブルから多くの項目が取得され、その大半の項目がフィルターで除外される場合は、より効率的に実行されるようにテーブル設計を進化させ続けることができます。

[複合ソートキー](data-modeling-blocks.md#data-modeling-blocks-composite)を使用して、このアクセスパターンを処理する方法を変更してみましょう。ソートキーを `State#Date` に変更したサンプルデータを [DeviceStateLog\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_3.json) からインポートできます。このソートキーは、属性 `State`、`#`、および `Date` から構成されます。この例では、`#` は区切り文字として使用されています。これで、データは次のようになります。

![\[複合ソートキー State#Date を使用して取得したデバイス d#12345 のステータス更新データ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-3-Step2.png)


デバイスの警告ログのみを取得する場合は、このスキーマの方がクエリの対象が絞り込まれます。クエリのキー条件にはパーティションキー `DeviceID="d#12345"` とソートキー `State#Date begins_with “WARNING”` が使用されます。このクエリは、警告ステータスの関連する 3 つの項目のみを読み取ります。

**ステップ 3: アクセスパターン 4 (`getLogsForOperatorBetweenTwoDates`) に対処する**

`Operator` 属性がサンプルデータとともに `DeviceStateLog` テーブルに追加された [DeviceStateLog\$14.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_4.json)D をインポートできます。

![\[特定の日付間におけるオペレーターのログを取得するための Operator 属性を備えた DeviceStateLog テーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-4-Step3.png)


`Operator` は現在パーティションキーではないため、`OperatorID` に基づいてこのテーブルのキーと値を直接検索する方法はありません。`OperatorID` にグローバルセカンダリインデックスを使用して新しい[項目コレクション](WorkingWithItemCollections.md)を作成する必要があります。アクセスパターンでは日付に基づく検索が必要なため、日付が[グローバルセカンダリインデックス (GSI)](GSI.md) のソートキー属性になります。GSI は現在次のようになっています。

![\[特定の演算子のログを取得するために OperatorID と Date をパーティションキーおよびソートキーとして使用する GSI 設定。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-5-Step3.png)


アクセスパターン 4 (`getLogsForOperatorBetweenTwoDates`) の場合、`2020-04-11T05:58:00` と `2020-04-24T14:50:00` の間のパーティションキー `OperatorID=Liz` とソートキー `Date` を使用してこの GSI をクエリできます。

![\[OperatorID と Date を使用して GSI にクエリを実行し、2 つの日付間における演算子のログを取得します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-6-GSI1_1.png)


**ステップ 4: アクセスパターン 5 (`getEscalatedLogsForSupervisor`)、6 (`getEscalatedLogsWithSpecificStatusForSupervisor`)、および 7 (`getEscalatedLogsWithSpecificStatusForSupervisorForDate`) に対処する**

[スパースインデックス](data-modeling-blocks.md#data-modeling-blocks-sparse-index)を使用して、これらのアクセスパターンに対処します。

グローバルセカンダリインデックスはデフォルトではスパースであるため、インデックスのプライマリキー属性を含むベーステーブルの項目だけが実際にインデックスに表示されます。これは、モデル化するアクセスパターンに関係のない項目を除外するもう 1 つの方法です。

`EscalatedTo` 属性がサンプルデータとともに `DeviceStateLog` テーブルに追加された [DeviceStateLog\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/device-state-log/json/DeviceStateLog_6.json) をインポートできます。前述のように、すべてのログがスーパーバイザーにエスカレーションされるわけではありません。

![\[スーパーバイザーにエスカレーションされたすべてのログを取得するための EscalatedTo 属性を備えた GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-7-Step4.png)


これで、`EscalatedTo` がパーティションキー、`State#Date` がソートキーである新しい GSI を作成できるようになりました。`EscalatedTo` 属性と `State#Date` 属性の両方を持つ項目のみがインデックスに表示されることに注意してください。

![\[GSI 設計で EscalatedTo 属性と State#Date 属性を備えたすべての項目を取得する GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-8-Step4.png)


その他のアクセスパターンは、以下のようにまとめられています。

    アクセスパターン 5 (GetEscalatedLogsForSupervisor) では、エスカレーション GSI に対してパーティションキー EscalatedTo="Sara" でクエリを実行できます。   アクセスパターン 6 (getEscalatedLogsWithSpecificStatusForSupervisor) の場合、パーティションキー EscalatedTo="Sara" およびソートキー State\$1Date begins\$1with "WARNING" を使用してエスカレーション GSI に対してクエリを実行できます。   アクセスパターン 7 (getEscalatedLogsWithSpecificStatusForSupervisorForDate) の場合、パーティションキー EscalatedTo="Sara" およびソートキー State\$1Date begins\$1with "WARNING4\$12020-04-27" を使用してエスカレーション GSI に対してクエリを実行できます。    

すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | その他の条件/フィルター | 
| --- | --- | --- | --- | --- | --- | 
| createLogEntryForSpecificDevice | ベーステーブル | PutItem | DeviceID=deviceId | State\$1Date=state\$1date |  | 
| getLogsForSpecificDevice | ベーステーブル | クエリ | DeviceID=deviceId | State\$1Date begins\$1with "state1\$1" | ScanIndexForward = False | 
| getWarningLogsForSpecificDevice | ベーステーブル | クエリ | DeviceID=deviceId | State\$1Date begins\$1with "WARNING" |  | 
| getLogsForOperatorBetweenTwoDates | GSI-1 | クエリ | Operator=operatorName | Date between date1 and date2 |  | 
| getEscalatedLogsForSupervisor | GSI-2 | クエリ | EscalatedTo=supervisorName |  |  | 
| getEscalatedLogsWithSpecificStatusForSupervisor | GSI-2 | クエリ | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1" |  | 
| getEscalatedLogsWithSpecificStatusForSupervisorForDate | GSI-2 | クエリ | EscalatedTo=supervisorName | State\$1Date begins\$1with "state1\$1date1" |  | 

## 最終スキーマ
<a name="data-modeling-schema-device-status-final-schema"></a>

最終的なスキーマ設計は次のとおりです。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の [DynamoDB の例](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)を参照してください。

**ベーステーブル**

![\[デバイス ID、状態、日付などのデバイスステータスメタデータを使用したベーステーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-9-Table.png)


**GSI-1**

![\[GSI-1 設計。プライマリキーと属性 (DeviceID、State#Date、State) を示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-10-GSI1.png)


**GSI-2**

![\[GSI-2 設計。プライマリキーと属性 (DeviceID、Operator、Date、State) を示します。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/DeviceStatus-11-GSI2.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-device-status-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB をオンラインショップのデータストアとして使用する
<a name="data-modeling-online-shop"></a>

このユースケースでは、オンラインショップ (または e ストア) のデータストアとして DynamoDB を使用する方法について説明しています。

## ユースケース
<a name="data-modeling-schema-online-shop"></a>

オンラインストアでは、ユーザーはさまざまな製品を閲覧し、最終的に購入することができます。生成された請求書に基づいて、お客様はディスカウントコードまたはギフトカードを使用して支払い、残りの金額をクレジットカードで支払うことができます。購入した商品は、複数の倉庫から集荷され、指定された住所に発送されます。オンラインストアの一般的なアクセスパターンは以下のとおりです。
+ 指定された customerId のお客様を取得する
+ 指定された productId の製品を取得する
+ 指定された warehouseId の倉庫を取得する
+ productId で全倉庫の商品在庫を取得する
+ 指定された OrderId の注文を取得する
+ 指定された OrderID のすべての商品を取得する
+ 指定された OrderID の請求書を取得する
+ 指定された OrderID のすべての出荷情報を取得する
+ 指定された日付範囲における指定された productId のすべての注文を取得する
+ 指定された invoiceId の請求書を取得する
+ 指定された InvoiceID のすべての支払いを取得する
+ 指定された shipmentId の出荷詳細を取得する
+ 指定された warehouseId のすべての出荷情報を取得する
+ 指定された warehouseId のすべての商品の在庫を取得する
+ 指定された日付範囲における指定された customerId のすべての請求書を取得する
+ 指定された日付範囲で指定された customerId で注文されたすべての製品を取得する

## エンティティ関係図
<a name="data-modeling-schema-online-shop-erd"></a>

これは、オンラインショップのデータストアとして DynamoDB をモデル化するために使用するエンティティ関係図 (ERD) です。

![\[製品、注文、支払い、顧客などのエンティティを示す、オンラインストアのデータモデルの ERD。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-1-ERD.png)


## アクセスパターン
<a name="data-modeling-schema-online-shop-access-patterns"></a>

以上が、DynamoDB をオンラインショップのデータストアとして利用する場合のアクセスパターンです。

1. `getCustomerByCustomerId`

1. `getProductByProductId`

1. `getWarehouseByWarehouseId`

1. `getProductInventoryByProductId`

1. `getOrderDetailsByOrderId`

1. `getProductByOrderId`

1. `getInvoiceByOrderId`

1. `getShipmentByOrderId`

1. `getOrderByProductIdForDateRange`

1. `getInvoiceByInvoiceId`

1. `getPaymentByInvoiceId`

1. `getShipmentDetailsByShipmentId`

1. `getShipmentByWarehouseId`

1. `getProductInventoryByWarehouseId`

1. `getInvoiceByCustomerIdForDateRange`

1. `getProductsByCustomerIdForDateRange`

## スキーマ設計の進化
<a name="data-modeling-schema-online-shop-design-evolution"></a>

[DynamoDB 用の NoSQL Workbench](workbench.md) を使用して、[AnOnlineShop\$11.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_1.json) をインポートし、`AnOnlineShop` という新しいデータモデルと `OnlineShop` という新しいテーブルを作成します。パーティションキーとソートキーには一般的な名前 `PK` と `SK` を使用することに注意してください。これは、さまざまなタイプのエンティティを同じテーブルに保存するための方法です。

**ステップ 1: アクセスパターン 1 (`getCustomerByCustomerId`) に対処する**

[AnOnlineShop\$12.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_2.json) をインポートして、アクセスパターン 1 (`getCustomerByCustomerId`) を処理します。エンティティの中には他のエンティティとリレーションシップを持たないものもあるため、それらには同じ値の `PK` と `SK` を使用します。データ例では、`customerId` を後で追加される他のエンティティから区別するために、キーに接頭辞 `c#` が使われていることに注意してください。この方法は他のエンティティでも繰り返されます。

このアクセスパターンに対処するために、[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) オペレーションを `PK=customerId` と `SK=customerId` で使用できます。

**ステップ 2: アクセスパターン 2 (`getProductByProductId`) に対処する**

[AnOnlineShop\$13.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_3.json) をインポートして、`product` エンティティのアクセスパターン 2 (`getProductByProductId`) に対処します。製品エンティティには `p#` というプレフィックスが付けられ、同じソートキー属性が `customerID` と `productID` の保存にも使われています。一般的な命名規則と[垂直パーティショニング](data-modeling-blocks.md#data-modeling-blocks-vertical-partitioning)によって、このような項目コレクションを作成し、効果的な単一テーブル設計を実現できます。

このアクセスパターンに対処するため、`GetItem` オペレーションは `PK=productId` および `SK=productId` で使用できます。

**ステップ 3: アクセスパターン 3 (`getWarehouseByWarehouseId`) に対処する**

[AnOnlineShop\$14.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_4.json) をインポートして、`warehouse` エンティティのアクセスパターン 3 (`getWarehouseByWarehouseId`) に対処します。現在、`customer`、`product`、および `warehouse` エンティティが同じテーブルに追加されています。これらはプレフィックスと `EntityType` 属性を使用して区別されます。タイプ属性 (またはプレフィックスの命名) は読みやすさを向上させます。さまざまなエンティティの英数字 ID を同じ属性に保存するだけでは、読みやすさに影響が出ます。これらの識別子がないと、あるエンティティを別のエンティティと区別するのが難しくなります。

このアクセスパターンに対処するため、`GetItem` オペレーションは `PK=warehouseId` および `SK=warehouseId` で使用できます。

**ベーステーブル **– 

![\[ID で倉庫データを取得するためのプレフィックスと EntityType を含む DynamoDB テーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-2-Step3.png)


**ステップ 4: アクセスパターン 4 (`getProductInventoryByProductId`) に対処する**

[AnOnlineShop\$15.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_5.json) をインポートしてアクセスパターン 4 (`getProductInventoryByProductId`) に対処します。`warehouseItem` エンティティは、各倉庫の商品数を追跡するために使用されます。この項目は通常、倉庫に商品が追加されたり、倉庫から商品が削除されたりすると更新されます。ERD にあるように、`product` と `warehouse` の間には多対多リレーションシップがあります。ここで、`product` から `warehouse` への 1 対多リレーションシップは `warehouseItem` のようにモデル化されます。その後、`warehouse` から `product` への 1 対多リレーションシップもモデル化されます。

アクセスパターン 4 は、`PK=ProductId` および `SK begins_with “w#“` に対するクエリで対処できます。

ソートキーに適用できる `begins_with()` およびその他の式の詳細については、「[キー条件式](Query.KeyConditionExpressions.md)」を参照してください。

**ベーステーブル **– 

![\[特定の倉庫の製品在庫を追跡するために ProductID と warehouseId をクエリするテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-3-Step4.png)


**ステップ 5: アクセスパターン 5 (`getOrderDetailsByOrderId`) と 6 (`getProductByOrderId`) に対処する**

[AnOnlineShop\$16.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_6.json) をインポートして、さらに `customer`、`product`、および `warehouse` の項目をテーブルに追加します。次に、[AnOnlineShop\$17.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_7.json) をインポートして、アクセスパターン 5 (`getOrderDetailsByOrderId`) および 6 (`getProductByOrderId`) に対処できる `order` 用項目コレクションを構築します。orderItem エンティティとしてモデル化された `order` と `product` の間の 1 対多リレーションシップを確認できます。

アクセスパターン 5 (`getOrderDetailsByOrderId`) に対処するには、`PK=orderId` を使用してテーブルをクエリします。これにより、`customerId` および注文された製品を含む注文に関するすべての情報が提供されます。

**ベーステーブル **– 

![\[orderId を使用してクエリを実行し、注文したすべての製品に関する情報を取得するテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-4-Step5.png)


アクセスパターン 6 (`getProductByOrderId`) に対処するには、`order` 内の製品のみを読み取る必要があります。これを達成するために、`PK=orderId` および `SK begins_with “p#”` を使用してテーブルをクエリします。

**ベーステーブル **– 

![\[orderId と productId を使用してクエリし、注文内の製品を取得するテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-5-Step5.png)


**ステップ 6: アクセスパターン 7 (`getInvoiceByOrderId`) に対処する**

[AnOnlineShop\$18.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_8.json) をインポートして、アクセスパターン 7 (`getInvoiceByOrderId`) を処理する注文項目コレクションに `invoice` エンティティを追加します。このアクセスパターンに対処するために、`PK=orderId` および `SK begins_with “i#”` でクエリオペレーションを使用できます。

**ベーステーブル **– 

![\[orderId で請求書を取得するための請求書エンティティを注文項目コレクション内に含むテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-6-Step6.png)


**ステップ 7: アクセスパターン 8 (`getShipmentByOrderId`) に対処する**

[AnOnlineShop\$19.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_9.json) をインポートして、アクセスパターン 8 (`getShipmentByOrderId`) を処理する注文項目コレクションに `shipment` エンティティを追加します。単一テーブルの設計にさらに多くの種類のエンティティを追加することで、同じ垂直分割モデルを拡張しています。注文項目コレクションに、`order` エンティティが `shipment`、`orderItem`、および `invoice` エンティティと持つさまざまな関係がどのように含まれているかに注意してください。

`orderId` による出荷を取得するには、`PK=orderId` と `SK begins_with “sh#”` でクエリオペレーションを実行します。

**ベーステーブル **– 

![\[注文 ID で出荷を取得するための出荷エンティティが注文項目コレクションに追加されているテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-7-Step7.png)


**ステップ 8: アクセスパターン 9 (`getOrderByProductIdForDateRange`) に対処する**

前のステップで、注文項目コレクションを作成しました。このアクセスパターンには新しいルックアップディメンション (`ProductID` および `Date`) があり、テーブル全体をスキャンし、関連するレコードをフィルタリングして、ターゲット項目を取得する必要があります。このアクセスパターンに対処するには、[グローバルセカンダリインデックス (GSI)](GSI.md) を作成する必要があります。[AnOnlineShop\$110.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_10.json) をインポートし、複数の注文項目コレクションから `orderItem` データを取得できる GSI を使用して、新しい項目コレクションを作成します。データには `GSI1-PK` と `GSI1-SK` が含まれ、それぞれ `GSI1` のパーティションキーとソートキーになります。

DynamoDB は、GSI のキー属性を含む項目をテーブルから GSI に自動的に入力します。GSI に追加の挿入を手動で行う必要はありません。

アクセスパターン 9 に対処するには、`GSI1-PK=productId` および `GSI1SK between (date1, date2)` を使用して `GSI1` でクエリを実行します。

**ベーステーブル **– 

![\[複数の注文項目コレクションから注文データを取得するための GSI を含むテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-8-Step8-Base.png)


**GSI1:**

![\[製品 ID と日付で注文を取得するための ProductID と Date をパーティションキーおよびソートキーとして含む GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-9-Step8-GSI.png)


**ステップ 9: アクセスパターン 10 (`getInvoiceByInvoiceId`) と 11 (`getPaymentByInvoiceId`) に対処する**

[AnOnlineShop\$111.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_11.json) をインポートして、アクセスパターン 10 (`getInvoiceByInvoiceId`) と 11 (`getPaymentByInvoiceId`) に対処します。どちらも `invoice` に関連しています。これらは 2 つの異なるアクセスパターンですが、同じキー条件を使用して実現されます。`Payments` は、`invoice` エンティティのマップデータタイプを持つ属性として定義されます。

**注記**  
`GSI1-PK` と `GSI1-SK` は、さまざまなエンティティに関する情報を保存するためにオーバーロードされているため、同じ GSI から複数のアクセスパターンに対応できます。GSI オーバーロードの詳細については、「[DynamoDB のグローバルセカンダリインデックスの多重定義](bp-gsi-overloading.md)」を参照してください。

アクセスパターン 10 と 11 に対処するには、`GSI1-PK=invoiceId` および `GSI1-SK=invoiceId` を使用して `GSI1` に対してクエリを実行します。

**GSI1:**

![\[請求書 ID で請求書と支払いを取得するための invoiceId をパーティションキーおよびソートキーの両方として含む GSI 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-10-Step9.png)


**ステップ 10: アクセスパターン 12 (`getShipmentDetailsByShipmentId`) と 13 (`getShipmentByWarehouseId`) に対処する**

[AnOnlineShop\$112.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_12.json) をインポートして、アクセスパターン 12 (`getShipmentDetailsByShipmentId`) と 13 (`getShipmentByWarehouseId`) に対処します。

1 回のクエリオペレーションで注文に関するすべての詳細を取得できるように、`shipmentItem` エンティティがベーステーブルの注文項目コレクションに追加されることに注意してください。

**ベーステーブル **– 

![\[すべての注文の詳細を取得するための shipmentItem エンティティを注文項目コレクション内に含むテーブル設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-11-Step10.png)


`GSI1` パーティションとソートキーは、`shipment` と `shipmentItem` の間の 1 対多リレーションシップをモデル化するために既に使用されています。アクセスパターン 12 (`getShipmentDetailsByShipmentId`) に対処するには、`GSI1-PK=shipmentId` および `GSI1-SK=shipmentId` を使用して `GSI1` に対してクエリを実行します。

**GSI1:**

![\[出荷 ID で出荷の詳細を取得するための shipmentId をパーティションおよびソートキーとして含む GSI1 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-12-Step10-GSI.png)


アクセスパターン 13 (`getShipmentByWarehouseId`) の `warehouse` と `shipment` の間の新しい 1 対多リレーションシップをモデル化するには、別の GSI (`GSI2`) を作成する必要があります。アクセスパターンに対処するには、`GSI2-PK=warehouseId` および `GSI2-SK begins_with “sh#”` を使用して `GSI2` に対してクエリを実行します。

**GSI2:**

![\[倉庫別の出荷を取得するための warehouseId と shipmentId をパーティションキーおよびソートキーとして含む GSI2 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-13-Step10-GSI2.png)


**ステップ 11: アクセスパターン 14 (`getProductInventoryByWarehouseId`)、15 (`getInvoiceByCustomerIdForDateRange`)、および 16 (`getProductsByCustomerIdForDateRange`) に対処する**

[AnOnlineShop\$113.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_13.json) をインポートして、次のアクセスパターンセットに関連するデータを追加します。アクセスパターン 14 (`getProductInventoryByWarehouseId`) に対処するには、`GSI2-PK=warehouseId` および `GSI2-SK begins_with “p#”` を使用して `GSI2` に対してクエリを実行します。

**GSI2:**

![\[アクセスパターン 14 に対処するための warehouseId と productId をパーティションキーおよびソートキーとして含む GSI2 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-14-Step11-GSI2.png)


アクセスパターン 15 (`getInvoiceByCustomerIdForDateRange`) に対処するには、`GSI2-PK=customerId` および `GSI2-SK between (i#date1, i#date2)` を使用して `GSI2` に対してクエリを実行します。

**GSI2:**

![\[アクセスパターン 15 に対処するための customerId と請求書の日付範囲をパーティションキーおよびソートキーとして含む GSI2 設計。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-15-Step11-GSI2.png)


アクセスパターン 16 (`getProductsByCustomerIdForDateRange`) に対処するには、`GSI2-PK=customerId` および `GSI2-SK between (p#date1, p#date2)` を使用して `GSI2` に対してクエリを実行します。

**GSI2:**

![\[アクセスパターン 16 に対処するための customerId と製品の日付範囲をパーティションキーおよびソートキーとして含む GSI2 設計\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-16-Step11-GSI2.png)


**注記**  
[NoSQL Workbench](workbench.md) では、ファセットは DynamoDB に対するアプリケーションのさまざまなデータアクセスパターンを表します。ファセットを使用すると、ファセットの制約を満たさないレコードを表示することなく、テーブル内のデータのサブセットを表示できます。プロビジョンドキャパシティの引き上げがファセットはビジュアルデータモデリングツールと見なされ、アクセスパターンのモデリングを純粋に補助するものであるため、DynamoDB で使用できるコンストラクトとしては存在しません。  
[AnOnlineShop\$1facets.json](https://github.com/aws-samples/amazon-dynamodb-design-patterns/blob/master/examples/an-online-shop/json/AnOnlineShop_facets.json) をインポートして、このユースケースのファセットを確認します。

すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。


| アクセスパターン | ベーステーブル/GSI/LSI | Operation | パーティションキー値 | ソートキー値 | 
| --- | --- | --- | --- | --- | 
| getCustomerByCustomerId | ベーステーブル | GetItem |  PK=customerId | SK=customerId | 
| getProductByProductId | ベーステーブル | GetItem |  PK=productId | SK=productId | 
| getWarehouseByWarehouseId | ベーステーブル | GetItem |  PK=warehouseId | SK=warehouseId | 
| getProductInventoryByProductId | ベーステーブル | クエリ |  PK=productId | SK begins\$1with "w\$1" | 
| getOrderDetailsByOrderId | ベーステーブル | クエリ |  PK=orderId |  | 
| getProductByOrderId | ベーステーブル | クエリ |  PK=orderId | SK begins\$1with "p\$1" | 
| getInvoiceByOrderId |  ベーステーブル | クエリ |  PK=orderId | SK begins\$1with "i\$1" | 
| getShipmentByOrderId |  ベーステーブル | クエリ |  PK=orderId | SK begins\$1with "sh\$1" | 
| getOrderByProductIdForDateRange |  GSI1 | クエリ |  PK=productId | SK between date1 and date2 | 
| getInvoiceByInvoiceId |  GSI1 | クエリ |  PK=invoiceId | SK=invoiceId | 
| getPaymentByInvoiceId |  GSI1 | クエリ |  PK=invoiceId | SK=invoiceId | 
| getShipmentDetailsByShipmentId |  GSI1 | クエリ |  PK=shipmentId | SK=shipmentId | 
| getShipmentByWarehouseId |  GSI2 | クエリ |  PK=warehouseId | SK begins\$1with "sh\$1" | 
| getProductInventoryByWarehouseId |  GSI2 | クエリ |  PK=warehouseId | SK begins\$1with "p\$1" | 
| getInvoiceByCustomerIdForDateRange |  GSI2 | クエリ |  PK=customerId | SK between i\$1date1 and i\$1date2 | 
| getProductsByCustomerIdForDateRange |  GSI2 | クエリ |  PK=customerId | SK between p\$1date1 and p\$1date2 | 

### オンラインショップの最終スキーマ
<a name="data-modeling-schema-online-store-final-schema"></a>

最終的なスキーマ設計は次のとおりです。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の「[DynamoDB 設計パターン](https://github.com/aws-samples/aws-dynamodb-examples/tree/master/schema_design/SchemaExamples)」を参照してください。

**ベーステーブル** – 

![\[EntityName や Name などの属性を持つオンラインショップのベーステーブルの最終スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-17-Final-BaseTable.png)


**GSI1**

![\[EntityType などの属性を持つオンラインショップのベーステーブルの最終 GSI1 スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-18-Final-GSI1.png)


**GSI2**

![\[EntityType などの属性を持つオンラインショップのベーステーブルの最終 GSI2 スキーマ。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/DataModeling/OnlineShop-19-Final-GSI2.png)


## このスキーマ設計での NoSQL Workbench の使用
<a name="data-modeling-schema-online-shop-nosql"></a>

この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである [NoSQL Workbench](workbench.md) にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。

1. NoSQL Workbench をダウンロードします。詳細については、「[DynamoDB 用の NoSQL Workbench のダウンロード](workbench.settingup.md)」を参照してください。

1. 上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。

1. JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「[既存のデータモデルのインポート](workbench.Modeler.ImportExisting.md)」を参照してください。

1. NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「[既存のデータモデルの編集](workbench.Modeler.Edit.md)」を参照してください。

# DynamoDB でリレーショナルデータをモデル化するためのベストプラクティス
<a name="bp-relational-modeling"></a>

このセクションでは、Amazon DynamoDB でリレーショナルデータをモデル化するためのベストプラクティスを説明します。まず、従来のデータモデリングのコンセプトを紹介します。次に、従来のリレーショナルデータベース管理システムではなく DynamoDB を使用する利点について説明します。つまり JOIN オペレーションが不要になり、オーバーヘッドが削減されるということです。

次に、効率的にスケーリングする DynamoDB テーブルを設計する方法を説明します。最後に、DynamoDB でリレーショナルデータをモデル化する方法の例を示します。

**Topics**
+ [従来のリレーショナルデータベースモデル](#SQLtoNoSQL.relational-modeling2)
+ [DynamoDB によって JOIN オペレーションが不要になる理由](#bp-relational-modeling-joins)
+ [DynamoDB トランザクションが書き込みプロセスのオーバーヘッドを排除する方法](#bp-relational-modeling-transactions)
+ [DynamoDB でリレーショナルデータをモデル化するための最初のステップ](bp-modeling-nosql.md)
+ [DynamoDB でリレーショナルデータをモデル化する例](bp-modeling-nosql-B.md)

## 従来のリレーショナルデータベースモデル
<a name="SQLtoNoSQL.relational-modeling2"></a>

従来のリレーショナルデータベース管理システム (RDBMS) は、正規化されたリレーショナル構造でデータを保存します。リレーショナルデータモデルの目的は、(正規化によって) データの重複を減らして参照整合性を維持し、データの異常を削減することです。

次のスキーマは、一般的な注文入力アプリケーションのリレーショナルデータ型の一例です。このアプリケーションは、理論上の製造元の運用およびビジネスサポートシステムを支える人事スキーマをサポートします。

![\[RDBMS スキーマの例。\]](http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/images/RDBMS.png)


非リレーショナルデータベースサービスである DynamoDB には、従来のリレーショナルデータベース管理システムと比較して多くの利点があります。

## DynamoDB によって JOIN オペレーションが不要になる理由
<a name="bp-relational-modeling-joins"></a>

RDBMS は、構造クエリ言語 (SQL) を使用してデータをアプリケーションに返します。データ型の正規化により、このようなクエリでは通常、`JOIN` 演算子を使用して 1 つ以上のテーブルのデータを結合する必要があります。

例えば、各項目を出荷するすべてのウェアハウスで在庫数量でソートされた発注書項目のリストを生成するには、前のスキーマに対して次の SQL クエリを発行します。

```
SELECT * FROM Orders
  INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
  INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
  INNER JOIN Inventories ON Products.Product_ID = Inventories.Product_ID
  ORDER BY Quantity_on_Hand DESC
```

この種の SQL クエリは、データにアクセスするための柔軟な API を提供できますが、大量の処理が必要です。クエリを結合するたびに、クエリのランタイムの複雑さが増します。これは、各テーブルのデータをステージングしてからアセンブルして結果セットを返す必要があるためです。

クエリの実行時間に影響するその他の要因は、テーブルのサイズおよび結合される列にインデックスがあるかどうかです。前述のクエリは、複数のテーブルにわたって複雑なクエリを実行し、結果として生成されるセットをソートします。

`JOINs` の必要性をなくすことが NoSQL データモデリングの中核となります。これが、Amazon.com をサポートするために DynamoDB を構築した理由であり、DynamoDB があらゆる規模で一貫したパフォーマンスを提供できる理由です。SQL クエリと `JOINs` のランタイムの複雑さを考慮すると、RDBMS のパフォーマンスは規模に応じて一定ではありません。そのため、お客様のアプリケーションが大きくなるにつれてパフォーマンスの問題が発生します。

データを正規化することでディスクに保存されるデータの量は削減されますが、多くの場合、パフォーマンスに影響する最も制約の厳しいリソースは CPU 時間とネットワーク遅延です。

DynamoDB は、`JOINs` を削除し(かつデータの非正規化を促進し)、データベースアーキテクチャを最適化して、項目に 1 回のリクエストでアプリケーションクエリに完全に応答します。これによって、両方の制約を最小限に抑えます。これらの特性により、DynamoDB は規模にかかわらず 1 桁のミリ秒単位のパフォーマンスを実現できます。これは、一般的なアクセスパターンでは、DynamoDB オペレーションの実行時の複雑さが一定であるためです。

## DynamoDB トランザクションが書き込みプロセスのオーバーヘッドを排除する方法
<a name="bp-relational-modeling-transactions"></a>

RDBMS の速度を低下させるもう 1 つの要因は、正規化されたスキーマに書き込むためにトランザクションを使用することです。例に示す通り、ほとんどのオンライントランザクション処理 (OLTP) アプリケーションで使用されるリレーショナルデータ構造は、RDBMS に格納されているときに複数の論理テーブルに分割して分散させる必要があります。

そのため、アプリケーションで書き込み中のオブジェクトを読み込もうとした場合に発生する可能性のある競合状態およびデータの整合性の問題を回避するためには、ACID 準拠のトランザクションフレームワークが必要です。こうしたトランザクションフレームワークをリレーショナルスキーマと組み合わせると、書き込みプロセスにかなりのオーバーヘッドが発生する可能性があります。

DynamoDB にトランザクションを実装すると、RDBMS で一般的に見られるスケーリングの問題が防止されます。DynamoDB では、トランザクションを 1 回の API 呼び出しとして発行し、その 1 回のトランザクションでアクセスできる項目の数を制限することで、これを行っています。トランザクションの実行時間が長いと、トランザクションがクローズされないため、データのロックが長期間、または永続的に保持され、運用上の問題が発生する可能性があります。

DynamoDB でのこのような問題を防ぐため、トランザクションは `TransactWriteItems` と `TransactGetItems` の 2 つの異なる API オペレーションを使用して実装されました。これらの API オペレーションには、RDBMS で一般的な開始および終了セマンティクスがありません。さらに、DynamoDB では、同様に長時間実行されるトランザクションを防ぐため、1 回のトランザクション内で 100 項目のアクセス制限を設けています。DynamoDB トランザクションの詳細については、「[トランザクションでの使用](transactions.md)」を参照してください。

こうした理由から、高トラフィックのクエリに対して低レイテンシーの応答が必要な場合は、NoSQL システムを利用すると、一般的に技術的および経済的な効果がもたらされます。Amazon DynamoDB では、リレーショナルシステムのスケーラビリティを制限する問題を回避できます。

RDBMS のパフォーマンスは、通常、以下の理由から適切にスケーリングできません。
+ 高価な結合を使用して、クエリ結果の必要なビューを再構成します。
+ データを正規化し、複数のクエリを必要とする複数のテーブルに格納して、ディスクに書き込みます。
+ 一般的に、ACID 準拠のトランザクションシステムのパフォーマンスコストが発生します。

DynamoDB は、次の理由により適切に拡張されます。
+ スキーマの柔軟性により、DynamoDB は複雑な階層データを 1 つの項目内に格納できます。
+ 複合キー設計では、関連する項目を同じテーブルの範囲内に格納できます。
+ トランザクションは 1 回のオペレーションで実行されます。オペレーションの実行が長時間に及ぶのを避けるため、アクセスできる項目の上限数は 100 となっています。

データストアに対するクエリは、多くの場合次のような形式で、非常に簡単になります。

```
SELECT * FROM Table_X WHERE Attribute_Y = "somevalue"
```

DynamoDB は、以前の例の RDBMS と比較して、リクエストされたデータを返す作業がはるかに少なくなっています。

# DynamoDB でリレーショナルデータをモデル化するための最初のステップ
<a name="bp-modeling-nosql"></a>

**注記**  
NoSQL 設計では、RDBMS 設計とは異なる考え方が必要です。RDBMS の場合は、アクセスパターンを考慮せずに正規化されたデータモデルを作成できます。その後、新しい課題とクエリの要件が発生したら、そのデータモデルを拡張することができます。対照的に、Amazon DynamoDB の場合は答えが必要な質問が分かるまで、スキーマの設計を開始しないでください。ビジネス上の問題とアプリケーションのユースケースを理解することが極めて重要です。

効率的に拡張する DynamoDB テーブルの設計を開始するには、サポートする必要のあるオペレーションおよびビジネスサポートシステム (OSS/BSS) で必要とされるアクセスパターンを特定するために、まずいくつかの手順を実行する必要があります。
+ 新しいアプリケーションの場合は、アクティビティや目的に関するユーザーストーリーを確認します。特定するさまざまなユースケースを文書化し、必要なアクセスパターンを分析します。
+ 既存のアプリケーションでは、クエリログを分析して、ユーザーが現在どのようにシステムを使用しているか、主要なアクセスパターンを調べます。

このプロセスが完了したら、次のようなリストが表示されます。


**注文入力アプリケーションのアクセスパターン**  

| パターン \$1 | アクセスパターンの説明 | 
| --- | --- | 
| 1 | 従業員 ID で従業員の詳細を検索する | 
| 2 | 従業員名で従業員の詳細をクエリする | 
| 3 | 従業員の電話番号を検索する (複数可) | 
| 4 | 顧客の電話番号を検索する (複数可) | 
| 5 | 日付範囲内の顧客の注文を取得する | 
| 6 | 日付範囲内のすべての OPEN の注文を表示する | 
| 7 | 最近雇用したすべての従業員を確認する | 
| 8 | 倉庫のすべての従業員を検索する | 
| 9 | 製品の注文中のすべての項目を取得する | 
| 10 | すべての倉庫の製品在庫を取得する | 
| 11 | アカウント担当者別に顧客を取得する | 
| 12 | アカウント担当者別に注文を取得する | 
| 13 | 役職を持つ従業員を取得する | 
| 14 | 商品および倉庫別に在庫を取得する | 
| 15 | 商品在庫総数を取得する | 

実際のアプリケーションでは、リストはさらに長くなる場合があります。しかし、このコレクションは、本番環境で見つかる可能性のあるクエリパターンの複雑さの範囲を表します。

DynamoDB スキーマ設計の最新のアプローチでは、集約指向の原則を使用し、厳格なエンティティの境界ではなくアクセスパターンに基づいてデータをグループ化します。このアプローチでは、複数の設計パターンを考慮します。
+ *単一テーブル設計* - 複合ソートキー、オーバーロードされたグローバルセカンダリインデックス、隣接リストパターンを使用して、複数のエンティティタイプを 1 つのテーブルに保存します
+ *マルチテーブル設計* - 独立した運用特性を持ち、アクセス相関が低いエンティティに個別のテーブルを使用し、エンティティ間のクエリには戦略的な GSI を使用します
+ *集約設計* - 常に一緒にアクセスされる場合に関連するデータを埋め込む (Order \$1 OrderItems)、または関係を識別するために項目コレクションを使用します (Product \$1 Inventory)

これらのアプローチの選択は、特定のアクセスパターン、データ特性、運用要件によって異なります。これらの要素を使用してデータを構造化することで、アプリケーションは、テーブルまたはインデックスの単一のクエリを使用して、特定のアクセスパターンに必要なものを取得できます。

**注記**  
単一テーブル設計とマルチテーブル設計の選択は、特定の要件によって異なります。単一テーブル設計は、エンティティのアクセス相関性が高く、運用特性が類似している場合に適しています。エンティティに独立した運用要件や異なるアクセスパターンがある場合、または明確な運用上の境界が必要な場合は、マルチテーブル設計が推奨されます。このガイドの例では、戦略的集約と非正規化によるマルチテーブルアプローチを示しています。

NoSQL Workbench for DynamoDB を使用してパーティションキー設計を視覚化する方法については、「[NoSQL Workbench を使用したデータモデルの構築](workbench.Modeler.md)」を参照してください。

# DynamoDB でリレーショナルデータをモデル化する例
<a name="bp-modeling-nosql-B"></a>

この例では、Amazon DynamoDB でリレーショナルデータをモデル化する方法について説明します。DynamoDB テーブルの設計は、「[リレーショナルモデル化](bp-relational-modeling.md)」に示されているリレーショナルオーダーのエントリスキーマに対応しています。この設計では、単一の隣接リストではなく複数の特殊なテーブルを使用し、戦略的 GSI を活用してすべてのアクセスパターンを効率的に処理しながら、明確な運用上の境界を提供します。

設計アプローチでは、集約指向の原則を使用し、厳格なエンティティ境界ではなくアクセスパターンに基づいてデータをグループ化します。主な設計上の決定事項には、アクセス相関が低いエンティティに個別のテーブルを使用すること、常に一緒にアクセスされる場合に関連データを埋め込むこと、関係を特定するための項目コレクションを使用することが含まれます。

次のテーブルとそれに付随するインデックスは、リレーショナルオーダーのエントリスキーマをサポートしています。

## 従業員テーブルの設計
<a name="employee-table-design"></a>

従業員テーブルには、従業員情報が項目ごとに 1 つのエンティティとして保存され、従業員を直接検索できるように最適化し、戦略的 GSI を通じて複数のクエリパターンがサポートされます。このテーブルは、独立した運用特性を持ち、エンティティ間のアクセス相関が低いエンティティに対して個別のテーブルを設計する原則を示しています。

このテーブルでは、各従業員が個別のエンティティであるため、ソートキーなしでシンプルなパーティションキー (employee\$1id) を使用します。4 つの GSI により、さまざまな属性による効率的なクエリが可能になります。
+ *EmployeeByName GSI* - すべての従業員属性で INCLUDE 射影を使用して、従業員の詳細を名前で完全に取得し、employee\$1id をソートキーとして重複する可能性のある名前を処理します。
+ *EmployeeByWarehouse GSI* - 必須属性 (名前、job\$1title、hire\$1date) のみを含む INCLUDE 射影を使用して、倉庫ベースのクエリをサポートしながらストレージコストを最小限に抑えます
+ *EmployeeByJobTitle GSI* - レポートと組織分析のための INCLUDE 射影を使用してロールベースのクエリを有効にします
+ *EmployeeByHireDate GSI* - hire\$1date をソートキーとする静的パーティションキー値「EMPLOYEE」を使用して、最近の採用者に対して効率的な日付範囲クエリを有効にします。従業員の追加/更新は通常 1,000 WCU 未満であるため、ホットパーティションの問題が発生することなく、単一のパーティションで書き込み負荷を処理できます。


**従業員テーブル - ベーステーブル構造**  

| employee\$1id (PK) | 名前 | phone\$1number | Warehouse\$1id | job\$1title | hire\$1date | entity\$1type | 
| --- | --- | --- | --- | --- | --- | --- | 
| emp\$1001 | John Smith | ["\$11-555-0101"] | wh\$1sea | マネージャー | 2024-03-15 | EMPLOYEES | 
| emp\$1002 | Jane Doe | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | アソシエイト | 2025-01-10 | EMPLOYEES | 
| emp\$1003 | Bob Wilson | ["\$11-555-0104"] | wh\$1pdx | アソシエイト | 2025-06-20 | EMPLOYEES | 
| emp\$1004 | Alice Brown | ["\$11-555-0105"] | wh\$1pdx | スーパーバイザー | 2023-11-05 | EMPLOYEES | 
| emp\$1005 | Charlie Davis | ["\$11-555-0106"] | wh\$1sea | アソシエイト | 2025-12-01 | EMPLOYEES | 


**EmployeeByName GSI - 従業員名クエリのサポート**  

| 名前 (GSI-PK) | employee\$1id (GSI-SK) | phone\$1number | Warehouse\$1id | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | --- | 
| Alice Brown | emp\$1004 | ["\$11-555-0105"] | wh\$1pdx | スーパーバイザー | 2023-11-05 | 
| Bob Wilson | emp\$1003 | ["\$11-555-0104"] | wh\$1pdx | アソシエイト | 2025-06-20 | 
| Charlie Davis | emp\$1005 | ["\$11-555-0106"] | wh\$1sea | アソシエイト | 2025-12-01 | 
| Jane Doe | emp\$1002 | ["\$11-555-0102", "\$11-555-0103"] | wh\$1sea | アソシエイト | 2025-01-10 | 
| John Smith | emp\$1001 | ["\$11-555-0101"] | wh\$1sea | マネージャー | 2024-03-15 | 


**EmployeeByWarehouse GSI - 倉庫クエリのサポート**  

| Warehouse\$1id (GSI-PK) | employee\$1id (GSI-SK) | 名前 | job\$1title | hire\$1date | 
| --- | --- | --- | --- | --- | 
| wh\$1pdx | emp\$1003 | Bob Wilson | アソシエイト | 2025-06-20 | 
| wh\$1pdx | emp\$1004 | Alice Brown | スーパーバイザー | 2023-11-05 | 
| wh\$1sea | emp\$1001 | John Smith | マネージャー | 2024-03-15 | 
| wh\$1sea | emp\$1002 | Jane Doe | アソシエイト | 2025-01-10 | 
| wh\$1sea | emp\$1005 | Charlie Davis | アソシエイト | 2025-12-01 | 


**EmployeeByJobTitle GSI - 役職クエリのサポート**  

| job\$1title (GSI-PK) | employee\$1id (GSI-SK) | 名前 | Warehouse\$1id | hire\$1date | 
| --- | --- | --- | --- | --- | 
| アソシエイト | emp\$1002 | Jane Doe | wh\$1sea | 2025-01-10 | 
| アソシエイト | emp\$1003 | Bob Wilson | wh\$1pdx | 2025-06-20 | 
| アソシエイト | emp\$1005 | Charlie Davis | wh\$1sea | 2025-12-01 | 
| マネージャー | emp\$1001 | John Smith | wh\$1sea | 2024-03-15 | 
| スーパーバイザー | emp\$1004 | Alice Brown | wh\$1pdx | 2023-11-05 | 


**EmployeeByHireDate GSI - 最近の採用者クエリのサポート**  

| entity\$1type (GSI-PK) | hire\$1date (GSI-SK) | employee\$1id | 名前 | Warehouse\$1id | 
| --- | --- | --- | --- | --- | 
| EMPLOYEES | 2023-11-05 | emp\$1004 | Alice Brown | wh\$1pdx | 
| EMPLOYEES | 2024-03-15 | emp\$1001 | John Smith | wh\$1sea | 
| EMPLOYEES | 2025-01-10 | emp\$1002 | Jane Doe | wh\$1sea | 
| EMPLOYEES | 2025-06-20 | emp\$1003 | Bob Wilson | wh\$1pdx | 
| EMPLOYEES | 2025-12-01 | emp\$1005 | Charlie Davis | wh\$1sea | 

## 顧客テーブルの設計
<a name="customer-table-design"></a>

顧客テーブルは、Account\$1rep\$1id を戦略的に非正規化して顧客情報を維持し、アカウント担当者のクエリを効率的に実行できるようにします。この設計の選択により、わずかなストレージオーバーヘッドと引き換えにクエリパフォーマンスが向上し、顧客データとアカウント担当者データの結合が不要になります。

このテーブルは、リスト属性を使用して顧客ごとに複数の電話番号をサポートしており、DynamoDB のスキーマの柔軟性を示しています。単一の GSI により、アカウント担当者のワークフローが有効になります。
+ *CustomerByAccountRep GSI* - 名前と E メール属性を含む INCLUDE 射影を使用して、完全な顧客レコードの取得を必要とせずに、アカウント担当者の顧客管理をサポートします。


**顧客テーブル - ベーステーブル構造**  

| customer\$1id (PK) | 名前 | phone\$1number | E メール | account\$1rep\$1id | 
| --- | --- | --- | --- | --- | 
| cust\$1001 | Acme Corp | ["\$11-555-1001"] | contact@acme.com | rep\$1001 | 
| cust\$1002 | TechStart Inc | ["\$11-555-1002", "\$11-555-1003"] | info@techstart.com | rep\$1001 | 
| cust\$1003 | Global Traders | ["\$11-555-1004"] | sales@globaltraders.com | rep\$1002 | 
| cust\$1004 | BuildRight LLC | ["\$11-555-1005"] | orders@buildright.com | rep\$1002 | 
| cust\$1005 | FastShip Co | ["\$11-555-1006"] | support@fastship.com | rep\$1003 | 


**CustomerByAccountRep GSI - アカウント担当者クエリのサポート**  

| account\$1rep\$1id (GSI-PK) | customer\$1id (GSI-SK) | 名前 | E メール | 
| --- | --- | --- | --- | 
| rep\$1001 | cust\$1001 | Acme Corp | contact@acme.com | 
| rep\$1001 | cust\$1002 | TechStart Inc | info@techstart.com | 
| rep\$1002 | cust\$1003 | Global Traders | sales@globaltraders.com | 
| rep\$1002 | cust\$1004 | BuildRight LLC | orders@buildright.com | 
| rep\$1003 | cust\$1005 | FastShip Co | support@fastship.com | 

## 注文テーブルの設計
<a name="order-table-design"></a>

注文テーブルは、注文ヘッダーと注文項目に個別の項目を含む垂直パーティショニングを使用します。この設計により、すべての注文コンポーネントを同じパーティション内に維持しながら、効率的な製品ベースのクエリが可能になり、効率的にアクセスできるようになります。各注文は複数の項目で構成されます。
+ *注文ヘッダー* - PK=order\$1id、SK=order\$1id の注文メタデータが含まれます
+ *注文項目* - PK=order\$1id、SK=product\$1id を持つ個々の明細項目。直接の製品クエリを有効にします

**注記**  
この垂直パーティショニングアプローチは、埋め込み注文項目のシンプルさと引き換えに、クエリの柔軟性を高めます。各注文項目は個別の DynamoDB 項目になり、すべての注文データを同じパーティション内に維持しながら効率的な製品ベースのクエリを実行し、単一のリクエストで効率的に取得できるようになります。

このテーブルには、顧客検索を必要とせずに直接アカウント担当者クエリを有効にするための account\$1rep\$1id (顧客テーブルから複製) の戦略的な非正規化が含まれています。高スループットの書き込みシナリオの場合、OPEN の注文には、複数のパーティションにわたる書き込みシャーディングを有効にするためのステータスとシャード属性が含まれます。

4 つの GSI は、最適化された射影を使用してさまざまなクエリパターンをサポートします。
+ *OrderByCustomerDate GSI* - 注文の概要と項目の詳細を含む INCLUDE 射影を使用して、日付範囲フィルタリングによる顧客の注文履歴をサポートします
+ *OpenOrdersByDate GSI (スパース、シャード)* - 5 つのシャードを持つマルチ属性パーティションキー (ステータス \$1 シャード) を使用して、5,000 WPS (1 秒あたりの書き込み) をパーティション全体に分散します (各パーティションに 1,000 WPS、DynamoDB の パーティション制限 1,000 WCU に一致)。OPEN の注文のみをインデックスします (全体の 20%)。これにより、GSI ストレージコストを削減できます。クライアント側で結果をマージし、5 つのシャードすべてにわたって並列クエリを実行する必要があります
+ *OrderByAccountRep GSI* - 完全な注文詳細がないアカウント担当者のワークフローをサポートするために、注文概要属性を含む INCLUDE 射影を使用します
+ *ProductInOrders GSI* - OrderItem レコード (PK=order\$1id、SK=product\$1id) から作成されたこの GSI により、特定の製品を含むすべての注文を検索するクエリが可能になります。製品需要分析のために、注文コンテキスト (customer\$1id、order\$1date、数量) を含む INCLUDE 射影を使用します


**注文テーブル - ベーステーブル構造 (垂直パーティショニング)**  

| PK | SK | customer\$1id | order\$1date | ステータス | account\$1rep\$1id | quantity | 料金 | シャード | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| ord\$1001 | ord\$1001 | cust\$1001 | 2025-11-15 | クローズ | rep\$1001 |  |  |  | 
| ord\$1001 | prod\$1100 |  |  |  |  | 5 | 25.00 |  | 
| ord\$1002 | ord\$1002 | cust\$1001 | 2025-12-20 | OPEN | rep\$1001 |  |  | 0 | 
| ord\$1002 | prod\$1101 |  |  |  |  | 10 | 15.00 |  | 
| ord\$1003 | ord\$1003 | cust\$1002 | 2026-01-05 | OPEN | rep\$1001 |  |  | 2 | 
| ord\$1003 | prod\$1100 |  |  |  |  | 3 | 25.00 |  | 


**OrderByCustomerDate GSI - 顧客注文クエリのサポート**  

| customer\$1id (GSI-PK) | order\$1date (GSI-SK) | order\$1id | ステータス | total\$1amount | order\$1items | シャード | 
| --- | --- | --- | --- | --- | --- | --- | 
| cust\$1001 | 2025-11-15 | ord\$1001 | クローズ | 225.00 | [\$1product\$1id: "prod\$1100", qty: 5\$1] |  | 
| cust\$1001 | 2025-12-20 | ord\$1002 | OPEN | 150.00 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 0 | 
| cust\$1002 | 2026-01-05 | ord\$1003 | OPEN | 175.00 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 2 | 
| cust\$1003 | 2025-10-10 | ord\$1004 | クローズ | 250.00 | [\$1product\$1id: "prod\$1101", qty: 5\$1] |  | 
| cust\$1004 | 2026-01-03 | ord\$1005 | OPEN | 200.00 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 1 | 


**OpenOrdersByDate GSI (スパース、シャード) - 高スループットの Open 注文クエリのサポート**  

| ステータス (GSI-PK-1) | シャード (GSI-PK-2) | order\$1date (SK) | order\$1id | customer\$1id | account\$1rep\$1id | order\$1items | total\$1amount | 
| --- | --- | --- | --- | --- | --- | --- | --- | 
| OPEN | 0 | 2025-12-20 | ord\$1002 | cust\$1001 | rep\$1001 | [\$1product\$1id: "prod\$1101", qty: 10\$1] | 150.00 | 
| OPEN | 1 | 2026-01-03 | ord\$1005 | cust\$1004 | rep\$1002 | [\$1product\$1id: "prod\$1100", qty: 20\$1] | 200.00 | 
| OPEN | 2 | 2026-01-05 | ord\$1003 | cust\$1002 | rep\$1001 | [\$1product\$1id: "prod\$1100", qty: 3\$1] | 175.00 | 


**OrderByAccountRep GSI - アカウント担当者の注文クエリのサポート**  

| account\$1rep\$1id (GSI-PK) | order\$1date (GSI-SK) | order\$1id | customer\$1id | ステータス | total\$1amount | 
| --- | --- | --- | --- | --- | --- | 
| rep\$1001 | 2025-11-15 | ord\$1001 | cust\$1001 | クローズ | 225.00 | 
| rep\$1001 | 2025-12-20 | ord\$1002 | cust\$1001 | OPEN | 150.00 | 
| rep\$1001 | 2026-01-05 | ord\$1003 | cust\$1002 | OPEN | 175.00 | 
| rep\$1002 | 2025-10-10 | ord\$1004 | cust\$1003 | クローズ | 250.00 | 
| rep\$1002 | 2026-01-03 | ord\$1005 | cust\$1004 | OPEN | 200.00 | 


**ProductInOrders GSI - 製品注文クエリのサポート**  

| product\$1id (GSI-PK) | order\$1id (GSI-SK) | customer\$1id | order\$1date | quantity | 
| --- | --- | --- | --- | --- | 
| prod\$1100 | ord\$1001 | cust\$1001 | 2025-11-15 | 5 | 
| prod\$1100 | ord\$1003 | cust\$1002 | 2026-01-05 | 3 | 
| prod\$1101 | ord\$1002 | cust\$1001 | 2025-12-20 | 10 | 

## 製品テーブルの設計
<a name="product-table-design"></a>

製品テーブルは、項目コレクションパターンを使用して、製品メタデータと在庫データの両方を同じパーティション内に保存します。この設計では、製品と在庫間の識別関係を活用します。親製品がないと在庫は存在できません。製品メタデータに SK=product\$1id を、在庫項目に SK=warehouse\$1id を使用し、PK=product\$1id を設定すると、個別の在庫テーブルと GSI が不要になり、コストが約 50% 削減されます。

このパターンにより、個々の倉庫在庫 (複合キーを持つ GetItem) と製品のすべての倉庫在庫 (パーティションキーに対するクエリ) の両方に対する効率的なクエリが可能になります。製品メタデータ項目の total\$1inventory 属性は、合計在庫数をすばやく検索するための非正規化集計を提供します。


**製品テーブル - ベーステーブル構造 (項目コレクションパターン)**  

| product\$1id (PK) | Warehouse\$1id (SK) | product\$1name | category | unit\$1price | inventory\$1quantity | total\$1inventory | 
| --- | --- | --- | --- | --- | --- | --- | 
| prod\$1100 | prod\$1100 | ウィジェット A |  ハードウェア | 25.00 |  | 500 | 
| prod\$1100 | wh\$1sea |  |  |  | 200 |  | 
| prod\$1100 | wh\$1pdx |  |  |  | 150 |  | 
| prod\$1100 | wh\$1atl |  |  |  | 150 |  | 
| prod\$1101 | prod\$1101 | ガジェット B | 電子機器 | 50.00 |  | 300 | 
| prod\$1101 | wh\$1sea |  |  |  | 100 |  | 
| prod\$1101 | wh\$1pdx |  |  |  | 200 |  | 

各テーブルは、必要なアクセスパターンを効率的にサポートするように、特定のグローバルセカンダリインデックス (GSI) で設計されています。この設計では、戦略的な非正規化とスパースインデックス作成を備えた集約指向の原則を使用して、パフォーマンスとコストの両方を最適化します。

主要な設計の最適化には以下が含まれます。
+ *スパース GSI* - OpenOrdersByDate は OPEN の注文 (全体の 20%) のみをインデックス化するため、GSI ストレージコストを削減できます。
+ *項目コレクションパターン* - 製品テーブルは PK=product\$1id、SK=warehouse\$1id を使用して在庫を保存し、個別の在庫テーブルを排除します。
+ *Order \$1 OrderItems 集約* - アクセス相関が 100% であるため、単一の項目として埋め込まれます
+ *戦略的な非正規化* - 効率的なクエリのために注文テーブルで account\$1rep\$1id を重複させる

最後に、前に定義したアクセスパターンを再度使用することができます。次のテーブルは、戦略的 GSIs を備えたマルチテーブル設計を使用して、各アクセスパターンを効率的にサポートする方法を示しています。各パターンは、直接キー検索または単一の GSI クエリのいずれかを使用するため、コストのかかるスキャンを回避し、あらゆる規模で一貫したパフォーマンスを提供します。


| S 番号 | アクセスパターン | クエリの状態 | 
| --- | --- | --- | 
|  1  |  従業員 ID で従業員の詳細を検索する  |  従業員テーブル: GetItem(employee\$1id="emp\$1001")  | 
|  2  |  従業員名で従業員の詳細をクエリする  |  EmployeeByName GSI: Query(name="John Smith")  | 
|  3  |  従業員の電話番号を検索する (複数可)  |  従業員テーブル: GetItem(employee\$1id="emp\$1001")  | 
|  4  |  顧客の電話番号を検索する (複数可)  |  顧客テーブル: GetItem(customer\$1id="cust\$1001")  | 
|  5  |  日付範囲内の顧客の注文を取得する  |  OrderByCustomerDate GSI: Query(customer\$1id="cust\$1001", order\$1date BETWEEN "2025-01-01" AND "2025-12-31")  | 
|  6  |  日付範囲内のすべての OPEN の注文を表示する  |  OpenOrdersByDate GSI: マルチ属性 PK (status="OPEN \$1 shard=0-4) を使用して 5 つのシャードを並列にクエリし、SK=order\$1date BETWEEN "2025-01-01" AND "2025-12-31"、結果をマージします  | 
|  7  |  最近雇用したすべての従業員を確認する  |  EmployeeByHireDate GSI: Query(entity\$1type="EMPLOYEE", hire\$1date >= "2025-01-01")  | 
|  8  |  倉庫のすべての従業員を検索する  |  EmployeeByWarehouse GSI: Query(warehouse\$1id="wh\$1sea")  | 
|  9  |  製品の注文中のすべての項目を取得する  |  ProductInOrders GSI: Query(product\$1id="prod\$1100")  | 
|  10  |  すべての倉庫の製品在庫を取得する  |  製品テーブル: Query(product\$1id="prod\$1100")  | 
|  11  |  アカウント担当者別に顧客を取得する  |  CustomerByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  12  |  アカウント担当者別に注文を取得する  |  OrderByAccountRep GSI: Query(account\$1rep\$1id="rep\$1001")  | 
|  13  |  役職を持つ従業員を取得する  |  EmployeeByJobTitle GSI: Query(job\$1title="Manager")  | 
|  14  |  商品および倉庫別に在庫を取得する  |  製品テーブル: GetItem(product\$1id="prod\$1100", warehouse\$1id="wh\$1sea")  | 
|  15  |  商品在庫総数を取得する  |  製品テーブル: GetItem(product\$1id="prod\$1100", warehouse\$1id="prod\$1100")  | 