MemoryStoreService é um serviço de dados de alta capacidade e baixa latência que fornece armazenamento rápido de dados em memória acessível de todos os servidores em uma sessão ao vivo. Armazenamentos em Memória são adequados para dados frequentes e efêmeros que mudam rapidamente e não precisam ser duráveis, pois são mais rápidos de acessar e desaparecem ao atingir a vida útil máxima. Para dados que precisam persistir entre sessões, use armazenamentos de dados.
Estruturas de dados
Em vez de acessar dados brutos diretamente, os armazenamentos em memória têm três estruturas de dados primitivas compartilhadas entre servidores para processamento rápido: mapa ordenado, fila e mapa hash. Cada estrutura de dados é adequada para certos casos de uso:
- Matchmaking baseado em habilidade - Salve informações do usuário, como nível de habilidade, em uma fila compartilhada entre servidores, e use servidores de lobby para executar o matchmaking periodicamente.
- Trocas e leilões entre servidores - Permita trocas universais entre diferentes servidores, onde os usuários podem dar lances em itens com preços que mudam em tempo real, com um mapa ordenado de pares chave-valor.
- Classificações globais - Armazene e atualize as classificações dos usuários em uma tabela de classificação compartilhada dentro de um mapa ordenado.
- Inventários compartilhados - Salve itens de inventário e estatísticas em um mapa hash compartilhado, onde os usuários podem utilizar itens de inventário simultaneamente entre si.
- Cache para Dados Persistentes - Sincronize e copie seus dados persistentes em um armazenamento de dados para um mapa hash de armazenamento em memória que pode atuar como um cache e melhorar o desempenho do seu jogo.
Em geral, se você precisa acessar dados com base em uma chave específica, use um mapa hash. Se você precisa que esses dados sejam ordenados, use um mapa ordenado. Se você precisa processar seus dados em uma ordem específica, use uma fila.
Limites e cotas
Para manter a escalabilidade e o desempenho do sistema, os armazenamentos em memória têm cotas de uso de dados para o tamanho da memória, solicitações de API e o tamanho da estrutura de dados.
Os armazenamentos em memória têm uma política de expulsão baseada no tempo de expiração, também conhecido como tempo de vida (TTL). Os itens são expulsos após expirarem, e a cota de memória é liberada para novas entradas. Quando você atinge o limite de memória, todas as solicitações de gravação subsequentes falham até que os itens expirem ou você os exclua manualmente.
Cota de tamanho de memória
A cota de memória limita a quantidade total de memória que um jogo pode consumir. Não é um valor fixo; em vez disso, muda ao longo do tempo dependendo do número de usuários no jogo de acordo com a fórmula 64 KB + 1,2 KB * [número de usuários]. A cota se aplica no nível do jogo em vez do nível do servidor.
Quando os usuários entram no jogo, a cota de memória adicional está disponível imediatamente. Quando os usuários saem do jogo, a cota não é reduzida imediatamente. Há um período de rastreamento de oito dias antes que a cota seja reavaliada para um valor mais baixo.
Depois que seu jogo atinge a cota de tamanho de memória, quaisquer solicitações de API que aumentem o tamanho da memória sempre falham. Solicitações que diminuem ou não alteram o tamanho da memória ainda têm sucesso.
Com o painel de observabilidade, você pode visualizar a cota de tamanho de memória do seu jogo em tempo real usando o gráfico de Uso de Memória.
Limites de solicitações de API
Uma cota de unidade de solicitação se aplica a todas as chamadas de API do MemoryStoreService. Essa cota é 1000 + 120 * [número de usuários simultâneos] unidades de solicitação por minuto.
A maioria das chamadas de API consome apenas uma unidade de solicitação, com algumas exceções:
MemoryStoreSortedMap:GetRangeAsync()
Consome unidades com base no número de itens retornados. Por exemplo, se este método retornar 10 itens, a chamada conta como 10 unidades de solicitação. Se retornar uma resposta vazia, conta como uma unidade de solicitação.
Consome unidades com base no número de itens retornados, assim como MemoryStoreSortedMap:GetRangeAsync(), mas consome uma unidade adicional a cada dois segundos enquanto lê. Especifique o tempo máximo de leitura com o parâmetro waitTimeout.
MemoryStoreHashMap:UpdateAsync()
Consome um mínimo de duas unidades.
MemoryStoreHashMap:ListItemsAsync()
Consome [número de partições escaneadas] + [itens retornados] unidades.
A cota de solicitações também é aplicada no nível do jogo em vez do nível do servidor. Isso fornece flexibilidade para alocar as solicitações entre servidores, desde que a taxa total de solicitações não exceda a cota. Se você exceder a cota, receberá uma resposta de erro quando o serviço limitar suas solicitações.
Com o recurso de observabilidade disponível, você pode visualizar a cota de unidade de solicitação do seu jogo em tempo real.
Limites de tamanho da estrutura de dados
Para um único mapa ordenado ou fila, os seguintes limites de tamanho e contagem de itens se aplicam:
- Número máximo de itens: 1.000.000
- Tamanho total máximo (incluindo chaves para mapa ordenado): 100 MB
Limites por partição
Veja limites por partição.
Melhores práticas
Para manter seu padrão de uso de memória otimizado e evitar atingir os limites, siga estas melhores práticas:
Remova itens processados. Limpar consistentemente itens lidos usando o método MemoryStoreQueue:RemoveAsync() para filas e MemoryStoreSortedMap:RemoveAsync() para mapas ordenados pode liberar memória e manter a estrutura de dados atualizada.
Defina o tempo de expiração para o menor intervalo de tempo possível ao adicionar dados. Embora o tempo de expiração padrão seja de 45 dias para ambos MemoryStoreQueue:AddAsync() e MemoryStoreSortedMap:SetAsync(), definir o menor tempo possível pode limpar automaticamente dados antigos para evitar que preencham sua cota de uso de memória.
- Não armazene uma grande quantidade de dados com uma longa expiração, pois isso pode exceder sua cota de memória e potencialmente causar problemas que podem quebrar seu jogo inteiro.
- Sempre exclua explicitamente itens desnecessários ou defina uma expiração curta para os itens.
- Geralmente, você deve usar a exclusão explícita para liberar memória e a expiração de itens como um mecanismo de segurança para evitar que itens não utilizados ocupem memória por um longo período de tempo.
Mantenha apenas valores necessários na memória.
Por exemplo, para um jogo de casa de leilão, você só precisa manter o maior lance. Você pode usar MemoryStoreSortedMap:UpdateAsync() em uma chave para manter o maior lance em vez de manter todos os lances em sua estrutura de dados.
Use retrocesso exponencial para ajudar a ficar abaixo dos limites de solicitações de API.
Por exemplo, se você receber um DataUpdateConflict, pode tentar novamente após dois segundos, depois quatro, oito, etc., em vez de enviar constantemente solicitações para MemoryStoreService para obter a resposta correta.
Divida grandes estruturas de dados em várias menores por meio de sharding.
É frequentemente mais fácil gerenciar dados em estruturas menores do que armazenar tudo em uma grande estrutura de dados. Essa abordagem também pode ajudar a evitar limites de uso e taxa. Por exemplo, se você tiver um mapa ordenado que usa prefixos para suas chaves, considere separar cada prefixo em seu próprio mapa ordenado. Para um jogo especialmente popular, você pode até separar usuários em vários mapas com base nos últimos dígitos de seus IDs de usuário.
Shard chaves acessadas frequentemente em mapas hash com várias cópias da chave para distribuir a carga.
Comprimir valores armazenados.
Por exemplo, considere usar o algoritmo LZW para reduzir o tamanho do valor armazenado.
Inscreva-se em Serviços Estendidos.
Você pode aumentar suas cotas de Limite de Armazenamento e Solicitações ao se inscrever em Serviços Estendidos.
Observabilidade
O Painel de Observabilidade fornece insights e análises para monitorar e solucionar problemas de uso do seu armazenamento em memória. Com gráficos que se atualizam em tempo real sobre diferentes aspectos do seu uso de memória e solicitações de API, você pode rastrear o padrão de uso de memória do seu jogo, visualizar as cotas alocadas atuais, monitorar o status da API e identificar problemas potenciais para otimização de desempenho.
A tabela a seguir lista e descreve todos os códigos de status das respostas da API disponíveis nos gráficos de Contagem de Solicitações por Status e Solicitações por API x Status do Painel de Observabilidade. Para mais informações sobre como resolver esses erros, veja Solução de Problemas. Para a cota ou limite específico ao qual um erro se relaciona, veja Limites e Cotizações.
| Código de status | Descrição |
|---|---|
| Sucesso | Sucesso. |
| DataStructureMemoryOverLimit | Excede o limite de tamanho de memória da estrutura de dados (100 MB). |
| DataUpdateConflict | Conflito devido a atualização concorrente. |
| AcessoNegado | Não autorizado a acessar os dados do jogo. Esta solicitação não consome unidades de solicitação nem usa cota. |
| ErroInterno | Erro interno. |
| SolicitaçãoInválida | A solicitação não possui as informações necessárias ou possui informações malformadas. |
| DataStructureItemsOverLimit | Excede o limite de contagem de itens da estrutura de dados (1M). |
| NenhumItemEncontrado | Nenhum item encontrado em MemoryStoreQueue:ReadAsync() ou MemoryStoreSortedMap:UpdateAsync(). ReadAsync() verifica a cada 2 segundos e retorna este código de status até encontrar itens na fila. |
| DataStructureRequestsOverLimit | Excede o limite de unidade de solicitação da estrutura de dados (100.000 unidades de solicitação por minuto). |
| PartitionRequestsOverLimit | Excede o limite de unidade de solicitação da partição. |
| TotalRequestsOverLimit | Excede o limite de unidade de solicitação em nível de universo. |
| TotalMemoryOverLimit | Excede a cota de memória em nível de universo. |
| ItemValueSizeTooLarge | O tamanho do valor excede o limite (32 KB). |
A tabela a seguir lista códigos de estado do lado do cliente, que atualmente não estão disponíveis no Painel de Observabilidade.
| Código de status | Descrição |
|---|---|
| ErroInterno | Erro Interno. |
| LocalNãoPublicada | Você deve publicar este local para usar o MemoryStoreService. |
| AcessoClienteInválido | MemoryStoreService deve ser chamado do servidor. |
| TempoDeExpiraçãoInválido | O campo 'expiração' deve estar entre 0 e 3.888.000. |
| SolicitaçãoInválida | Não foi possível converter o valor para json. |
| SolicitaçãoInválida | Não foi possível converter sortKey em um número ou string válido. |
| TransformCallbackFailed | Falha ao invocar a função de callback de transformação. |
| RequestThrottled | Solicitações recentes de MemoryStores atingiram um ou mais limites. |
| UpdateConflict | Excedeu o número máximo de tentativas. |
Solução de Problemas
A tabela a seguir lista e descreve a solução recomendada para cada código de status de resposta:
| Erro | Opções de solução de problemas |
|---|---|
| DataStructureRequestsOverLimit / PartitionRequestsOverLimit |
|
| TotalRequestsOverLimit | |
| DataStructureItemsOverLimit |
|
| DataStructureMemoryOverLimit | |
| TotalMemoryOverLimit | |
| DataUpdateConflict |
Exemplo de Abortando Solicitação |
| Erro Interno |
|
| SolicitaçãoInválida |
|
| ItemValueSizeTooLarge |
|
Testar e depurar no Estúdio
Os dados em MemoryStoreService são isolados entre o Estúdio e a produção, portanto, alterar os dados no Estúdio não afeta o comportamento da produção. Isso significa que suas chamadas de API do Estúdio não acessam dados de produção, permitindo que você teste com segurança os armazenamentos em memória e novos recursos antes de ir para a produção.
Os testes no Estúdio têm os mesmos limites e cotas que a produção. Para cotas calculadas com base no número de usuários, a cota resultante pode ser muito pequena, uma vez que você é o único usuário para testes no Estúdio. Ao testar a partir do Estúdio, você também pode notar uma latência ligeiramente maior e taxas de erro elevadas em comparação com o uso na produção devido a algumas verificações adicionais que são realizadas para verificar acesso e permissões.
Para informações sobre como depurar um armazenamento em memória em jogos ao vivo ou ao testar no estúdio, use o Console do Desenvolvedor.