Un cuento embarazoso: por qué mi servidor solo podía manejar 10 jugadores

Lo que podría ser aún más vergonzoso es que en un momento me había convencido de que 10 jugadores por servidor era normal.

Todo empezó con una idea a principios de verano. Estaba de pie en mi habitación tratando de pensar en un juego io para hacer (decidí que si iba a hacer un juego, me limité a hacer un juego io para obtener el máximo potencial viral, eso es una cosa, lo juro).

Entonces, comencé a analizar qué hacía que ciertos juegos io (agar.io, slither.io, etc.) fueran adictivos. Estaba encontrando comparaciones y similitudes entre dichos juegos, como se ve en la siguiente imagen:

Finalmente, después de un poco más de lluvia de ideas, aterricé en knckout.io. Es el nombre del juego. Intente permanecer en el mapa y derribar a los demás. Me encantó. Controles simples, objetivo claro y una hermosa mecánica de juego.

Después de explicar cómo quería que se viera y se sintiera el juego, me puse manos a la obra. Regresaba a casa todos los días de mi pasantía de verano, hacía ejercicio y luego codificaba.

Primero conseguí que el jugador se moviera como quería. Luego manejé el impulso. Luego las colisiones. Finalmente, el juego estaba listo y listo para ser probado por el público. O eso pensé…

El fin de semana pasado (hace aproximadamente una semana), estaba emocionado y listo para mostrarle al mundo lo que hice. Así que fui a Internet y encontré un pequeño subreddit llamado "playmygame". Escribí un breve resumen y lo publiqué (ps en los comentarios de la publicación, puedes ver claramente que estaba estresado por la capacidad de mi servidor). Esperé pacientemente, luego ¡HUZZAH! Un jugador se había unido.

Íbamos y veníamos el uno al otro en el juego. Mientras tanto, estaba estresado y preocupado por lo que pensaba este jugador. Después de que este jugador perdiera todas sus vidas y fuera expulsado del partido en el que estábamos, esperé para ver si regresaban. ¡Y lo hicieron! Pero aún mejor: el jugador estableció su nombre en "ilikethisgame". ¡Mis ojos se abrieron como platos y sentí una descarga de adrenalina! Yo era el chico más feliz del mundo.

Pronto se unieron otros jugadores y algunos dejaron comentarios en la publicación de Reddit. ¡Más jugadores dijeron que habían disfrutado del juego! Estaba extasiado. Luego verifiqué cómo estaba funcionando mi servidor (el 15/8) ...

Sentí como si alguien me hubiera dejado sin aliento. ¿Fue esto real? Esto tenía que ser falso, pensé. Solo dos juegos y el servidor está teniendo dificultades para procesarlos.

Empecé a pensar en dónde me equivoqué en mi código. Pensé que la detección de colisiones, sin duda, tenía que ser el cuello de botella. Pero ya estaba usando quadtrees para ayudar a reducir el número de pases de detección de colisiones.

Tuve que hacer un trabajo sucio, así que puse en marcha un nuevo servidor Digital Ocean para usar como mi servidor de desarrollo. Luego desactivé temporalmente la detección de colisiones por completo y vi que el problema seguía ahí.

Bien, si la detección de colisiones no fue el problema, ¿qué más podría ser?

Pensé en la cantidad de información que estaba enviando desde el servidor a cada cliente cada segundo. Tenía esta función de transmisión que enviaba el estado del juego cada 22 milisegundos a cada cliente. En esta función, estaba filtrando innecesariamente el reproductor local del cliente dado en una allPlayerspropiedad, solo para poner el reproductor local en su propia propiedad. Entonces, no solo estaba poniendo un bucle for (el filtrado) en otro bucle for (la transmisión para cada cliente), también estaba personalizando los datos que se enviarían mediante esta función de transmisión para cada cliente.

Esta personalización no fue necesaria. Debería poder enviar el estado del juego a todos sin personalización. Todos deberían obtener los mismos datos (y los datos no deberían adaptarse a un cliente específico). Aquí tenía que ser donde la CPU estaba siendo devorada. Así que optimicé esta función, la subí al servidor de desarrollo y verifiqué el gráfico de la CPU. Sin arreglo.

Con mi ignorancia, comencé a convencerme de que ~ 10-20 jugadores por 1 servidor central era bueno. Ahora bien, ¿cómo llego a esa conclusión? Bueno, mi extrema confianza en mis habilidades técnicas claramente me estaba cegando de la realidad. Me encontré con una publicación donde el creador de agar.io dijo que su servidor de 1 núcleo puede manejar alrededor de 190 jugadores. Rápidamente salí de ella.

El siguiente culpable que había alineado era: socket.io. Estaba usando socket.io para administrar la comunicación en tiempo real entre el cliente y el servidor. Había escuchado antes que socket.io no era tan liviano como otras alternativas.

En el pasado, si deseaba enviar un mensaje de forma asincrónica, tenía que implementar algún tipo de truco: sondeo largo o sockets flash. Esto se debió a que no todos los navegadores web admitían websockets. Pero la mayoría de los navegadores ahora ofrecen soporte nativo. Pero para que socket.io establezca una conexión, primero lo hace utilizando uno de los trucos disponibles mencionados y luego actualiza la conexión si el cliente admite una mejor manera. Aunque los websockets ya son ampliamente compatibles. Este enfoque se produce a expensas de la CPU y la memoria. Pero no tanto como había pensado ...

Entré en línea e ingenuamente escribí "problema de CPU de socket io" en Google. El primer par de resultados se tituló "Node.js - Cómo depurar problemas de CPU de Node + Socket.io - Fallo del servidor" y "Node.js - Servidor de nodo Socket.io con CPU alta - Stack Overflow". Mis ojos se iluminaron. Me tranquilizó saber que este era el culpable de mi problema. Pero hice clic en el primer artículo y el autor mencionó que estaba tratando con ~ 1500 conexiones de socket simultáneas. No soy un estudiante de matemáticas, pero 20 jugadores es significativamente menos que 1,500 jugadores.

Solo por el gusto de hacerlo, cambié mi aplicación Node del lado del servidor para usar pequeños websockets, luego cambié la aplicación Node del lado del cliente para usar el soporte de websocket nativo, directamente dentro del navegador. Envié los cambios al servidor de desarrollo y verifiqué el gráfico de la CPU. Sin arreglo.

Mi moral estaba en su punto más bajo. Empecé a encogerme cada vez que tenía que comprobar el maldito gráfico de la CPU. Pensé que nunca conseguiría que esa línea azul dejara de huir de mí. Esta fue la única vez que me sentí completamente incapaz de manejar alguna tarea técnica. Pero luego sucedió ...

Estaba sentado frente al gráfico de la CPU revolcándome en mi miseria cuando noté algo. No importaba cuántos juegos completos se estuvieran ejecutando o qué tan cerca estuvieran todos. La CPU aumentaba constantemente a un ritmo constante. Nunca me había quedado el tiempo suficiente para observar esto. ¡Pérdida de memoria!

Escaneé mi código, línea por línea, buscando el error (lo que debería haber hecho desde el principio). Allí estaba.

En mi juego, un evento es un objeto que captura información sobre cosas como muertes de jugadores, aumentos y colisiones. Entonces, se crea un evento cada vez que sucede una de esas cosas.

Tengo este bucle que pasa por cada evento y lo actualiza. Se llama cada 16 ms. Una vez que un evento cumple con su deber, se supone que debe eliminarse. Palabras clave: "se supone".

Bingo. Tenía memoria acumulada, así como una cantidad creciente de pases for-loop innecesarios. Inserté una línea de código y ¡listo!

Gran suspiro de alivio.

Mi próxima tarea es ver cuántos juegos (4 jugadores por juego) un servidor puede admitir ahora sin problemas. (Sé que son al menos 12 juegos, pero aún no he probado más). Ahora que sé que la cantidad de eventos tiene un gran impacto en la CPU ... ¿qué pasará en la producción cuando todos los jugadores disparen eventos de impulso, colisión y muerte cada segundo? Mis pruebas no lo han tenido en cuenta.

Además, después de que esta publicación se vuelva viral y mi juego siga su ejemplo, necesitaré escalar rápidamente la cantidad de servidores disponibles. Lo convertiré en el tema de una publicación futura junto con: "Cómo knckout.io creció a millones de jugadores". Sígueme aquí para recibir actualizaciones. :)