Melhores práticas ao projetar estruturas de dados de armazenamento de memória

Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Dependendo do digitarde 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 restritas por um limite global de solicitações por partição.

Cada experiência do Roblox tem o Painel de Observabilidade da Loja de Memória , que inclui um conjunto de gráficos que você pode usar para monitorar o uso da loja de memória.

Mapas e Filas Ordenados

Mapas e filas classificados 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 solicitação para uma dessas estruturas de dados é uma solicitação para a mesma partição.

Quando um mapa ou filas classificados atingem seu limite de item ou memória, a melhor ação é remover itens desnecessários manualmente ou adicionando uma política de expiração para os itens. Alternativamente, se apenas o limite de memória estiver causando estrangulamento, você pode tentar reduzir o tamanho de seus itens removendo informações desnecessárias de suas chaves e valores.

Se você precisar de todos os seus itens ou está passando por estrangulamento devido a solicitações de throughput, a única solução é o sharding.

Fragmentação

O Sharding é 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 rendimento e substituí-la por vários menores que juntos contêm o mesmo conjunto de dados que o original.

O principal desafio do sharding é encontrar uma maneira de espalhar os dados em várias estruturas de dados de forma a manter a mesma funcionalidade que o original.

Fragmentando um Mapa Classificado

Para fragmentar um mapa classificado, considere dividir seus dados em subdivisões alfabéticas com intervalos de caracteres. Por exemplo, suponha que você só tenha chaves com a primeira letra de A a Z e acredite que quatro mapas classificados sejam 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 obter um item, use o mapa apropriado com base no personagem inicial do item.
Fragmentando um Mapa Classificado

-- Inicializar o Serviço de Armazenamento de Memória
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Crie seus próprios baldes de mapa classificados
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 de ajudante para obter o balde correto 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
-- Inicialize nomes de jogadores com o valor padrão de 0
for i,player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- Obter o valor de um jogador
local player = "myPlayer"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)

Partindo uma Fila

Fragmentar uma fila é mais complicado do que fragmentar um mapa classificado. Embora você queira espalhar o throughput da solicitação em 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 várias filas e rodando entre elas quando você adiciona ou lê um item:

  1. Crie várias filas e adicione-as a uma matriz / lista.

  2. Crie dois ponteiros locais. Um representa a fila na qual você deseja ler e remover itens. O outro representa a fila na qual você deseja adicionar itens:

    • Para operações de leitura, conte o número de itens necessários em cada fila, bem como para onde mover o ponteiro de leitura.
    • Para remover operações, passe os IDs da leitura para cada fila.
    • Para adicionar operações, adicione à fila no ponteiro adicionar e aumente o ponteiro.
Partindo uma Fila

-- Inicializar o Serviço de Armazenamento de 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 uma matriz
local queueArr = { q1, q2, q3, q4 }
-- Crie dois ponteiros representando os índices da leitura e adicione filas
local readIndex = 1
local addIndex = 1
-- Crie uma função local que atualize 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 vai ler um item extra
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- ler itens de cada fila
-- +1 itens se corresponder a critérios de leitura extras
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 remova n itens da fila
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- Crie uma função local que adicione 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 i,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 memória individual ou limite de contagem de itens e são automaticamente fragmentados, mas você ainda pode encontrar estrangulamento se os usar mal.

Por exemplo, considere uma experiência com um mapa hash de dados do jogo, armazenados como o valor de uma única chave chamada metadata . Se esses metadados contiverem um objeto aninhado com informações como ID do lugar, número de jogadores e muito mais, sempre que os metadados forem necessários, você não terá escolha a não ser chamar GetAsync("metadata") e obter o Objetointeiro. Nesse caso, todas as solicitações vão para uma única chave e, portanto, uma única partição.

Em vez de armazenar todos os metadados como um único Objetoaninhado, a melhor abordagem é armazenar cada campo como sua própria chave para que o mapa de hash possa aproveitar o sharding automático. Se você precisar separar os metadados do resto do mapa de hash, adicione um prefixo de nome (por exemplo, metadata_user_count em vez de apenas user_count ).