Módulo 8: Proyecto — Prueba de carga de la API de Reservo

5. La corrida y las métricas (p95/RPS/error)

Descripción

Ya tienes el escenario (lección 2), el perfil (lección 3) y los thresholds (lección 4). Al juntarlos y correr la prueba, sale un puñado de números por etapa. Esta lección es sobre leer esos números: los tres instrumentos que toda prueba de carga produce —latencia (p50/p95/p99), throughput (RPS) y tasa de error— y la historia que cuentan juntos. Corremos la prueba completa de verdad contra /quote_cpu y leemos su tabla con criterio: por qué el p95 trepa con la carga mientras el error se queda en cero, qué significa un RPS plano, y por qué ningún instrumento solo da el veredicto. Y escribimos el resumen de k6 equivalente como contenido, para ver los mismos números en el formato del k6 run.

Conexión con el módulo: las métricas son el material que el threshold de la lección 4 juzga y que la lección 6 exporta a JSON. Reúsa por entero el módulo 3: qué es el p95 y por qué el promedio miente, qué es el throughput/RPS, qué es la tasa de error, y cómo se lee el resumen de k6. Aquí no re-explicamos cómo se calcula un percentil (lo hace statistics.quantiles); leemos las métricas que la corrida produce y sacamos un veredicto honesto. Es la lección que te enseña a interpretar la corrida antes de que la lección 6 la convierta en un gate automático.

Los tres cuadrantes del tablero

Un piloto no vuela mirando un solo instrumento. En el tablero hay al menos tres cuadrantes que lee juntos: la velocidad (¿voy lo bastante rápido?), el combustible (¿me alcanza?) y la altitud (¿estoy donde debo?). Ninguno solo cuenta la historia: puedes ir rapidísimo y quedarte sin combustible; puedes tener tanque lleno y estar cayendo. El piloto competente los lee en conjunto, porque cada uno vigila una forma distinta de fallar.

Una prueba de carga tiene sus tres cuadrantes. La latencia (p95) es la velocidad: ¿responde rápido? La tasa de error es el combustible: ¿está fallando? El throughput (RPS) es la altitud: ¿cuánto trabajo sostiene? Y como en la cabina, ninguno solo da el veredicto. Un sistema con p95 perfecto pero 10% de error está roto. Uno con 0% de error pero p95 de 2 segundos es inusable. Uno con buen p95 y 0% de error pero que solo aguanta 5 RPS no sirve si esperas 500. Leer la corrida es leer los tres cuadrantes a la vez y entender qué forma de fallar vigila cada uno.

La corrida completa (ejecutada)

Corremos la prueba entera contra /quote_cpu —el escenario cotizar→reservar, por las etapas smoke→load→stress— y leemos la tabla de métricas. Salida real contra Reservo:

$ python3.14 loadtest.py http://127.0.0.1:PORT /quote_cpu results.json
PRUEBA DE CARGA — escenario cotizar->reservar contra /quote_cpu
perfil: smoke(5) -> load(20) -> stress(80) VUs
--------------------------------------------------------------------------
etapa     VUs    reqs      RPS      p50      p95      p99   error   checks
--------------------------------------------------------------------------
smoke       5    2336    582.2     8.95    14.13    16.91   0.00%  100.00%
load       20    2920    579.0    34.84    57.23    62.35   0.00%  100.00%
stress     80    3676    587.2   135.48   237.68   249.82   0.00%  100.00%
--------------------------------------------------------------------------

Léela cuadrante por cuadrante, empezando —siempre— por la tasa de error (M3: el error primero, porque una latencia baja de un sistema que falla no vale nada):

  • Tasa de error: 0.00% en las tres etapas. El sistema no falla: responde a todas las peticiones. Este cuadrante está limpio. Ojo: eso no significa que la prueba pase —la latencia puede estar rota aunque el error sea cero—. Significa que la degradación, si la hay, no es de disponibilidad.
  • Latencia (p50/p95/p99). Aquí está la historia. El p50 (la mediana, el usuario típico) va de 8.95 ms a 135.48 ms; el p95 (el usuario de la cola) de 14.13 ms a 237.68 ms; el p99 (el peor 1%) de 16.91 ms a 249.82 ms. Los tres trepan con la carga, y el p95 del stress (237.68 ms) es lo que interesa: cruzó el terreno del SLO. Fíjate en que el p50 y el p95 están cerca en cada etapa (135 vs 237 en stress): la distribución no tiene una cola larguísima —el trabajo de CPU afecta a todas las peticiones por igual, no solo a unas pocas—. Es una firma distinta a la de un endpoint con timeouts esporádicos, donde el p99 se dispararía mucho más que el p50.
  • Throughput (RPS): ~580 req/s, plano. Aquí está la señal sutil. El RPS casi no cambia entre smoke (582), load (579) y stress (587), aunque los VUs se multiplicaron por 16 (de 5 a 80). ¿Por qué? Porque /quote_cpu está saturado de CPU: el servidor procesa aproximadamente el mismo trabajo por segundo pase lo que pase, así que meter más VUs no aumenta el throughput —solo alarga la cola de espera, y esa cola es exactamente lo que dispara el p95—. Un RPS plano con p95 creciente es la firma de la saturación: el sistema llegó a su techo y los usuarios extra solo esperan.

La lectura conjunta: el sistema está disponible (0% error) y es correcto (100% checks), pero bajo el pico se pone lento (p95 = 237.68 ms) porque saturó su throughput (~580 RPS es su techo con este motor de precios). Ningún cuadrante solo lo dice; los tres juntos, sí. Esa es la interpretación que el threshold de la lección 4 convertirá en un FAIL.

El resumen agregado y el de k6 (contenido)

El threshold no juzga una etapa: juzga la corrida entera. Estas son las métricas agregadas de las tres etapas juntas (lo que el gate evalúa, lección 4):

agregado (corrida entera):  8932 peticiones
  RPS      583.4 req/s
  p50       42.00 ms
  p95      221.01 ms   <- lo que el threshold p(95)<200 juzga -> FAIL
  p99      244.74 ms
  error     0.00%
  checks  100.00%

El p95 agregado (221.01 ms) es un poco más bajo que el del pico (237.68 ms): las peticiones rápidas de smoke y load lo abaratan al mezclarse (M4). Pero igual cruza los 200 ms, así que el threshold falla. Así se vería el resumen de k6 para esta prueba —contenido rotulado, fiel al formato oficial de k6 run, no ejecutado aquí—:

     # CONTENIDO (así se ve `k6 run`; k6 no está instalado)

     scenarios: (100.00%) 1 scenario, 80 max VUs, 10m30s max duration
              * default: Up to 80 looping VUs for 10m30s over 7 stages

     ✓ quote status is 200
     ✓ quote price is correct
     ✓ book status is 200
     ✓ book is confirmed
     ✓ book has booking_id

     ✗ http_req_duration..............: p(95)=221.01ms  (threshold: p(95)<200)
     ✓ http_req_failed................: rate=0.00%      (threshold: rate<0.01)
     ✓ checks.........................: rate=100.00%    (threshold: rate>0.99)

     checks.........................: 100.00% ✓ 22330     ✗ 0
     http_req_duration..............: avg=45ms  min=4ms med=42ms max=280ms p(90)=180ms p(95)=221.01ms
     http_req_failed................: 0.00%   ✓ 0         ✗ 8932
     http_reqs......................: 8932    583.4/s
     iterations.....................: 4466    291.7/s
     vus............................: 80      min=5       max=80
     vus_max........................: 80      min=80      max=80

Mapea tus métricas medidas a las líneas de k6: tu p95 = 221.01 es http_req_duration ... p(95)=221.01ms; tu error 0.00% es http_req_failed 0.00%; tu RPS 583.4 es la tasa de http_reqs 583.4/s; tus checks al 100% son checks 100.00%. La ✗ roja junto a http_req_duration es el threshold roto —el mismo FAIL de tu tabla—, y es lo que hace que k6 run salga con código 99. Nota que iterations (4466) es la mitad de http_reqs (8932): cada iteración del escenario hace dos peticiones (cotizar y reservar), como vimos en la lección 2.

Errores comunes

Leer la latencia sin leer el error primero. Qué pasa: se celebra un p95 bajo sin notar que el 8% de las peticiones falló. Por qué pasa: la latencia es la métrica vistosa. Cómo detectarlo: si tu veredicto empieza por el p95 y no por la tasa de error, lees en el orden equivocado. Cómo corregirlo: lee primero la tasa de error —un sistema que falla no tiene una latencia "buena", tiene una latencia de las peticiones que respondió, que es otra cosa (M3)—. En esta corrida el error fue 0%, así que la latencia sí es representativa; si hubiera sido 8%, el p95 mentiría.

Interpretar el RPS plano como buena señal. Qué pasa: se ve que el RPS se mantiene en ~580 y se concluye "el throughput es estable, todo bien". Por qué pasa: "estable" suena positivo. Cómo detectarlo: si el RPS no sube al multiplicar los VUs y el p95 trepa, no es estabilidad, es saturación. Cómo corregirlo: lee el RPS y el p95 juntos —un RPS que se estanca mientras el p95 crece significa que el sistema llegó a su techo de throughput y los VUs extra solo hacen cola—. Ese techo es un dato clave: es cuánto trabajo aguanta Reservo con este motor de precios.

Reportar el p95 del pico como el que evalúa el threshold. Qué pasa: se dice "el p95 es 237.68 ms" (el del stress) cuando el gate reporta 221.01 (el agregado). Por qué pasa: se confunde el número de una etapa con el de la corrida entera. Cómo detectarlo: si tu p95 "oficial" no coincide con el del gate, uno es de etapa y otro agregado. Cómo corregirlo: usa el p95 por etapa para leer la forma (dónde se dobla) y el agregado para el veredicto (lo que el threshold juzga). Los dos son reales; el capstone reporta ambos a propósito (M4).

Ejercicios

Ejercicio 1 — El orden de lectura. Una corrida reporta: p95 = 45 ms, RPS = 1200, tasa de error = 12%. (a) ¿En qué orden lees estos tres números? (b) ¿Cuál es tu veredicto y por qué el p95 de 45 ms no lo salva?

Ver solución
  • (a) Primero la tasa de error (12%), luego los checks (si los hubiera), y al final la latencia. La disponibilidad manda: no tiene sentido celebrar la velocidad de un sistema que falla una de cada ocho peticiones.
  • (b) Veredicto: falla (un 12% de error rompe cualquier SLO de disponibilidad razonable, y probablemente el threshold rate<0.01). El p95 de 45 ms no lo salva porque es la latencia de las peticiones que respondieron —el 88%—; los 12% que fallaron no aparecen en ese número. Un p95 bonito de un sistema que falla es un espejismo: mide solo a los usuarios con suerte (M3).

Ejercicio 2 — RPS plano, ¿qué pasa? Entre load (20 VUs) y stress (80 VUs), el RPS pasó de 579 a 587 (casi igual) pero el p95 pasó de 57 a 238 ms. (a) ¿Por qué el RPS no subió si hay cuatro veces más VUs? (b) ¿A dónde se fue el tiempo de esos VUs extra?

Ver solución
  • (a) Porque /quote_cpu está saturado de CPU y el GIL serializa su trabajo: el servidor solo puede procesar ~580 peticiones por segundo, sin importar cuántos clientes lo pidan. Más VUs no le dan más capacidad de cómputo; el throughput ya tocó su techo.
  • (b) A la cola de espera. Los 60 VUs extra (de 20 a 80) no consiguen que el servidor trabaje más rápido; sus peticiones se forman en fila esperando su turno de CPU. Ese tiempo en fila es exactamente lo que dispara el p95 (de 57 a 238 ms). El trabajo total por segundo es el mismo; lo que crece es cuánto espera cada petición —y la espera es latencia—.

Ejercicio 3 — Escribe el veredicto. Con la corrida de esta lección (p95 agregado 221.01 ms, error 0%, checks 100%) y el SLO p(95)<200, rate<0.01, checks>0.99, escribe en dos o tres frases el veredicto honesto que le darías al equipo, leyendo los tres instrumentos.

Ver solución

Un ejemplo de veredicto bien hecho:

La prueba falla el SLO de latencia: el p95 de la corrida entera fue 221.01 ms, por encima del umbral de 200 ms, y bajo la etapa de stress llegó a 237.68 ms. La causa no es indisponibilidad —la tasa de error fue 0% y los checks 100%, así que el sistema está disponible y responde correcto— sino saturación: /quote_cpu topa su throughput en ~580 RPS, y al empujar la carga a 80 VUs las peticiones hacen cola y el p95 se dispara. El sistema es correcto y está disponible, pero no es lo bastante rápido bajo el pico esperado. El gate debe bloquear el deploy hasta que se investigue el cuello de botella de CPU del motor de precios.

Lo clave: cita los tres instrumentos, explica que el problema es latencia (no error) y nombra la causa (saturación de throughput), no solo el síntoma.

Resumen y siguiente paso

En esta lección aprendiste a leer la corrida: los tres cuadrantes del tablero —latencia (p50/p95/p99), throughput (RPS) y tasa de error— leídos juntos, porque cada uno vigila una forma distinta de fallar. Corriste la prueba completa de verdad contra /quote_cpu y la interpretaste: el error en 0% (disponible), los checks en 100% (correcto), pero el p95 trepando con la carga hasta 237.68 ms en el pico (lento) porque el RPS plano (~580, saturado) delata que el sistema tocó su techo de throughput. Viste el resumen agregado (p95 = 221.01 ms, lo que el threshold juzga) y el resumen de k6 equivalente como contenido, con la ✗ roja del threshold roto. La lección de fondo: ningún instrumento solo da el veredicto; los tres juntos, sí.

Reusaste por entero el módulo 3 (percentiles, throughput, tasa de error, leer el resumen). Antes de avanzar deberías poder: leer una tabla de métricas en el orden correcto (error primero); explicar qué significa un RPS plano con p95 creciente; y distinguir el p95 por etapa del agregado. Lo que sigue, en la lección 6, es convertir esta lectura en un gate automático: evaluar los thresholds, salir con exit code, y exportar las métricas a un results.json —el verde y el rojo, ejecutados, con sus códigos de salida—.

Recursos