Pruebas de rendimiento e Ideas de Mejoras - vykymoon/rpg-character-creator GitHub Wiki

Resultados de Pruebas de Carga y Rendimiento

En este documento se registran los resultados de las pruebas de carga realizadas sobre el servidor, evaluando el comportamiento del sistema ante variaciones en la cantidad de jugadores, la frecuencia de los mensajes y la configuración del socket TCP (Algoritmo de Nagle).

📊 Tabla de Resultados

Nombre de prueba Descripción del Escenario Observaciones y Resultados (p95, Errores, Throughput)
oficial-baseline 2 jugadores, envían cada 200ms (Baseline ADR) p95 = 3ms, 0% error. SLO cumplido con amplio margen.
oficial-carga 8 jugadores, cada 200ms por 3 min (Carga oficial ADR) p95 = 43ms, 0% error. SLO cumplido (umbral ≤150ms / <1%).
jug-2 Escalado: 2 jugadores, intervalo 200ms p95 = 4ms, sin errores. Comportamiento sano.
jug-4 Escalado: 4 jugadores, intervalo 200ms p95 = 45ms, sin errores. Salto notorio de latencia vs jug-2.
jug-8 Escalado: 8 jugadores, intervalo 200ms p95 = 47ms, sin errores. Estable.
jug-16 Escalado: 16 jugadores, intervalo 200ms p95 = 49ms, sin errores. Estable.
jug-24 Escalado: 24 jugadores, intervalo 200ms p95 = 51ms, sin errores. Estable.
jug-32 Escalado: 32 jugadores, intervalo 200ms p95 = 55ms, sin errores. Último punto sano antes del quiebre.
jug-48 Escalado: 48 jugadores, intervalo 200ms p95 = 689ms, 3236 mensajes omitidos, error 0.15%. Inicia colapso. CPU al 32% (No es cuello de botella de CPU).
jug-64 Escalado: 64 jugadores, intervalo 200ms p95 = 1534ms, error 8.75% (rompe SLO). Sistema saturado; throughput estancado en ~45.500 KB/s.
int8-50 8 jugadores, envían cada 50ms (4x más frecuente) p95 = 48ms, 38 msjs omitidos, 0% error. Primeras señales de saturación.
int8-100 8 jugadores, envían cada 100ms p95 = 46ms, 0 omitidos. Sano.
int8-200 8 jugadores, envían cada 200ms p95 = 48ms, 0 omitidos. Sano, consistente con oficial-carga.
int8-500 8 jugadores, envían cada 500ms (Lento) p95 = 47ms, 0 omitidos. Sano.
int8-1000 8 jugadores, envían cada 1000ms (Poco tráfico) p95 = 47ms, 0 omitidos. Sano.
int32-100 32 jugadores, envían cada 100ms (2x más frecuente) p95 = 100ms, 592 msjs omitidos. Alcanza el techo de ~45.500 KB/s.
int32-500 32 jugadores, envían cada 500ms p95 = 64ms, 0 omitidos. Mejora al reducir frecuencia.
int32-1000 32 jugadores, envían cada 1000ms p95 = 58ms, 0 omitidos. Sano.
nodelay-2 2 jugadores con TCP_NODELAY (Sin Nagle) p95 = 4ms. Sin cambios relevantes a baja escala.
nodelay-4 4 jugadores con TCP_NODELAY p95 = 3ms. Mejora drástica vs 45ms en jug-4.
nodelay-8 8 jugadores con TCP_NODELAY p95 = 5ms. Mejora drástica vs 47ms en jug-8.
nodelay-16 16 jugadores con TCP_NODELAY p95 = 6ms. Mejora drástica vs 49ms en jug-16.
nodelay-32 32 jugadores con TCP_NODELAY p95 = 9ms. Mejora drástica vs 55ms en jug-32.
nodelay-48 48 jugadores con TCP_NODELAY p95 = 739ms, error 0.10%. Colapso similar a jug-48.
nodelay-64 64 jugadores con TCP_NODELAY p95 = 1551ms, error 9.47%. Colapso idéntico a jug-64.

Pruebas unitarias y de integración

Implementan las secciones 6.1 y 6.2 de la estrategia de pruebas del ADR-001. El código está en src/test/java/.

Cómo ejecutarlas

Qué Comando
Solo unitarias mvn test
Unitarias + integración mvn verify

Herramientas: JUnit 5, con Maven Surefire para las unitarias (*Test.java) y Maven Failsafe para la de integración (*IT.java).

Pruebas unitarias

Prueban cada clase del dominio por separado, sin red ni interfaz gráfica.

Clase de prueba Qué verifica Pruebas Resultado
GameMapTest Dimensiones del mapa, borde de piedra bloqueado, zona de aparición despejada, agua bloqueada, caminos caminables y que el mapa se genere siempre igual 7 7 pasan
MovementRulesTest Movimiento válido, fuera del mapa, sobre piedra y sobre agua 5 5 pasan
GameSessionServiceTest Unirse a la partida, aplicar un movimiento válido, rechazar uno inválido, quitar un jugador, unirse dos veces y el contenido de los mensajes map y state 9 7 pasan, 2 desactivadas

No se usó Mockito: GameSessionService no tiene dependencias externas que simular y el mapa se genera siempre igual (semilla fija), así que se prueba con el mapa real.

Prueba de integración

MultijugadorIT levanta el GameServer real en un puerto local y conecta clientes GameClient reales por WebSocket. Es una prueba de caja negra: solo usa los mensajes del protocolo (join, move, map, state).

Prueba Resultado
Al unirse, el jugador recibe el mapa del servidor Pasa
Cuando un jugador se mueve, el otro recibe la posición nueva Pasa
Un movimiento inválido no cambia la posición que ven los demás Pasa
Cuando un jugador se desconecta, desaparece del estado de los demás Pasa

Resumen

Tipo Pruebas Pasan Desactivadas
Unitarias 21 19 2
Integración 4 4 0

Para comprobar que las pruebas detectan errores, se modificó MovementRules a propósito para aceptar cualquier movimiento: fallaron 4 pruebas.

Pendientes

Las 2 pruebas desactivadas documentan comportamiento que un servidor autoritativo debería tener y el código actual todavía no cumple:

  • move() acepta saltar a cualquier casilla caminable, no solo a una vecina.
  • move() crea al jugador aunque nunca haya hecho join.

Al corregir GameSessionService.move() basta con quitarles la anotación @Disabled.

🧠 Interpretación y Conclusiones Arquitectónicas

Para entender mejor los límites de nuestro sistema, los resultados se han agrupado en cuatro áreas clave de análisis:

1. Cumplimiento de SLOs (Escenarios Oficiales)

Las pruebas oficial-baseline y oficial-carga confirman que el sistema actual cumple holgadamente con los Service Level Objectives (SLOs) definidos en el ADR para las cargas esperadas. En el escenario realista de 8 jugadores, la latencia p95 se mantiene en 43ms (muy por debajo del límite de 150ms) con un 0% de error.

2. Límites Estructurales y Cuellos de Botella (Barrido de Jugadores)

Al escalar la cantidad de jugadores manteniendo el envío a 200ms, observamos lo siguiente:

  • Zona de Confort: El sistema es completamente estable hasta los 32 jugadores (jug-32).
  • Punto de Quiebre: A partir de los 48 jugadores, el sistema colapsa (jug-48 y jug-64). La latencia p95 se dispara a más de 1.5 segundos y los errores superan el 8%.
  • Diagnóstico del Cuello de Botella: La CPU del servidor se mantiene baja (~32% en el colapso), lo que indica que no es un problema de procesamiento. El problema radica en que el throughput de red se estanca en un techo rígido de ~45.500 KB/s. Esto sugiere que el modelo de broadcast (donde enviar el estado a $N$ jugadores cuesta $O(N)$ o $O(N^2)$ dependiendo de la arquitectura) satura el ancho de banda o la cola de red del sistema operativo a partir de ~45 conexiones concurrentes transmitiendo a ese ritmo.

3. Impacto de la Frecuencia de Actualización (Tickrate)

Las pruebas con el prefijo int8 e int32 demuestran que el límite del sistema no es estrictamente la "cantidad de jugadores", sino el volumen de paquetes por segundo:

  • Aumentar la frecuencia a 50ms con solo 8 jugadores (int8-50) ya empieza a mostrar mensajes omitidos.
  • Con 32 jugadores enviando cada 100ms (int32-100), el sistema alcanza el mismo techo exacto de throughput (~45.500 KB/s) que se vio con 48/64 jugadores a 200ms.
  • Conclusión: Si necesitamos soportar más jugadores en una misma sala, la mitigación directa es reducir la frecuencia de actualización (enviar cada 500ms o más).

4. El Efecto del Algoritmo de Nagle (TCP_NODELAY)

El hallazgo más importante para la optimización de latencia en escenarios sanos.

  • El Problema Original: En las pruebas base (jug-4 a jug-32), notamos un salto artificial de latencia (de 4ms a ~45ms-55ms).
  • La Solución: Al desactivar el Algoritmo de Nagle usando el flag TCP_NODELAY (pruebas nodelay-X), la latencia p95 bajó a niveles de un solo dígito (3ms - 9ms) para cualquier escenario de hasta 32 jugadores. Esto confirma que el retraso previo era causado por el OS almacenando paquetes en búfer, no por el código del servidor.
  • Limitación de la Solución: Aunque TCP_NODELAY mejora drásticamente la latencia en estados sanos, no salva al sistema del colapso estructural. Las pruebas nodelay-48 y nodelay-64 fracasaron exactamente igual que sus contrapartes normales. Esto reconfirma que el límite de ~45 jugadores a 200ms es un tope físico/arquitectónico (throughput/broadcast), y no un problema de configuración de socket.

Ideas de evolución

  • Chat y lista de jugadores conectados en el modo LAN.
  • Sincronizar encuentros o combates entre jugadores.
  • Editor de catálogo desde la interfaz (hoy solo vía JSON).
  • Nueva pantalla del wizard (Step6…Controller) sin modificar las existentes.