MemoryStoreService es un servicio de datos de alta capacidad y baja latencia que proporciona un almacenamiento de datos en memoria accesible desde todos los servidores en una sesión activa. Los Almacenes de Memoria son adecuados para datos frecuentes y efímeros que cambian rápidamente y no necesitan ser duraderos, ya que son más rápidos de acceder y desaparecen al alcanzar la vida máxima. Para los datos que necesitan persistir entre sesiones, utiliza almacenes de datos.
Estructuras de datos
En lugar de acceder directamente a los 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 la información del usuario, como el nivel de habilidad, en una cola compartida entre servidores y utiliza servidores de sala para realizar emparejamientos periódicamente.
- Comercio y subasta 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.
- Clasificaciones globales - Almacena y actualiza las clasificaciones de usuarios en una clasificación compartida dentro de un mapa ordenado.
- Inventarios compartidos - Guarda los artículos de inventario y las estadísticas en un mapa hash compartido, donde los usuarios pueden utilizar los artículos del 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 del almacén de memoria que puede actuar como una caché y mejorar el rendimiento de tu juego.
En general, si necesitas acceder a los datos basándote en una clave específica, utiliza un mapa hash. Si necesitas que esos datos estén ordenados, utiliza un mapa ordenado. Si necesitas procesar tus datos en un orden específico, utiliza 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, las 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 se desalojan 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 subsecuentes fallan hasta que los elementos expiren o los elimines 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; más bien, cambia con el tiempo dependiendo del número de usuarios en el juego según la fórmula 64KB + 1.2KB * [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 vuelva a evaluar a un valor más bajo.
Después de que tu juego alcance 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 tablero 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 en función del 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 en función del 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 excedes la cuota, recibirás una respuesta de error cuando el servicio limite tus solicitudes.
Con la funcionalidad 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 conteo 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
Consulta los límites por partició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 los elementos procesados. Limpiar constantemente los elementos leídos usando el método MemoryStoreQueue:RemoveAsync() para colas y MemoryStoreSortedMap:RemoveAsync() para mapas ordenados puede liberar memoria y mantener la estructura de datos actualizada.
Configura 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 tu juego por completo.
- Siempre elimina explícitamente los elementos no necesarios o establece una corta expiración de elementos.
- En general, deberías usar eliminación explícita para liberar memoria y 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 los 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 varias más pequeñas mediante sharding.
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 tasa. Por ejemplo, si tienes un mapa ordenado que usa 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 basados en los últimos dígitos de sus ID de usuario.
Shard claves de acceso frecuentes 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 Solicitud al integrarte en Servicios Extendidos
Observabilidad
El Tablero de Observabilidad proporciona información y análisis para monitorear y solucionar problemas relacionados con el uso de tu almacén 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 actuales asignadas, monitorizar 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 Tablero de Observabilidad. Para 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 refiere 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 (100MB). |
| DataUpdateConflict | Conflicto debido a una 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 conteo 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 unidades de solicitud a nivel de estructura de datos (100,000 unidades de solicitud por minuto). |
| PartitionRequestsOverLimit | Excede el límite de unidades de solicitud por partición. |
| TotalRequestsOverLimit | Excede el límite de unidades 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 (32KB). |
La siguiente tabla enumera los códigos de estado del lado del cliente, que actualmente no están disponibles en el Tablero 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álidos. |
| TransformCallbackFailed | Falló al invocar la función de callback de transformación. |
| RequestThrottled | Las solicitudes recientes a MemoryStores alcanzaron uno o más límites. |
| UpdateConflict | Excedió el máximo número 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 |
|
| 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, permitiéndote 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 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 podrías 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 información sobre cómo depurar un almacén de memoria en juegos en vivo o al probar en Studio, utiliza Consola de Desarrollador.