Utiliza estas prácticas para organizar datos temporales, distribuir la carga y responder a problemas de almacén de memoria.
Dependiendo del tipo de estructura de datos, MemoryStoreService impone límites en la memoria y el número de elementos en una estructura de datos. Todas las estructuras de datos también están limitadas por un límite global de solicitudes por partición.
Diseñar claves y estructuras de datos
Consulta Usa patrones de clave estáticos y prefijos en Mejores prácticas para almacenes de datos. Aplica esos patrones tanto a las claves como a los nombres de las estructuras de datos para que cada servidor dirija los mismos datos lógicos al mismo lugar.
Elige tiempos de expiración que coincidan con cuánto tiempo los datos siguen siendo útiles. No uses almacenes de memoria para registros de jugadores persistentes o datos que deben sobrevivir a la expiración. Para obtener ayuda al elegir entre servicios, consulta Almacenes de datos versus almacenes de memoria.
Manejar fallos de solicitud
Consulta Reintentar fallos transitorios en Mejores prácticas para almacenes de datos.
Preferir UpdateAsync sobre SetAsync
Consulta Preferir UpdateAsync sobre SetAsync en Mejores prácticas para almacenes de datos. Los mapas hash y los mapas ordenados proporcionan MemoryStoreHashMap:UpdateAsync() y MemoryStoreSortedMap:UpdateAsync() para este patrón.
Escalonar solicitudes recurrentes
Consulta Escalonar solicitudes recurrentes en Mejores prácticas para almacenes de datos.
Monitorear uso
Utiliza el Tablero de Observabilidad del Almacén de Memoria para monitorear el uso de cuotas, el volumen de solicitudes y los estados de respuesta. Revisa las alertas por correo electrónico integradas, configura alertas personalizadas para métricas importantes del almacén de memoria y utiliza el Informe de Errores para investigar fallos.
Reduce solicitudes, tamaños de elementos y tiempos de expiración antes de aumentar la capacidad. Si el uso legítimo excede las cuotas predeterminadas, evalúa Servicios Extendidos.
Gestionar límites de mapas ordenados y colas
Los mapas ordenados y las colas tienen límites en el número máximo de elementos y la memoria total máxima. Además, los elementos en una de estas estructuras de datos siempre residen en una sola partición. Cada solicitud a una de esas estructuras de datos es una solicitud a la misma partición.
Cuando un mapa ordenado o una cola alcanza su límite de elementos o de memoria, elimina elementos innecesarios manualmente o agregando una política de expiración. Si solo el límite de memoria está causando limitaciones, reduce los tamaños de los elementos eliminando información innecesaria de las claves y valores.
Si necesitas todos tus elementos o estás experimentando limitaciones debido al rendimiento de las solicitudes, la única solución es el sharding.
Distribuir carga con sharding
El sharding es el proceso de almacenar un conjunto de datos relacionados en múltiples estructuras de datos. En otras palabras, significa tomar una estructura de datos existente de alto rendimiento y reemplazarla con varias más pequeñas que juntas contengan el mismo conjunto de datos que la original.
El desafío clave del sharding es encontrar una manera de distribuir los datos en múltiples estructuras de datos de manera que mantenga la misma funcionalidad que la original.
Aunque Roblox ya particiona mapas hash, puedes fragmentarlos aún más distribuyendo las solicitudes entre varias claves.
Sharding de un mapa ordenado
Para fragmentar registros de jugadores en un mapa ordenado, utiliza aritmética de módulo para asignar cada Id a uno de un número fijo de mapas. El siguiente ejemplo utiliza cuatro mapas y dirige consistentemente al mismo usuario al mismo mapa:
-- Inicializa el Servicio de Almacén de Memoria
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Crea tus cubos 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 }
-- Función auxiliar para recuperar el cubo correcto de la Clave del Elemento
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- Inicializa jugadores con valor predeterminado 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
-- Recupera el valor de un jugador
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)Sharding de una cola
El sharding de una cola es más complicado que el sharding de un mapa ordenado. Aunque deseas distribuir el rendimiento de las solicitudes entre múltiples colas, las adiciones, lecturas y eliminaciones solo ocurren al frente o atrás de la cola.
Una solución es usar una cola rotativa, lo que significa crear múltiples colas y rotar entre ellas cuando agregas o lees un elemento:
- Crea varias colas y agrégalas a un array.
- Crea dos punteros locales. Uno representa la cola de la que deseas leer y eliminar elementos. El otro representa la cola a la que deseas agregar elementos:
- Para operaciones de lectura, calcula el número de elementos que necesitas de cada cola, así como dónde mover el puntero de lectura.
- Para operaciones de eliminación, pasa los IDs de la lectura a cada cola.
- Para operaciones de adición, agrega a la cola en el puntero de adición e incrementa el puntero.
-- Inicializa el Servicio de Almacén de Memoria
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Crea tus Colas
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Coloca las Colas en un Array
local queueArr = { q1, q2, q3, q4 }
-- Crea dos punteros que representan los índices de las colas de lectura y adición
local readIndex = 1
local addIndex = 1
-- Crea una función local que actualiza los índices apropiadamente
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Crea una función local que lee n elementos de la cola
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- bucle a través de cada cola
for i = 1, 4, 1 do
-- determina si esta cola leerá un elemento extra
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- lee elementos de cada cola
-- +1 elementos si coincide con los criterios de lectura 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
-- Crea una función local que elimina n elementos de la cola
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[i]
queue:RemoveAsync(ids[i])
end
end
-- Crea una función local que agrega un elemento a la cola
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- ¡Escribe algo de 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
Los mapas hash no tienen límites individuales de memoria o conteo de elementos y están automáticamente fragmentados, pero aún puedes encontrar limitaciones si los usas mal.
Por ejemplo, considera un juego con un mapa hash de datos, almacenado como el valor de una única clave llamada metadata. Si esta metadata contiene un objeto anidado con información como el ID del lugar, el conteo de jugadores y más, cada vez que se necesita la metadata, no tienes más remedio que llamar a GetAsync("metadata") y recuperar todo el objeto. En este caso, todas las solicitudes van a una sola clave y, por lo tanto, a una sola partición.
En lugar de almacenar toda la metadata como un único objeto anidado, almacena cada campo de acceso independiente como su propia clave para que el mapa hash pueda aprovechar el sharding automático. Si necesitas separación entre la metadata y el resto del mapa hash, agrega un prefijo de nombre, como metadata_user_count en lugar de user_count.
Si una o pocas claves reciben solicitudes frecuentes, fragmenta esas llamadas entre múltiples claves. Por ejemplo, si todos los servidores del juego recuperan un valor de una clave de mapa hash, las solicitudes podrían causar limitaciones de partición. Para reducir la carga, copia el valor a múltiples claves y dirige cada servidor a un shard estable.