MemoryStoreService es un servicio de datos de alta capacidad y baja latencia que proporciona un almacenamiento de datos en memoria rápido y accesible desde todos los servidores en una sesión en vivo. Almacenes de Memoria son adecuados para datos frecuentes y efímeros que cambian rápidamente y no necesitan ser duraderos, porque son más rápidos de acceder y desaparecen al alcanzar la vida útil máxima. Para datos que necesitan persistir entre sesiones, utiliza almacenes de datos.
Estructuras de datos
En lugar de acceder directamente a datos en bruto, los almacenes de memoria tienen tres estructuras de datos primitivas compartidas entre servidores para un procesamiento rápido: mapa ordenado, cola y mapa hash. Cada estructura de datos es adecuada para ciertos casos de uso:
- Emparejamiento basado en habilidades - Guarda información del usuario, como el nivel de habilidad, en una cola compartida entre servidores, y utiliza servidores de vestíbulo para ejecutar el emparejamiento periódicamente.
- Comercio y subastas entre servidores - Permite el comercio universal entre diferentes servidores, donde los usuarios pueden pujar por artículos con precios que cambian en tiempo real, utilizando un mapa ordenado de pares clave-valor.
- Tableros de líderes globales - Almacena y actualiza las clasificaciones de los usuarios en un tablero de líderes compartido dentro de un mapa ordenado.
- Inventarios compartidos - Guarda artículos de inventario y estadísticas en un mapa hash compartido, donde los usuarios pueden utilizar artículos de inventario simultáneamente entre sí.
- Caché para datos persistentes - Sincroniza y copia tus datos persistentes en un almacén de datos a un mapa hash de almacén de memoria que puede actuar como una caché y mejorar el rendimiento de tu juego.
En general, si necesitas acceder a datos basados en una clave específica, usa un mapa hash. Si necesitas que esos datos estén ordenados, usa un mapa ordenado. Si necesitas procesar tus datos en un orden específico, usa una cola.
Límites y cuotas
Para mantener la escalabilidad y el rendimiento del sistema, los almacenes de memoria tienen cuotas de uso de datos para el tamaño de la memoria, solicitudes de API y el tamaño de la estructura de datos.
Los almacenes de memoria tienen una política de desalojo basada en el tiempo de expiración, también conocido como tiempo de vida (TTL). Los elementos son desalojados después de que expiran, y la cuota de memoria se libera para nuevas entradas. Cuando alcanzas el límite de memoria, todas las solicitudes de escritura subsiguientes fallan hasta que los elementos expiran o los eliminas manualmente.
Cuota de tamaño de memoria
La cuota de memoria limita la cantidad total de memoria que un juego puede consumir. No es un valor fijo; en cambio, cambia con el tiempo dependiendo del número de usuarios en el juego según la fórmula 64 KB + 1.2 KB * [número de usuarios]. La cuota se aplica a nivel de juego en lugar de a nivel de servidor.
Cuando los usuarios se unen al juego, la cuota de memoria adicional está disponible de inmediato. Cuando los usuarios abandonan el juego, la cuota no se reduce de inmediato. Hay un período de retroceso de ocho días antes de que la cuota se reevalúe a un valor más bajo.
Después de que tu juego alcanza la cuota de tamaño de memoria, cualquier solicitud de API que aumente el tamaño de la memoria siempre falla. Las solicitudes que disminuyen o no cambian el tamaño de la memoria aún tienen éxito.
Con el panel de observabilidad, puedes ver la cuota de tamaño de memoria de tu juego en tiempo real utilizando el gráfico de Uso de Memoria.
Límites de solicitudes de API
Una cuota de unidad de solicitud se aplica a todas las llamadas de API de MemoryStoreService. Esta cuota es 1000 + 120 * [número de usuarios concurrentes] unidades de solicitud por minuto.
La mayoría de las llamadas de API solo consumen una unidad de solicitud, con algunas excepciones:
MemoryStoreSortedMap:GetRangeAsync()
Consume unidades basadas en el número de elementos devueltos. Por ejemplo, si este método devuelve 10 elementos, la llamada cuenta como 10 unidades de solicitud. Si devuelve una respuesta vacía, cuenta como una unidad de solicitud.
Consume unidades basadas en el número de elementos devueltos, al igual que MemoryStoreSortedMap:GetRangeAsync(), pero consume una unidad adicional cada dos segundos mientras lee. Especifica el tiempo máximo de lectura con el parámetro waitTimeout.
MemoryStoreHashMap:UpdateAsync()
Consume un mínimo de dos unidades.
MemoryStoreHashMap:ListItemsAsync()
Consume [número de particiones escaneadas] + [elementos devueltos] unidades.
La cuota de solicitudes también se aplica a nivel de juego en lugar de a nivel de servidor. Esto proporciona flexibilidad para asignar las solicitudes entre servidores siempre que la tasa total de solicitudes no exceda la cuota. Si superas la cuota, recibes una respuesta de error cuando el servicio limita tus solicitudes.
Con la función de observabilidad disponible, puedes ver la cuota de unidad de solicitud de tu juego en tiempo real.
Límites de tamaño de estructura de datos
Para un solo mapa ordenado o cola, se aplican los siguientes límites de tamaño y recuento de elementos:
- Número máximo de elementos: 1,000,000
- Tamaño total máximo (incluyendo claves para el mapa ordenado): 100 MB
Límites por partición
Además de la cuota de unidad de solicitud a nivel de juego, los almacenes de memoria aplican límites de solicitud a cada partición como una salvaguarda que protege la estabilidad del servicio para todos los juegos. Estos límites no son una cuota que se asigna a tu juego, y no son un techo rígido sobre el rendimiento que tu juego puede lograr. Los siguientes valores son estimaciones. Están configurados en el backend de Roblox y pueden cambiar, así que no diseñes en función de ellos como números fijos.
Cada solicitud cuenta hacia el límite de la partición que contiene el elemento, sea cual sea la estructura de datos. Actualmente puedes esperar que la limitación comience aproximadamente a 30,000 unidades de solicitud por minuto para una sola partición, así que intenta mantenerte muy por debajo de esa tasa. Cuánto de ese límite consume una estructura de datos depende de cómo se distribuyen sus elementos:
- Mapas ordenados y colas residen cada uno en una sola partición, por lo que cada solicitud a una de estas estructuras de datos cuenta hacia el mismo límite de partición.
- Mapas hash distribuyen sus elementos a través de muchas particiones, por lo que el tráfico distribuido entre muchas claves de elementos rara vez se acerca al límite de una sola partición.
Los mapas hash tienen un límite adicional en cada clave de elemento de aproximadamente 5,000 unidades de solicitud de escritura por minuto y 15,000 unidades de solicitud de lectura por minuto. Este límite por clave se aplica además del límite por partición, por lo que una clave de acceso frecuente puede alcanzar cualquiera de los dos. El límite por clave se aplica solo a operaciones de clave única: el límite de lectura se aplica a MemoryStoreHashMap:GetAsync(), el límite de escritura se aplica a MemoryStoreHashMap:SetAsync() y MemoryStoreHashMap:RemoveAsync(), y MemoryStoreHashMap:UpdateAsync() cuenta contra ambos límites de lectura y escritura. MemoryStoreHashMap:ListItemsAsync() escanea particiones en lugar de una sola clave, por lo que solo se aplica el límite por partición.

Si no necesitas ordenación o funcionalidad de primero en entrar, primero en salir, un mapa hash es generalmente la mejor opción porque puede distribuir la carga entre particiones.
Cuando las solicitudes superan un límite de partición o por clave, el servicio las limita y devuelve un código de estado PartitionRequestsOverLimit, que puedes monitorear con el panel de observabilidad.
Si esperas que una estructura de datos o una clave de elemento reciba una alta tasa de solicitudes sostenida, fragmenta para que la carga se distribuya entre más particiones o claves. Distribuye lecturas y escrituras de manera uniforme entre múltiples claves de elementos para mantenerte dentro de los límites por clave. También puedes reducir la tasa de solicitudes almacenando en caché valores en el servidor y volviéndolos a verificar después de un intervalo, agrupando solicitudes cuando sea posible y aplicando retroceso exponencial cuando recibas respuestas de limitación.
Mejores prácticas
Para mantener tu patrón de uso de memoria óptimo y evitar alcanzar los límites, sigue estas mejores prácticas:
Elimina elementos procesados. Limpiar consistentemente los elementos leídos utilizando el método MemoryStoreQueue:RemoveAsync() para colas y MemoryStoreSortedMap:RemoveAsync() para mapas ordenados puede liberar memoria y mantener la estructura de datos actualizada.
Establece el tiempo de expiración al marco de tiempo más pequeño posible al agregar datos. Aunque el tiempo de expiración predeterminado es de 45 días tanto para MemoryStoreQueue:AddAsync() como para MemoryStoreSortedMap:SetAsync(), establecer el tiempo más corto posible puede limpiar automáticamente los datos antiguos para evitar que llenen tu cuota de uso de memoria.
- No almacenes una gran cantidad de datos con una larga expiración, ya que corre el riesgo de exceder tu cuota de memoria y potencialmente causar problemas que pueden romper todo tu juego.
- Siempre elimina explícitamente los elementos innecesarios o establece una corta expiración de elementos.
- En general, deberías usar la eliminación explícita para liberar memoria y la expiración de elementos como un mecanismo de seguridad para evitar que los elementos no utilizados ocupen memoria durante un período prolongado.
Solo mantén valores necesarios en memoria.
Por ejemplo, para un juego de casa de subastas, solo necesitas mantener la puja más alta. Puedes usar MemoryStoreSortedMap:UpdateAsync() en una clave para mantener la puja más alta en lugar de mantener todas las pujas en tu estructura de datos.
Usa retroceso exponencial para ayudar a mantenerte por debajo de los límites de solicitudes de API.
Por ejemplo, si recibes un DataUpdateConflict, podrías reintentar después de dos segundos, luego cuatro, ocho, etc. en lugar de enviar constantemente solicitudes a MemoryStoreService para obtener la respuesta correcta.
Divide estructuras de datos gigantes en múltiples más pequeñas mediante fragmentación.
A menudo es más fácil gestionar datos en estructuras más pequeñas en lugar de almacenar todo en una gran estructura de datos. Este enfoque también puede ayudar a evitar límites de uso y de tasa. Por ejemplo, si tienes un mapa ordenado que utiliza prefijos para sus claves, considera separar cada prefijo en su propio mapa ordenado. Para un juego especialmente popular, incluso podrías separar a los usuarios en múltiples mapas según los últimos dígitos de sus ID de usuario.
Fragmenta claves de acceso frecuente en mapas hash con múltiples copias de la clave para distribuir la carga.
Comprime los valores almacenados.
Por ejemplo, considera usar el algoritmo LZW para reducir el tamaño del valor almacenado.
Inscríbete en Servicios Extendidos.
Puedes aumentar tus cuotas de Límite de Almacenamiento y Solicitudes al unirte a Servicios Extendidos.
Observabilidad
El Panel de Observabilidad proporciona información y análisis para monitorear y solucionar problemas de tu uso de almacenes de memoria. Con gráficos que se actualizan en tiempo real sobre diferentes aspectos de tu uso de memoria y solicitudes de API, puedes rastrear el patrón de uso de memoria de tu juego, ver las cuotas asignadas actuales, monitorear el estado de la API e identificar problemas potenciales para la optimización del rendimiento.
La siguiente tabla enumera y describe todos los códigos de estado de las respuestas de API disponibles en los gráficos de Conteo de Solicitudes por Estado y Solicitudes por API x Estado del Panel de Observabilidad. Para obtener más información sobre cómo resolver estos errores, consulta Solución de problemas. Para la cuota o límite específico al que se relaciona un error, consulta Límites y Cuotas.
| Código de estado | Descripción |
|---|---|
| Éxito | Éxito. |
| DataStructureMemoryOverLimit | Excede el límite de tamaño de memoria a nivel de estructura de datos (100 MB). |
| DataUpdateConflict | Conflicto debido a actualización concurrente. |
| AccessDenied | No autorizado para acceder a los datos del juego. Esta solicitud no consume unidades de solicitud ni utiliza cuota. |
| InternalError | Error interno. |
| InvalidRequest | La solicitud no tiene la información requerida o tiene información mal formada. |
| DataStructureItemsOverLimit | Excede el límite de recuento de elementos a nivel de estructura de datos (1M). |
| NoItemFound | No se encontró ningún elemento en MemoryStoreQueue:ReadAsync() o MemoryStoreSortedMap:UpdateAsync(). ReadAsync() sondea cada 2 segundos y devuelve este código de estado hasta que encuentra elementos en la cola. |
| DataStructureRequestsOverLimit | Excede el límite de unidad de solicitud a nivel de estructura de datos (100,000 unidades de solicitud por minuto). |
| PartitionRequestsOverLimit | Excede un límite de unidad de solicitud por partición o por clave. |
| TotalRequestsOverLimit | Excede el límite de unidad de solicitud a nivel de universo. |
| TotalMemoryOverLimit | Excede la cuota de memoria a nivel de universo. |
| ItemValueSizeTooLarge | El tamaño del valor excede el límite (32 KB). |
La siguiente tabla enumera los códigos de estado desde el lado del cliente, que actualmente no están disponibles en el Panel de Observabilidad.
| Código de estado | Descripción |
|---|---|
| InternalError | Error interno. |
| UnpublishedPlace | Debes publicar este lugar para usar MemoryStoreService. |
| InvalidClientAccess | MemoryStoreService debe ser llamado desde el servidor. |
| InvalidExpirationTime | El campo 'expiration' debe estar entre 0 y 3,888,000. |
| InvalidRequest | No se puede convertir el valor a json. |
| InvalidRequest | No se puede convertir sortKey a un número o cadena válida. |
| TransformCallbackFailed | Error al invocar la función de devolución de llamada de transformación. |
| RequestThrottled | Las solicitudes recientes de MemoryStores alcanzaron uno o más límites. |
| UpdateConflict | Se superó el número máximo de reintentos. |
Solución de problemas
La siguiente tabla enumera y describe la solución recomendada para cada código de estado de respuesta:
| Error | Opciones de solución de problemas |
|---|---|
| DataStructureRequestsOverLimit / PartitionRequestsOverLimit |
|
| TotalRequestsOverLimit | |
| DataStructureItemsOverLimit |
|
| DataStructureMemoryOverLimit | |
| TotalMemoryOverLimit | |
| DataUpdateConflict |
Ejemplo de Abortar Solicitud |
| Error Interno |
|
| InvalidRequest |
|
| ItemValueSizeTooLarge |
|
Prueba y depuración en Studio
Los datos en MemoryStoreService están aislados entre Studio y producción, por lo que cambiar los datos en Studio no afecta el comportamiento de producción. Esto significa que tus llamadas de API desde Studio no acceden a datos de producción, lo que te permite probar de manera segura los almacenes de memoria y nuevas características antes de pasar a producción.
Las pruebas en Studio tienen los mismos límites y cuotas que la producción. Para las cuotas calculadas en función del número de usuarios, la cuota resultante puede ser muy pequeña ya que eres el único usuario para las pruebas en Studio. Al probar desde Studio, también puedes notar una latencia ligeramente más alta y tasas de error elevadas en comparación con el uso en producción debido a algunas verificaciones adicionales que se realizan para verificar el acceso y los permisos.
Para obtener información sobre cómo depurar un almacén de memoria en juegos en vivo o al probar en studio, utiliza la Consola de Desarrolladores.