Dependendo do tipo de estrutura de dados, o 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 restringidas por um limite global de requisições por partição.
Cada jogo Roblox tem o Painel de Observabilidade do Memory Store, que inclui um conjunto de gráficos que você pode usar para monitorar o uso do memory store.
Mapas ordenados e filas
Mapas ordenados e filas têm limites sobre o número máximo de itens e a 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 para uma dessas estruturas de dados é uma requisição para a mesma partição.
Quando um mapa ordenado ou uma fila atinge seu limite de itens ou de memória, a melhor ação a tomar é remover itens desnecessários manualmente ou adicionando uma política de expiração para os itens. Alternativamente, se apenas o limite de memória está causando limitação, você pode tentar reduzir o tamanho de seus itens, removendo informações desnecessárias de suas chaves e valores.
Se você precisa de todos os seus itens ou está experienciando limitação devido à taxa de requisição, a única solução é fragmentação.
Fragmentação
Fragmentação é o processo de armazenar um conjunto de dados relacionados em múltiplas estruturas de dados. Em outras palavras, significa pegar uma estrutura de dados existente, de alto rendimento, e substituí-la por várias menores que juntas contenham o mesmo conjunto de dados que a original.
O principal desafio da fragmentação é encontrar uma maneira de espalhar os dados entre múltiplas estruturas de dados de forma que mantenha a mesma funcionalidade que a original.
Para mapas de hash, embora a estrutura de dados já esteja particionada, a fragmentação é feita espalhando as requisições entre várias chaves.
Fragmentando um mapa ordenado
Para fragmentar um mapa ordenado, considere dividir seus dados em subseções alfabéticas com intervalos de caracteres. Por exemplo, suponha que você tenha apenas chaves com a primeira letra de A-Z, e acredita que quatro mapas ordenados são suficientes para seu caso de uso atual e crescimento futuro:
- O primeiro mapa pode cobrir A-G, o segundo H-N, o terceiro O-T, e o quarto U-Z.
- Para inserir ou recuperar um item, use o mapa apropriado com base no caractere inicial do item.
Fragmentando um Mapa Ordenado
-- Inicializa o Serviço de MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Cria seus baldes de Mapa Ordenado
local sm_AtoG = MemoryStoreService:GetSortedMap("AtoG")
local sm_HtoM = MemoryStoreService:GetSortedMap("HtoM")
local sm_NtoT = MemoryStoreService:GetSortedMap("NtoT")
local sm_UtoZ = MemoryStoreService:GetSortedMap("UtoZ")
-- Função auxiliar para recuperar o balde correto a partir da Chave do Item
local function getSortedMapBucket(itemKey)
if (itemKey >= "a" and itemKey < "h") then
return sm_AtoG
elseif (itemKey < "n") then
return sm_HtoM
elseif (itemKey < "u") then
return sm_NtoT
else
return sm_UtoZ
end
end
-- Inicializa nomes de jogadores com valor padrão de 0
for _, player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- Recupera o valor de um jogador
local player = "meuJogador"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)
Fragmentando uma fila
Fragmentar uma fila é mais complicado do que fragmentar um mapa ordenado. Embora você queira espalhar a taxa de requisição entre várias filas, adições, leituras e remoções só ocorrem na frente ou atrás da fila.
Uma solução é usar uma fila rotativa, o que significa criar múltiplas 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ê quer ler e remover itens. O outro representa a fila na qual você quer adicionar itens:
- Para operações de leitura, calcule o número de itens que você precisa de cada fila, assim 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 na fila no ponteiro de adição e incremente o ponteiro.
Fragmentando uma Fila
-- Inicializa o Serviço de MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Cria suas Filas
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Coloca as Filas em um Array
local queueArr = { q1, q2, q3, q4 }
-- Cria dois ponteiros representando os índices das filas de leitura e adição
local readIndex = 1
local addIndex = 1
-- Cria uma função local que atualiza os índices adequadamente
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Cria 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 por cada fila
for i = 1, 4, 1 do
-- determina se essa fila irá ler um item extra
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- lê 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
-- Cria uma função local que remove n itens da fila
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- Cria uma função local que adiciona um item à fila
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[readIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Escreva algum código!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)
Mapas de hash
Mapas de hash não têm limites individuais de memória ou contagem de itens e são automaticamente fragmentados, mas você ainda pode encontrar limitações se usá-los de forma inadequada.
Por exemplo, considere um jogo com um mapa de 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, a uma única partição.
Em vez de armazenar toda a metadata como um único objeto aninhado, a melhor abordagem é armazenar cada campo como sua própria chave, para que o mapa de hash possa aproveitar a fragmentação automática. Se você precisar de separação entre a metadata e o resto do mapa de hash, adicione um prefixo de nomenclatura (por exemplo, metadata_user_count em vez de apenas user_count).
Além disso, se uma ou poucas chaves estão sendo acessadas frequentemente, é importante fragmentar essas chamadas entre muitas chaves. Por exemplo, se todos os servidores do jogo precisam recuperar um valor de uma chave de mapa de hash, você pode encontrar uma limitação de partição. Para evitar isso, você pode espalhar essas chamadas entre várias chaves com o mesmo valor até que a limitação de partição não ocorra mais.