Warcraft Rumble

Detrás de Warcraft Rumble: Cómo se calcula la experiencia de los minis

Blizzard Entertainment

¡Saludos, fans de Rumble!

Soy Andy Lim, el ingeniero principal encargado de las funciones del servidor de Warcraft Rumble. El equipo del servidor se encarga de todas las tareas que se pueden esperar de un equipo con ese nombre, como la creación de redes, la computación en la nube y el almacenamiento de datos, pero también diseñamos funciones para el juego, como el progreso de las campañas y las misiones. Hoy levantaré el telón azul y les contaré acerca del proceso de almacenamiento de experiencia y cómo se utiliza para calcular los niveles de cada mini.


Conozcan a Cassandra

¡Advertencia! Esta sección incluirá muchos detalles técnicos. Puede resultar algo denso, pero no se desanimen.

Empecemos conociendo un poco acerca del método de almacenamiento que utilizamos para hacer un seguimiento de los cambios frecuentes en los datos de nuestros jugadores para muchos usuarios simultáneos: Cassandra, una base de datos popular, de código abierto, altamente expansible y distribuible, que nos proporciona el equilibrio adecuado entre la consistencia de datos y la disponibilidad. En cuanto a los datos en sí, Cassandra puede procesar conjuntos de datos extensos sin forzar un esquema complejo. Creamos herramientas para que los ingenieros definan nuestras tablas de bases de datos y los esquemas de las tablas según sea necesario para cada función, lo que nos permite ser flexibles al momento de estructurar y organizar distintos elementos. Fácilmente podemos escribir y validar nuestros esquemas y consultas.

¿Qué es un esquema? Un esquema de base de datos define cómo se organizan los datos dentro de una base de datos relacional, como puede verse en las tablas.


Almacenar experiencia como un libro de contabilidad

Cassandra es muy veloz para escribir datos, pero su lectura es más lenta. Por lo general, la actualización de datos existentes es una operación de lectura y luego de escritura que puede volverse muy lenta. Para solucionarlo, diseñamos la base de datos de los jugadores para que puedan almacenarse como si fuera un libro de contabilidad. En ese tipo de registros, cada línea se escribe como un cambio, y cuando se lee, todas las entradas se leen juntas y luego se hacen algunos cálculos basados en ellas.

Un buen ejemplo de esos registros es una tarjeta de crédito. Cada transacción se escribe como una sola entrada que puede ser positiva o negativa. Cada vez que se utiliza, se realiza una transacción negativa, y cada vez que la tarjeta se paga, se realiza una transacción positiva. ¿Cómo se puede saber cuánto se debe? Para eso, es necesario ver todo el registro y sumar y restar cada valor.

Los datos existentes son como un solo elemento que se puede almacenar. Cuando quieres cambiar ese valor, tienes que leerlo, hacer el cambio y luego escribir ese nuevo valor. Entonces, si fuera un marcador de fútbol, cada vez que un equipo hace un gol se debe hacer una lectura para ver cuál fue el último valor total, sumar el nuevo gol y luego se debe escribir en el marcador.

Un ejemplo concreto relacionado con Arclight es la experiencia registrada para cada mini. Después de cada misión, se agrega una fila para un solo mini al registro, que indica la cantidad de experiencia que obtuvo. Después de cinco misiones, podrían aparecer entradas como las que verás a continuación:

Tabla 1

Jugador

Mini

Valor

Tiempo

Andy

Bruto Gnoll

3

Lunes, 2:00 p. m.

Andy

Jinete de Grifos

3

Lunes, 2:05 p. m.

Andy

Bruto Gnoll

3

Lunes, 2:10 p. m.

Andy

Piloto de S.E.G.U.R.O.

3

Lunes, 2:15 p. m.

Andy

Bruto Gnoll

3

Lunes, 2:20 p. m.

Por último, para calcular el total de experiencia de los minis, debemos recuperar esas entradas, agruparlas por mini y agregar los valores de experiencia. El resultado se vería de la siguiente manera:

Tabla 2

Jugador

Mini

Total

Andy

Bruto Gnoll

9

Andy

Jinete de Grifos

3

Andy

Piloto de S.E.G.U.R.O.

3


La preparación del proceso de consolidación de datos

Ahora que hemos establecido cómo almacenamos la experiencia, veamos cómo podemos mejorar los cálculos que se necesitan para cada mini. Utilizar filas individuales para el almacenamiento de la experiencia obtenida de cada mini sería demasiado intenso para una lectura. Tendríamos demasiadas filas, y esta tabla no dejaría de crecer... NUNCA. Supongamos que un día normal en Rumble consiste en la combinación de 15 misiones o partidas JcJ, junto con 20 sobrecargas a la semana para conseguir oro y una ronda semanal de calabozos para mejorar tu ejército. Eso daría un total de 100 entradas por semana. Después de 3 meses de juego, aparecerían cerca de 1260 entradas en el registro. Para calcular los totales, tendríamos que recuperarlos utilizando el sistema lento de lectura de Cassandra. Uff.

Nosotros diseñamos una solución: la consolidación de datos. Ese proceso calcula los valores hasta un momento determinado. En este caso, los agregamos todos juntos y los representamos en una sola fila. Todo esto lo guardamos en otra tabla. Aquí es donde puede verse la utilidad de las marcas de tiempo. En el caso de la experiencia, la clave será el jugador y el mini, y el cálculo es una suma total. Hagamos una consolidación de datos con todas las entradas en el registro hasta el martes a las 12 a. m. y almacenemos los resultados en otra tabla de Cassandra. Las entradas podrían verse así:

Tabla 3

Jugador

Mini

Total

Fecha de finalización

Andy

Bruto Gnoll

9

Martes, 12 a. m.

Andy

Jinete de Grifos

3

Martes, 12 a. m.

Andy

Piloto de S.E.G.U.R.O.

3

Martes, 12 a. m.

Juegas más partidas el miércoles y generas más entradas de experiencia. La tabla original de experiencia debería haber crecido.

Tabla 4

Jugador

Mini

Valor

Tiempo

Andy

Bruto Gnoll

3

Lunes, 2:00 p. m.

Andy

Jinete de Grifos

3

Lunes, 2:05 p. m.

Andy

Bruto Gnoll

3

Lunes, 2:10 p. m.

Andy

Piloto de S.E.G.U.R.O.

3

Lunes, 2:15 p. m.

Andy

Bruto Gnoll

3

Lunes, 2:20 p. m.

Andy

Piloto de S.E.G.U.R.O.

3

Miércoles, 12 p. m.

Andy

Cadena de Relámpagos

3

Miércoles, 12:05 p. m.

Andy

Jinete de Grifos

3

Miércoles, 12:10 p. m.

Ahora tenemos que hacer una búsqueda completa y volver a calcular los niveles actuales de los minis después de jugar estas partidas. Consultamos ambas tablas, la tabla con los datos consolidados para la entrada individual y el registro de las entradas después de ese momento determinado, y luego sumamos los valores para obtener una tabla final con el total de experiencia. Entonces, leemos la tabla 3 y consultamos la tabla 4 solo para los datos que existen después de cada fila de la tabla 3. Esta es una lista de los pasos que seguiríamos:

  1. Leer una fila de la tabla 3
     

Jinete de Grifos

3

Martes, 12 a. m.

  1. Leer filas de la tabla 3 del Jinete de Grifos después del martes a las 12 a. m.
     

Jinete de Grifos

3

Miércoles, 12:10 p. m.

  1. Ahora sumamos ambos conjuntos de datos para generar el total:

Jinete de Grifos

6

Como verán, este método corrompería varias lecturas de la tabla 4.

Algunos podrían pensar que deberíamos almacenar los nuevos datos calculados después de almacenar una nueva entrada en el registro. Es una buena idea en teoría, pero se produciría una inconsistencia en los datos y una degradación de rendimiento. Un ejemplo de una posible degradación de rendimiento es que el sistema estaría ocupado recalculando y almacenando datos por tener la necesidad de hacer una lectura y luego una escritura. Tenemos que equilibrar el proceso de cálculo mientras hacemos que el juego se pueda ejecutar con un nivel de rendimiento adecuado para todos los jugadores.

Un ejemplo de la inconsistencia de datos son los cálculos realizados al final de las partidas. La infraestructura y la plataforma de nuestro servidor admiten mensajes que llegan tarde. Por ejemplo, cuando se produce un problema transitorio entre dos centros de datos (A y B), y el mensaje que le otorga experiencia al mini de un jugador se envía desde el centro A, pero no llega al centro B a tiempo. Imaginemos esta situación: juegas una partida justo antes de la medianoche. Juegas y ganas un miércoles a las 11:59 p. m. El procesador encargado de la consolidación de datos se configuró para que funcione todos los días con todos los datos hasta el día actual. Luego, a la medianoche, empezaría a calcular el total de tus valores de experiencia hasta el jueves a las 12 a. m. Esos datos se almacenarían en la segunda tabla, y las nuevas lecturas de la experiencia buscarían el último valor almacenado y sumaría todos los valores después del jueves a las 12 a. m. Cuando un mensaje llega tarde, lo recibimos, pero notamos que la partida se completó el miércoles a las 11:59 p. m., y la entrada de la experiencia se escribe con ese horario. La tabla de datos consolidados no hubiera incluido esa entrada, y la consulta de los datos posteriores tampoco la hubiera mostrado. Esos datos incompletos serían un problema. ¿Qué sucede con los mensajes que llegan un par de días después? Bueno, tenemos sistemas de monitoreo y alerta que casi siempre garantizan que esa nueva información se procese a tiempo para la próxima consolidación de datos.


Calcular los niveles de los minis

Si observamos la tabla de experiencia total, notaremos que no incluye ningún nivel calculado, y solo tenemos el total de la cantidad de experiencia obtenida. Por otro lado, tenemos una tabla invariable que los diseñadores del juego incorporaron y que funciona de la siguiente manera: un mini empieza en el nivel 1, y cuando obtiene 1 punto de experiencia, alcanza el nivel 2, y luego necesita ganar 3 puntos para llegar al nivel 3.

 Nivel

Cantidad de puntos para subir de nivel

1

1

2

3

3

6

4

10

5

20

10

250

La combinación de ambos nos permite calcular en tiempo de ejecución el nivel real de un mini haciendo una suma total, lo que nos da como resultado este conjunto final de datos:

Mini

Nivel

Exp

Puntos para subir de nivel

Bruto Gnoll

3

5

1

Jinete de Grifos

3

2

4

Piloto de S.E.G.U.R.O.

3

2

4

Cadena de Relámpagos

2

2

1

Los diseñadores del juego tienen la flexibilidad de cambiar la cantidad de experiencia necesaria para ganar un nivel. Ellos pueden concluir que subir de nivel en los niveles más bajos es muy difícil y reducir la cantidad necesaria para alcanzar el siguiente nivel. Eso significa que, sin hacer cambios en los datos de los jugadores guardados en la base de datos, los minis obtendrían una pequeña bonificación y necesitarían menos experiencia para subir de nivel. Esto también ocurre al revés. Si los diseñadores del juego deciden que los jugadores suben de nivel demasiado rápido y aumentan esos valores, no otorgamos experiencia para restaurar a los minis a los niveles que tenían antes del cambio. Eso no quiere decir que no podemos darles la opción a los diseñadores de otorgar algo de experiencia como forma de compensación.


Métodos alternativos considerados

Antes de utilizar nuestra solución actual, analizamos distintos métodos para almacenar la experiencia obtenida y calcular el nivel resultante de los minis. Algunos implicaban más tiempo de inactividad para modificar la experiencia de un jugador, mientras que otros ofrecían una buena experiencia sin perderle el ritmo al progreso de un jugador.

Siempre almacenar el nivel y la EXP para el próximo nivel del mini

¿Qué tal si usamos un patrón de SQL estándar y actualizaciones transaccionales? Las actualizaciones transaccionales permiten actualizar un conjunto de datos por completo, ya sea todos o ninguno. Si una parte no se escribe, todas vuelven a su valor original. Es útil para contar con las propiedades A.C.I.D. de atomicidad, consistencia, aislamiento y durabilidad.

Se usaría una sola fila para mantener la experiencia y el nivel total de cada mini, para que la lectura sea fluida y no exija muchos recursos.

Cuando cada mini obtiene experiencia, se actualiza el mini a 2 puntos de experiencia con 8 puntos faltantes, y su nivel es 3. Este tipo de cambio fuerza que se realice una lectura de los datos, un nuevo cómputo y una escritura en la base de datos. Este patrón no se lleva bien con Cassandra y sus puntos fuertes. Otro problema con este enfoque es que cambiar los montos necesarios para subir de nivel requeriría sacar el juego de funcionamiento para modificar los datos de todos los minis para todos los jugadores.

Almacenarlo como porcentaje

También pensamos en almacenar un porcentaje para el próximo nivel. Por ejemplo:

Mini

Nivel

Para próximo nivel

Bruto Gnoll

3

20%

Jinete de Grifos

2

20%

Piloto de S.E.G.U.R.O.

2

20%

Así, después de una partida, sería el momento de hacer unos cálculos rápidos. Por ejemplo, el Bruto Gnoll obtiene +3 de experiencia y el porcentaje es +3 / 10, por lo que hay que darle al Bruto Gnoll un 30% de un nivel, con el siguiente resultado:

Mini

Nivel

Para próximo nivel

Bruto Gnoll

3

50%

Jinete de Grifos

2

20%

Piloto de S.E.G.U.R.O.

2

20%

Esto nos daría flexibilidad si cambiáramos la curva de niveles de los minis, pero no implicaría ningún cambio de nivel en un mini. Esto también nos habilitaría unas visualizaciones más sencillas en la interfaz de usuario del juego, como si diéramos la indicación de llenar un 30% de la barra, sin necesidad de otros cálculos. Sin embargo, esta clase de patrón arrojaría porcentajes muy pequeños en los niveles más altos. Si un mini recibe 10 de experiencia por partida, y requiere 100 000 para subir de nivel, ese valor estaría representado en el 0,01%. Las unidades de procesamiento central (CPUs) pueden lidiar con los decimales flotantes, pero la precisión y la exactitud pueden causar errores si no introducimos todas las medidas necesarias para procesar la matemática de decimales flotantes.


Hasta la próxima...

Había muchas opciones para considerar en la tecnología de base de datos, como qué herramientas podían utilizarse y, por último, cómo almacenar y calcular todo. Como suele pasar con los proyectos en desarrollo, puede que cambiemos de rumbo más adelante si encontramos algo que funciona mejor, pero este parecía ser un buen comienzo para el juego.

¡Gracias por acompañarnos en esta actualización de ingeniería!

~ Andy Lim

Por cierto, aprovecho para avisar oficialmente que no soy el bandido de los ojos chistosos. Mi primer día con este equipo, el bandido ya había marcado mis monitores nuevos. Me observan todo el día… Todo… el... día.