DynamoDB でのゲームプロファイルスキーマの設計
ゲームプロファイルのビジネスユースケース
このユースケースでは、DynamoDB を使用してゲームシステムのプレイヤープロファイルを保存する方法について説明します。ユーザー (この場合はプレイヤー) は、多くの最新のゲーム、特にオンラインゲームを操作する前に、プロファイルを作成する必要があります。ゲームプロファイルには通常、以下が含まれます。
-
ユーザー名などの基本情報
-
項目や装備などのゲームデータ
-
タスクやアクティビティなどのゲーム記録
-
友達リストなどのソーシャル情報
このアプリケーションのきめ細かなデータクエリアクセス要件を満たすため、プライマリキー (パーティションキーとソートキー) には汎用名 (PK と SK) を使用するため、以下に示すように、さまざまなタイプの値でオーバーロードできます。
このスキーマ設計のアクセスパターンは以下のとおりです。
-
ユーザーの友達リストを取得する。
-
プレイヤーのすべての情報を取得する。
-
ユーザーの項目リストを取得する。
-
ユーザーの項目リストから特定の項目を取得する。
-
ユーザーのキャラクターを更新する。
-
ユーザーの項目数を更新する。
ゲームプロファイルのサイズは、ゲームによって異なります。大きな属性値は圧縮することで、DynamoDB の項目制限内に収め、コストを削減できます。スループット管理戦略は、プレイヤー数、1 秒あたりにプレイされるゲーム数、ワークロードの季節性などのさまざまな要因によって異なります。新しくリリースされたゲームの場合、通常、プレイヤーの数や人気度は不明であるため、オンデマンドスループットモードから始めます。
ゲームプロファイルエンティティ関係図
次に示すのは、ゲームプロファイルのスキーマ設計に使用するエンティティ関係図 (ERD) です。
ゲームプロファイルのアクセスパターン
これらは、ソーシャルネットワークのスキーマ設計のために検討するアクセスパターンです。
-
getPlayerFriends
-
getPlayerAllProfile
-
getPlayerAllItems
-
getPlayerSpecificItem
-
updateCharacterAttributes
-
updateItemCount
ゲームプロファイルスキーマ設計の進化
上記の ERD から、これは 1 対多リレーションシップタイプのデータモデリングであることがわかります。DynamoDB では、1 対多のデータモデルを項目コレクションに整理できます。これは、複数のテーブルを作成して外部キーでリンクする従来のリレーショナルデータベースとは異なります。項目コレクションは、同じパーティションキー値を共有していてもソートキー値が異なる項目のグループです。項目コレクション内の各項目には、他の項目と区別する固有のソートキー値があります。これを念頭に置いて、エンティティタイプごとに HASH
値と RANGE
値のパターンを使用してみましょう。
まず、PK
や SK
などの汎用名を使用して、さまざまなタイプのエンティティを同じテーブルに保存し、モデルを将来にわたって使用できるようにします。読みやすくするために、データのタイプを示すプレフィックスを含めたり、Entity_type
または Type
という任意の属性を含めたりできます。現在の例では、player
で始まる文字列を PK
として player_ID
を保存し、SK
のプレフィックスとして entity name#
を使用します。さらに、このデータがどのエンティティタイプであるかを示す Type
属性を追加します。これにより、将来的にはより多くのエンティティタイプの保存をサポートできるようになり、GSI オーバーローディングやスパース GSI などの高度なテクノロジーを使用して、より多くのアクセスパターンに対応できるようになります。
アクセスパターンの実装を始めましょう。プレイヤーの追加や装備の追加などのアクセスパターンは PutItem
操作を通じて実現できるため、無視して構いません。このドキュメントでは、上記の一般的なアクセスパターンに焦点を当てます。
ステップ 1: アクセスパターン 1 (getPlayerFriends
) に対処する
このステップではアクセスパターン 1 (getPlayerFriends
) に対処します。現在の設計の場合、友達の関係はシンプルで、ゲーム内の友達の数は少なくなっています。わかりやすくするために、リストデータ型を使用して友達リストを保存します (1:1 モデリング)。この設計では、GetItem
を使用してこのアクセスパターンに対処します。GetItem
操作では、特定の項目を取得するためのパーティションキーとソートキーの値を明示的に指定します。
ただし、ゲームに多数の友達がいて、友達間の関係が複雑な場合 (招待コンポーネントと承諾コンポーネントの両方で友達の関係が双方向であるなど)、友達リストのサイズを無制限にスケールするには、多対多リレーションシップを使用して各友達を個別に保存する必要があります。また、友達関係の変更に伴って複数の項目を同時に操作する必要がある場合は、DynamoDB トランザクションを使用して複数のアクションをグループ化し、単一の全部かゼロかの TransactWriteItems
操作または TransactGetItems
操作として送信できます。
ステップ 2: アクセスパターン 2 (getPlayerAllProfile
),3 (getPlayerAllItems
)、4 (getPlayerSpecificItem
) に対処する
このステップを使用して、アクセスパターン 2 (getPlayerAllProfile
)、3 (getPlayerAllItems
)、4 (getPlayerSpecificItem
) に対処します。これら 3 つのアクセスパターンに共通しているのは、Query 操作を使用する範囲クエリです。クエリの範囲によっては、実際の開発でよく使用されるキー条件およびフィルター式を使用します。
Query 操作では、パーティションキーとして 1 つの値を指定し、このパーティションキー値を持つすべての項目を取得します。このようにしてアクセスパターン 2 (getPlayerAllProfile
) を実装します。オプションで、ソートキー条件式、つまりテーブルから読み取る項目を決定する文字列を追加できます。アクセスパターン 3 (getPlayerAllItems
) は、ソートキー begins_with ITEMS#
というキー条件を追加することで実装します。さらに、アプリケーション側の開発を簡略化するために、フィルター式を使用してアクセスパターン 4 (getPlayerSpecificItem
) を実装できます。
Weapon
カテゴリの項目をフィルタリングするフィルター式を使用した疑似コードの例を次に示します。
filterExpression: "ItemType = :itemType" expressionAttributeValues: {":itemType": "Weapon"}
注記
フィルター式は、クエリの完了後、結果がクライアントに返される前に適用されます。したがって、Query は、フィルター式の有無にかかわらず、同じ量の読み取りキャパシティを消費します。
アクセスパターンとして、大きなデータセットをクエリし、大量のデータをフィルタリングして一部のデータのみを保持する場合、適切なアプローチは DynamoDB パーティションキーとソートキーをより効果的に設計することです。例えば、前述の特定の ItemType
を取得する例で、各プレイヤーに多数の項目があり、特定の ItemType
をクエリするのが一般的なアクセスパターンである場合は、ItemType
を複合キーとして SK
に取り込む方が効率的です。データモデルは ITEMS#ItemType#ItemId
のようになります。
ステップ 3: アクセスパターン 5 (updateCharacterAttributes
) および 6 (updateItemCount
) に対処する
このステップを使用して、アクセスパターン 5 (updateCharacterAttributes
) と 6 (updateItemCount
) に対処します。プレイヤーが通貨を減らしたり、項目内の特定の武器の数量を変更したりするなど、キャラクターを変更する必要がある場合は、UpdateItem
を使用してこれらのアクセスパターンを実装します。プレイヤーの通貨を更新しながら最低額を下回らないようにするには、条件式 を追加して残高が最低額以上である場合にのみ残高を減らすことができます。擬似コードの例を次に示します。
UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount"
DynamoDB で開発し、アトミックカウンタを使用してインベントリを減らす場合、楽観的ロックを使用することで冪等性を確保できます。アトミックカウンタの擬似コードの例を次に示します。
UpdateExpression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '{":incr":{"N":"1"}}'
さらに、プレイヤーが通貨で項目を購入するシナリオでは、プロセス全体で通貨の差し引きと項目の追加を同時に行う必要があります。DynamoDB トランザクションを使用すると、複数のアクションをグループ化し、単一の全部かゼロかの TransactWriteItems
操作または TransactGetItems
操作として送信できます。TransactWriteItems
は、単一の全部かゼロかの操作で最大 100 の書き込みアクションをグループ化する、同期かつ冪等性の書き込み操作です。アクションはアトミックに完了するため、すべてが成功するか、どれも成功しません。トランザクションは、通貨の重複や消滅のリスクを排除するのに役立ちます。トランザクションの詳細については、「DynamoDB トランザクションの例」を参照してください。
すべてのアクセスパターンと各アクセスパターンにスキーマ設計で対処する方法を次の表にまとめています。
アクセスパターン | ベーステーブル/GSI/LSI | 操作 | パーティションキー値 | ソートキー値 | その他の条件/フィルター |
---|---|---|---|---|---|
getPlayerFriends | ベーステーブル | GetItem | PK=PlayerID | SK=“FRIENDS#playerID” | |
getPlayerAllProfile | ベーステーブル | Query | PK=PlayerID | ||
getPlayerAllItems | ベーステーブル | Query | PK=PlayerID | SK begins_with “ITEMS#” | |
getPlayerSpecificItem | ベーステーブル | Query | PK=PlayerID | SK begins_with “ITEMS#” | filterExpression: "ItemType = :itemType" expressionAttributeValues: { ":itemType": "Weapon" } |
updateCharacterAttributes | ベーステーブル | UpdateItem | PK=PlayerID | SK=“#METADATA#playerID” | UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount" |
updateItemCount | ベーステーブル | UpdateItem | PK=PlayerID | SK =“ITEMS#ItemID” | update-expression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '{":incr":{"N":"1"}}' |
ゲームプロファイルの最終スキーマ
これが最終的なスキーマ設計です。このスキーマ設計を JSON ファイルとしてダウンロードするには、GitHub の DynamoDB の例
ベーステーブル:
このスキーマ設計での NoSQL Workbench の使用
この最終スキーマを、DynamoDB のデータモデリング、データ視覚化、クエリ開発機能を提供するビジュアルツールである NoSQL Workbench にインポートして、新しいプロジェクトを詳しく調べたり編集したりできます。使用を開始するには、次の手順に従います。
-
NoSQL Workbench をダウンロードします。詳細については、「DynamoDB 用の NoSQL Workbench のダウンロード」を参照してください。
-
上記の JSON スキーマファイルをダウンロードします。このファイルは既に NoSQL Workbench モデル形式になっています。
-
JSON スキーマファイルを NoSQL Workbench にインポートします。詳細については、「既存のデータモデルのインポート」を参照してください。
-
NOSQL Workbench にインポートしたら、データモデルを編集できます。詳細については、「既存のデータモデルの編集」を参照してください。
-
データモデルの視覚化、サンプルデータの追加、CSV ファイルからのサンプルデータのインポートを行うには、NoSQL Workbench のデータビジュアライザー機能を使用します。