Hace poco les compartí cómo nació Aurora Optimizer y cómo estructuré su arquitectura para sobrevivir a la concurrencia masiva de Folia. Diseñar el sistema en papel (y en código) fue un reto entretenido, casi de manual. Pero si algo enseña el desarrollo backend es que el código compila de maravilla hasta que se enfrenta al mundo real. Y en un servidor de Minecraft comunitario, donde el presupuesto es limitado y el hardware no perdona, el mundo real golpea duro.
Hoy quiero hablarles de la fase de pruebas, la etapa donde toca ser pragmático.
Cuando empecé a someter el plugin a pruebas de carga, partía de una idea clásica y algo ingenua: el rendimiento debe ser impecable. Quería ver la consola marcando un promedio de 19.5 TPS (ticks por segundo, el “pulso” del servidor: 20 es lo ideal) sin importar cuántos jugadores o entidades hubiera.
Construí un entorno automatizado bastante completo: scripts de PowerShell que levantaban el servidor localmente, compilaban el JAR y soltaban una horda de bots programados en Node.js (Mineflayer) para simular escenarios de estrés. Había de todo, desde una carga nominal de jugadores moviéndose hasta “bombas de lag” sostenidas durante horas en lo que llamé la fase D (soak testing).
¿El resultado inicial en mi entorno local? Las pruebas fallaban constantemente. El TPS caía, el test se abortaba y yo me frustraba. Me preguntaba si mi código en Java era ineficiente, si los hilos asíncronos de Folia se estaban bloqueando o si simplemente mi máquina no daba para más.
Hasta que caí en cuenta de algo que replanteó el proyecto: un “el test falló porque el TPS bajó de 19.5” es el equivalente técnico a que tu mecánico te diga “el auto hace un ruido raro” y se vaya. No da contexto, no da herramientas y esconde la verdadera naturaleza del problema.
La trampa del falso negativo
En mi primer modelo, si el servidor sufría un pico de lag repentino en el minuto 10 de una prueba de estrés de 4 horas, el orquestador abortaba la ejecución de inmediato. Rojo en la consola.
¿Qué aprendía de eso? Prácticamente nada. Me quedaba ciego ante las siguientes 3 horas y 50 minutos. ¿El servidor se recuperaba un minuto después? ¿Era un pico aislado del recolector de basura de Java, o el inicio de una fuga de memoria progresiva? Nunca lo sabría, porque el sistema mataba la prueba antes de dejar que el plugin intentara hacer su trabajo.
Y en un servidor real los picos de estrés no son un bug: son parte del terreno. Los jugadores van a construir granjas masivas y a activar mecanismos de redstone absurdos de golpe. Pretender que un servidor con recursos limitados absorba eso sin inmutarse no es realista. Lo que importa no es evitar el golpe, sino qué tan rápido te levantas después de recibirlo.
Eso me llevó a rediseñar la estrategia de QA con un modelo de puerta dual (dual-gate):
- La puerta operacional (bloqueante): lo básico. El servidor no debe crashear bajo cargas nominales ni escupir errores críticos. Si el servidor se apaga, el test falla y se detiene.
- La puerta de resiliencia (observacional): corre en modo sombra. Si viene un pico masivo y el TPS se desploma, la prueba no se detiene: empieza a tomar notas.
Qué se mide ahora
El modelo observacional entrega telemetría en lugar de un veredicto binario. Ahora mido tres cosas:
- Degradación de pico (%): cuánto dolió el golpe. Por ejemplo: “el TPS cayó un 44% respecto a lo normal”.
- Latencia de recuperación: el cronómetro más importante. ¿Cuánto tarda Aurora Optimizer en detectar el problema, aplicar sus políticas (pausar la IA de los mobs en un radio, reducir la sincronización de jugadores) y devolver el TPS al 90% de su capacidad? ¿Fueron 10 segundos o 5 minutos?
- Tiempo degradado (%): en una prueba de 4 horas, ¿pasamos el 30% del tiempo asfixiados o solo hubo caídas intermitentes?
Este enfoque también resolvió un problema de hardware. Yo desarrollo y pruebo en mi máquina local, que no tiene el poder multinúcleo del entorno de producción, y el modelo viejo me exigía rendimiento de producción en casa, así que generaba “fallos” todo el tiempo. Saqué esas variables de los scripts y las pasé a un contrato JSON declarativo: perfiles de SLO (objetivos de nivel de servicio). Ahora el entorno sabe que, en el perfil local, mantener 12 TPS bajo una bomba de lag y recuperarse en menos de 300 segundos es un éxito; en producción, la vara sube a 19.5. Dejé de pelearme con el hardware y empecé a medir la capacidad de adaptación del software.
El código ya no intenta ser un muro rígido que tarde o temprano se quiebra, sino un amortiguador que cede, absorbe el impacto y se recupera solo. Y una tabla CSV que detalla exactamente cómo se comportó el sistema durante un pico de estrés es mucho más útil que un simple texto verde de “Test Passed”.
En las próximas semanas estaré consolidando la evidencia de estas pruebas para decidir si finalmente le damos luz verde para producción. Deséenme suerte.