Design de esquema de perfil de jogo no DynamoDB
Caso de uso de negócios de perfil de jogo
Este caso de uso aborda o uso do DynamoDB para armazenar perfis de jogadores em um sistema de jogos. Os usuários (neste caso, os jogadores) precisam criar perfis para poderem interagir com vários jogos modernos, especialmente os on-line. Geralmente, os perfis de jogos incluem:
-
Informações básicas, como nome de usuário
-
Dados do jogo, como itens e equipamentos
-
Registros do jogo, como tarefas e atividades
-
Informações sociais, como listas de amigos
Para atender aos requisitos detalhados de acesso à consulta de dados dessa aplicação, as chaves primárias (chave de partição e chave de classificação) usarão nomes genéricos (PK e SK) para que sejam sobrecarregadas com vários tipos de valor, como veremos abaixo.
Os padrões de acesso para este design de esquema são:
-
Obter a lista de amigos de um usuário
-
Obter todas as informações de um jogador
-
Obter a lista de itens de um usuário
-
Obter um item específico da lista de itens do usuário
-
Atualizar o personagem de um usuário
-
Atualizar a contagem de itens de um usuário
O tamanho do perfil de jogo variará de acordo com o jogo. A compactação de valores de atributos grandes possibilita que eles se ajustem aos limites do item no DynamoDB e reduzam os custos. Nesse caso, a estratégia de gerenciamento de throughput dependeria de vários fatores, como número de jogadores, número de jogos jogados por segundo e sazonalidade da workload. Normalmente, para um jogo recém-lançado, o número de jogadores e o nível de popularidade são desconhecidos, então começaremos com o modo de throughput sob demanda.
Diagrama de relacionamento de entidades de perfil de jogo
Este é o diagrama de relacionamento de entidades (ERD) que usaremos para o design do esquema do perfil de jogo.
![Diagrama de ER para um perfil de jogo, mostrando relações entre entidades, como User, Game e Score.](images/DataModeling/GamingProfileERD.png)
Padrões de acesso do perfil de jogo
Estes são os padrões de acesso que vamos considerar para o design do esquema da rede social.
-
getPlayerFriends
-
getPlayerAllProfile
-
getPlayerAllItems
-
getPlayerSpecificItem
-
updateCharacterAttributes
-
updateItemCount
Evolução do design do esquema do perfil de jogo
No ERD acima, podemos ver que esse é um tipo de modelagem de dados de relação de um para muitos. No DynamoDB, os modelos de dados de um para muitos podem ser organizados em coleções de itens, o que é diferente dos bancos de dados relacionais tradicionais, nos quais várias tabelas são criadas e vinculadas por meio de chaves externas. Coleção de itens é um grupo de itens que compartilham o mesmo valor de chave de partição, mas têm valores de chave de classificação diferentes. Em uma coleção, cada item tem um valor de chave de classificação exclusivo que o distingue dos demais. Com isso em mente, vamos usar o padrão a seguir para os valores HASH
e RANGE
referentes a cada tipo de entidade.
Primeiro, usamos nomes genéricos como PK
e SK
para armazenar diferentes tipos de entidade na mesma tabela e tornar o modelo preparado para o futuro. A fim de melhorar a legibilidade, podemos incluir prefixos para indicar o tipo de dados ou incluir um atributo arbitrário chamado Entity_type
ou Type
. No presente exemplo, usamos uma string que começa com player
para armazenar player_ID
como PK
, usamos entity name#
como prefixo de SK
e adicionamos um atributo Type
para indicar o tipo de entidade desse dado. Isso nos permite comportar o armazenamento de mais tipos de entidade no futuro e usar tecnologias avançadas, como sobrecarga de GSI e GSI esparso, para atender a mais padrões de acesso.
Vamos começar a implementar os padrões de acesso. Padrões de acesso como adicionar jogadores e equipamentos podem ser conseguidos por meio da operação PutItem
; portanto, podemos ignorá-los. Neste documento, vamos nos concentrar nos padrões de acesso típicos listados acima.
Etapa 1: abordar o padrão de acesso 1 (getPlayerFriends
)
Abordamos o padrão de acesso 1 (getPlayerFriends
) com esta etapa. No nosso design atual, a relação de amizade é simples e o número de amigos no jogo é pequeno. Para simplificar, usamos um tipo de dados de lista para armazenar listas de amigos (modelagem 1:1). Neste design, usamos GetItem
para satisfazer esse padrão de acesso. Na operação GetItem
, fornecemos explicitamente a chave de partição e o valor da chave de classificação para obter um item específico.
No entanto, se um jogo tiver um grande número de amigos e os relacionamentos entre eles forem complexos (como amizades bidirecionais com um componente de convite e aceitação), será necessário usar uma relação de muitos para muitos para armazenar cada amigo individualmente e escalar para um tamanho ilimitado de lista de amigos. Além disso, se a mudança de amizade envolver a operação de vários itens ao mesmo tempo, as transações do DynamoDB podem ser usadas para agrupar várias ações e enviá-las como uma única operação TransactWriteItems
ou TransactGetItems
do tipo “tudo ou nada”.
![Diagrama de relação de um para muitos (MITM) para um perfil de jogo da entidade Friends.](images/DataModeling/GamingProfile1.png)
Etapa 2: abordar os padrões de acesso 2 (getPlayerAllProfile
), 3 (getPlayerAllItems
) e 4 (getPlayerSpecificItem
)
Abordamos os padrões de acesso 2 (getPlayerAllProfile
), 3 (getPlayerAllItems
) e 4 (getPlayerSpecificItem
) com esta etapa. O que esses três padrões de acesso têm em comum é uma consulta de intervalo, que usa a operação Query. Dependendo do escopo da consulta, são usadas expressões de condição de chave e de filtro, que normalmente são utilizadas no desenvolvimento prático.
Na operação de consulta, fornecemos um único valor para a chave de partição e obtemos todos os itens com esse valor de chave de partição. O padrão de acesso 2 (getPlayerAllProfile
) é implementado dessa forma. Opcionalmente, podemos adicionar uma expressão de condição de chave de classificação, que é uma string que determina os itens a serem lidos da tabela. Para implementar o padrão de acesso 3 (getPlayerAllItems
), adicionamos a condição de chave de classificação begins_with ITEMS#
. Além disso, para simplificar o desenvolvimento do lado da aplicação, podemos usar expressões de filtro para implementar o padrão de acesso 4 (getPlayerSpecificItem
).
Veja aqui um exemplo de pseudocódigo usando uma expressão de filtro que filtra itens da categoria Weapon
:
filterExpression: "ItemType = :itemType" expressionAttributeValues: {":itemType": "Weapon"}
![Usando a operação de consulta com uma chave de partição e condições de chave de classificação para implementar diferentes padrões de acesso.](images/DataModeling/GamingProfile2.png)
nota
A expressão de filtro é aplicada depois que a operação é concluída, porém antes que os resultados sejam retornados. Portanto, a operação consome a mesma quantidade de capacidade de leitura, independentemente de haver uma expressão de filtro.
Se o padrão de acesso for consultar um grande conjunto de dados e filtrar uma grande quantidade de dados para manter apenas um pequeno subconjunto de dados, a abordagem apropriada será criar a chave de partição e a chave de classificação do DynamoDB de uma maneira mais eficiente. Por exemplo, no exemplo acima para obter um determinado ItemType
, se houvesse muitos itens para cada jogador e se consultar um determinado ItemType
fosse um padrão de acesso típico, seria mais eficiente inserir ItemType
em SK
como uma chave composta. O modelo de dados ficaria assim: ITEMS#ItemType#ItemId
.
Etapa 3: abordar os padrões de acesso 5 (updateCharacterAttributes
) e 6 (updateItemCount
)
Abordamos os padrões de acesso 5 (updateCharacterAttributes
) e 6 (updateItemCount
) com esta etapa. Quando o jogador precisar modificar o personagem, reduzindo a moeda ou modificando a quantidade de uma determinada arma em seus itens, por exemplo, use UpdateItem
para implementar esses padrões de acesso. Para atualizar a moeda de um jogador, mas garantir que ela nunca fique abaixo do valor mínimo, podemos adicionar uma Expressões de condição para reduzir o saldo somente se ele for maior ou igual ao valor mínimo. Veja aqui um exemplo de pseudocódigo:
UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount"
![Usar UpdateItem com uma expressão de condição para modificar a moeda de um jogador, garantindo que ela nunca seja menor que um valor definido.](images/DataModeling/GamingProfile4-Update-player-Currency.png)
Ao desenvolver com o DynamoDB e usar contadores atômicos para diminuir o inventário, podemos garantir a idempotência usando o bloqueio positivo. Veja aqui um exemplo de pseudocódigo para contadores atômicos:
UpdateExpression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '{":incr":{"N":"1"}}'
![Usando o contador atômico para diminuir o valor do atributo ItemCount de 5 para 4.](images/DataModeling/GamingProfile5-Update-Item-Count.png)
Além disso, em uma situação em que o jogador compra um item com moeda, todo o processo precisa deduzir a moeda e adicionar um item ao mesmo tempo. Podemos usar as transações do DynamoDB para agrupar várias ações e enviá-las como uma única operação tudo ou nada TransactWriteItems
ou TransactGetItems
. TransactWriteItems
é uma operação de gravação síncrona e idempotente que agrupa até 100 ações de gravação em uma única operação tudo ou nada. As ações são concluídas de forma atômica para que todas sejam bem-sucedidas ou nenhuma tenha êxito. As transações ajudam a eliminar o risco de duplicação ou desaparecimento da moeda. Para obter mais informações sobre transações, consulte Exemplo de transações do DynamoDB.
Todos os padrões de acesso e a forma como o design do esquema os aborda estão resumidos na tabela abaixo:
Padrão de acesso | Tabela base/GSI/LSI | Operation | Valor da chave de partição | Valores de chave de classificação | Outras condições/filtros |
---|---|---|---|---|---|
getPlayerFriends | Tabela base | GetItem | PK=PlayerID | SK=“FRIENDS#playerID” | |
getPlayerAllProfile | Tabela base | Consulta | PK=PlayerID | ||
getPlayerAllItems | Tabela base | Consulta | PK=PlayerID | SK begins_with “ITEMS#” | |
getPlayerSpecificItem | Tabela base | Consulta | PK=PlayerID | SK begins_with “ITEMS#” | filterExpression: "ItemType = :itemType" expressionAttributeValues: { ":itemType": "Weapon" } |
updateCharacterAttributes | Tabela base | UpdateItem | PK=PlayerID | SK=“#METADATA#playerID” | UpdateExpression: "SET currency = currency - :amount" ConditionExpression: "currency >= :minAmount" |
updateItemCount | Tabela base | UpdateItem | PK=PlayerID | SK =“ITEMS#ItemID” | update-expression: "SET ItemCount = ItemCount - :incr" expression-attribute-values: '{":incr":{"N":"1"}}' |
Esquema final do perfil de jogo
Veja aqui o design do esquema final. Para baixar esse design de esquema como arquivo JSON, consulte DynamoDB Examples
Tabela base:
![Design do esquema final de uma tabela que contém os resultados das implementações anteriores do padrão de acesso.](images/DataModeling/GamingProfile6-FinalSchema.png)
Como usar o NoSQL Workbench com esse design de esquema
Você pode importar esse esquema final para o NoSQL Workbench, uma ferramenta visual que fornece atributos de modelagem de dados, visualização de dados e desenvolvimento de consultas para o DynamoDB, se quiser explorar e editar ainda mais seu novo projeto. Para começar, siga estas etapas:
-
Baixe o NoSQL Workbench. Para ter mais informações, consulte Baixar o NoSQL Workbench para DynamoDB.
-
Baixe o arquivo do esquema JSON listado acima, que já está no formato do modelo NoSQL Workbench.
-
Importe o arquivo do esquema JSON para o NoSQL Workbench. Para ter mais informações, consulte Importar um modelo de dados existente.
-
Depois de importar para o NOSQL Workbench, você pode editar o modelo de dados. Para ter mais informações, consulte Editar um modelo de dados existente.
-
Para visualizar o modelo de dados, adicionar dados de exemplo ou importar dados de exemplo de um arquivo CSV, use o atributo Visualizador de dados do NoSQL Workbench.