Use estas práticas para organizar dados temporários, distribuir carga e responder a problemas de armazenamento em memória.
Dependendo do tipo de estrutura de dados, MemoryStoreService impõe limites na memória e no número de itens em uma estrutura de dados. Todas as estruturas de dados também são limitadas por um limite global de requisições por partição.
Projete chaves e estruturas de dados
Veja Use padrões e prefixos de chave estáticos em Melhores práticas para armazenamentos de dados. Aplique esses padrões tanto nas chaves quanto nos nomes das estruturas de dados para que cada servidor direcione os mesmos dados lógicos para o mesmo local.
Escolha tempos de expiração que correspondam ao tempo que os dados permanecem úteis. Não use armazenamentos em memória para registros persistentes de jogadores ou dados que devem sobreviver à expiração. Para ajuda na escolha entre serviços, veja Armazenamentos de dados versus armazenamentos em memória.
Lide com falhas de requisição
Veja Tente novamente falhas transitórias em Melhores práticas para armazenamentos de dados.
Prefira UpdateAsync em vez de SetAsync
Veja Prefira UpdateAsync em vez de SetAsync em Melhores práticas para armazenamentos de dados. Mapas hash e mapas ordenados fornecem MemoryStoreHashMap:UpdateAsync() e MemoryStoreSortedMap:UpdateAsync() para esse padrão.
Espalhe requisições recorrentes
Veja Espalhe requisições recorrentes em Melhores práticas para armazenamentos de dados.
Monitore o uso
Use o Painel de Observabilidade do Armazenamento em Memória para monitorar o uso de cotas, volume de requisições e status de respostas. Revise os alertas por e-mail integrados, configure alertas personalizados para métricas importantes de armazenamento em memória e use o Relatório de Erros para investigar falhas.
Reduza requisições, tamanhos de itens e tempos de expiração antes de aumentar a capacidade. Se o uso legítimo exceder as cotas padrão, avalie Serviços Estendidos.
Gerencie limites de mapas ordenados e filas
Mapas ordenados e filas têm limites no número máximo de itens e na memória total máxima. Além disso, os itens em uma dessas estruturas de dados sempre residem em uma única partição. Cada requisição a uma dessas estruturas de dados é uma requisição à mesma partição.
Quando um mapa ordenado ou fila atinge seu limite de itens ou memória, remova itens desnecessários manualmente ou adicionando uma política de expiração. Se apenas o limite de memória estiver causando estrangulamento, reduza os tamanhos dos itens removendo informações desnecessárias das chaves e valores.
Se você precisar de todos os seus itens ou estiver enfrentando estrangulamento devido ao throughput de requisições, a única solução é a fragmentação.
Distribua carga com fragmentação
A fragmentação é o processo de armazenar um conjunto de dados relacionados em várias estruturas de dados. Em outras palavras, significa pegar uma estrutura de dados existente, de alto throughput, e substituí-la por várias menores que juntas contêm o mesmo conjunto de dados que a original.
O principal desafio da fragmentação é encontrar uma maneira de espalhar os dados por várias estruturas de dados de forma que mantenha a mesma funcionalidade que a original.
Embora o Roblox já particione mapas hash, você pode fragmentá-los ainda mais espalhando requisições entre várias chaves.
Fragmentando um mapa ordenado
Para fragmentar registros de jogadores em um mapa ordenado, use aritmética de módulo para atribuir cada Id a um de um número fixo de mapas. O exemplo a seguir usa quatro mapas e direciona consistentemente o mesmo usuário para o mesmo mapa:
-- Inicialize o Serviço de Armazenamento em Memória
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Crie seus baldes de Mapa Ordenado
local sm1 = MemoryStoreService:GetSortedMap("sm1")
local sm2 = MemoryStoreService:GetSortedMap("sm2")
local sm3 = MemoryStoreService:GetSortedMap("sm3")
local sm4 = MemoryStoreService:GetSortedMap("sm4")
local sortedMaps = { sm1, sm2, sm3, sm4 }
-- Função auxiliar para recuperar o balde correto a partir da Chave do Item
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- Inicialize jogadores com valor padrão de 0
for _, player in game:GetService("Players"):GetPlayers() do
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
bucket:SetAsync(tostring(userId), 0, 600)
end
-- Recupere o valor de um jogador
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)Fragmentando uma fila
Fragmentar uma fila é mais complicado do que fragmentar um mapa ordenado. Embora você queira espalhar o throughput de requisições entre várias filas, adições, leituras e remoções ocorrem apenas na frente ou atrás da fila.
Uma solução é usar uma fila rotativa, o que significa criar várias filas e alternar entre elas quando você adiciona ou lê um item:
- Crie várias filas e adicione-as a um array.
- Crie dois ponteiros locais. Um representa a fila da qual você deseja ler e remover itens. O outro representa a fila à qual você deseja adicionar itens:
- Para operações de leitura, calcule o número de itens que você precisa de cada fila, bem como onde mover o ponteiro de leitura.
- Para operações de remoção, passe os IDs da leitura para cada fila.
- Para operações de adição, adicione à fila no ponteiro de adição e incremente o ponteiro.
-- Inicialize o Serviço de Armazenamento em Memória
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Crie suas Filas
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Coloque as Filas em um Array
local queueArr = { q1, q2, q3, q4 }
-- Crie dois ponteiros representando os índices das filas de leitura e adição
local readIndex = 1
local addIndex = 1
-- Crie uma função local que atualiza os índices adequadamente
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Crie uma função local que lê n itens da fila
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- loop através de cada fila
for i = 1, 4, 1 do
-- determine se esta fila irá ler um item extra
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- leia itens de cada fila
-- +1 itens se corresponder aos critérios de leitura extra
if diff < endIndex then
items[i], ids[i] = queue:ReadAsync(countPerQueue + 1, allOrNothing, waitTimeout)
else
items[i], ids[i] = queue:ReadAsync(countPerQueue, allOrNothing, waitTimeout)
end
end
readIndex = rotateIndex(readIndex, count)
return items, ids
end
-- Crie uma função local que remove n itens da fila
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[i]
queue:RemoveAsync(ids[i])
end
end
-- Crie uma função local que adiciona um item à fila
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Escreva algum código!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player.User.Id, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)Mapas hash
Mapas hash não têm limites individuais de memória ou contagem de itens e são automaticamente fragmentados, mas você ainda pode encontrar estrangulamento se os usar de forma inadequada.
Por exemplo, considere um jogo com um mapa hash de dados, armazenado como o valor de uma única chave chamada metadata. Se essa metadata contiver um objeto aninhado com informações como ID do lugar, contagem de jogadores e mais, toda vez que a metadata for necessária, você não tem escolha a não ser chamar GetAsync("metadata") e recuperar todo o objeto. Nesse caso, todas as requisições vão para uma única chave e, portanto, uma única partição.
Em vez de armazenar toda a metadata como um único objeto aninhado, armazene cada campo acessado independentemente como sua própria chave para que o mapa hash possa aproveitar a fragmentação automática. Se você precisar de separação entre a metadata e o restante do mapa hash, adicione um prefixo de nome, como metadata_user_count em vez de user_count.
Se uma ou algumas chaves receberem requisições frequentes, fragmentar essas chamadas entre várias chaves. Por exemplo, se todos os servidores do jogo recuperarem um valor de uma chave de mapa hash, as requisições podem causar estrangulamento de partição. Para reduzir a carga, copie o valor para várias chaves e direcione cada servidor para uma fragmentação estável.