El arte de soltar el control: De la obsesión por el TPS perfecto a la resiliencia real
Hace poco les compartí cómo nació HeuristicOptimizer 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 desafío hermoso, casi de manual. Pero si algo te enseña el desarrollo de backend, es que el código compila de maravilla hasta que lo enfrentas al mundo real. Y en el contexto de 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. Esa etapa donde tu ego de desarrollador se desinfla y toca ser verdaderamente pragmático.
Cuando comencé a someter el plugin a pruebas de carga, mi mente operaba bajo un paradigma clásico y algo ingenuo: el rendimiento debe ser impecable. Quería ver la consola marcando un promedio de 19.5 TPS (Ticks Per Second) sin importar cuántos jugadores o entidades hubiera.
Construí un entorno automatizado bastante robusto. Orquesté scripts en PowerShell que levantaban el servidor localmente, compilaban el JAR y luego soltaban una horda de bots programados en Node.js (Mineflayer) para simular escenarios de estrés. Teníamos de todo: desde una carga nominal de jugadores moviéndose, hasta “bombas de lag” sostenidas durante horas en lo que llamamos 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.
Y entonces, hubo un cambio de mentalidad que replanteó todo el proyecto: un simple “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 te da contexto, no te da herramientas y, peor aún, te oculta la verdadera naturaleza del problema.
La trampa del falso negativo y el valor de la telemetría
Piénsenlo así: 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. ¡Pum! Rojo en la consola.
¿Qué aprendía de eso? Prácticamente nada. Me quedaba ciego ante lo que hubiera pasado en las siguientes 3 horas y 50 minutos. ¿El servidor se iba a recuperar un minuto después? ¿Era un pico aislado causado por el Garbage Collector, o era el inicio de una fuga de memoria (memory leak) progresiva y mortal? Jamás lo sabría porque el sistema mataba la prueba antes de dejar que el plugin intentara hacer su trabajo.
Me di cuenta de que en la administración de sistemas reales, los picos de estrés no son un bug, son una característica estructural. Los jugadores van a generar granjas masivas, van a activar mecanismos de redstone absurdos de golpe. Intentar que un servidor con recursos limitados absorba eso sin inmutarse es una utopía técnica. Lo que realmente importa no es evitar el golpe, sino qué tan rápido te levantas tras recibirlo.
Esto me llevó a rediseñar la estrategia de QA e implementar un modelo de puerta dual (Dual-Gate Model):
La Puerta Operacional (Bloqueante): Lo básico. El servidor no debe crashear bajo cargas nominales y no debe escupir errores críticos. Si el servidor se apaga, el test falla y se detiene.
La Puerta de Resiliencia (Observacional): Aquí está la magia. Se ejecuta en modo shadow (en la sombra). Si viene un pico masivo y el TPS se desploma, la prueba no se detiene. En su lugar, empieza a tomar notas febrilmente.
¿Por qué esto funciona mil veces mejor?
Técnicamente, este modelo observacional nos entrega telemetría pura y dura en lugar de un veredicto binario. Ahora medimos tres cosas vitales:
Degradación de Pico (Peak Degradation %): ¿Cuánto dolió el golpe? (Ej: “El TPS cayó un 44% respecto a lo normal”).
Latencia de Recuperación (Recovery Latency): El cronómetro más importante. ¿Cuánto tarda HeuristicOptimizer en darse cuenta del desastre, aplicar sus políticas (como pausar la IA de los mobs en un radio o reducir la sincronización de jugadores) y devolver el TPS al 90% de su capacidad? ¿Fueron 10 segundos o fueron 5 minutos?
Porcentaje de tiempo degradado (Soak Degraded %): Durante una prueba de 4 horas, ¿pasamos el 30% del tiempo asfixiados o solo hubo caídas intermitentes?
Además, este enfoque resolvió una crisis existencial de hardware. Yo desarrollo y hago pruebas en mi máquina local (que no tiene el poder multinúcleo de nuestro entorno de producción). El viejo modelo me exigía un rendimiento de CI (Integración Continua) en mi casa, lo que generaba “fallos” todo el tiempo. Al sacar estas variables de los scripts y pasarlas a un contrato JSON declarativo, creamos Perfiles de SLO (Service Level Objectives).
Ahora, mi 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 rotundo. En producción (ci), la vara sube a 19.5 TPS. Dejamos de pelearnos con el hardware y empezamos a medir la capacidad de adaptación del software.
El código ya no intenta ser un muro de piedra rígido que eventualmente se quiebra, sino un amortiguador que cede, absorbe el impacto y se recupera de forma autónoma. Y honestamente, tener una tabla de CSV detallando exactamente cómo tu código peleó y sobrevivió a un pico de estrés es mucho más útil (y satisfactorio) que ver 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.