Módulo 7: Analizar resultados y CI
7. Cuándo correr la prueba de carga
Descripción
Ya sabes poner la prueba de carga en un pipeline (lección 6). Queda la pregunta que el bloque on: de aquel load.yml dejó abierta: ¿cuándo se dispara?. La respuesta natural —"en cada PR, como los tests unitarios"— es un error, y entenderlo separa a quien copió un YAML de quien sabe operar pruebas de rendimiento. Una prueba de carga es lenta, cara, ruidosa y necesita un entorno estable, cualidades que la hacen pésima candidata para correr en cada cambio. En esta lección aprendes la cadencia correcta: nightly (programada, cada noche), pre-release (antes de un despliegue o un tag), y on-demand (a mano cuando hace falta), reservando el "en cada cambio" para los tests rápidos y baratos. Ves los disparadores on: de GitHub Actions que expresan cada cadencia (contenido) y el criterio para elegir entre ellas.
Conexión con el módulo: esta lección completa la mitad de automatizar. La lección 6 montó el pipeline (el cómo); esta decide su cadencia (el cuándo). Reúsa el load.yml de la lección 6, rellenando su bloque on: con criterio. Con ella cierra el módulo 7: sabes producir una prueba (M2–M6), analizarla (lecciones 2–5) y automatizarla con la frecuencia correcta (lecciones 6–7). Lo que sigue es el capstone (M8), donde todo esto se junta en una prueba de carga completa.
No pones el coche en el banco de pruebas cada vez que ajustas un tornillo
Un taller de coches tiene dos tipos de comprobación, y no los confunde. Cuando un mecánico ajusta un tornillo, hace una comprobación rápida: gira la pieza con la mano, verifica que quedó firme. Toma segundos, la hace tras cada ajuste, y es baratísima. Pero para saber si el coche rinde —potencia, consumo, comportamiento a alta velocidad— lo sube a un banco de pruebas (un dinamómetro): un equipo caro, que requiere montar el coche, calibrar sensores, correr un ciclo completo, y que ocupa una bahía entera durante un buen rato. Nadie sube el coche al dinamómetro cada vez que aprieta un tornillo —sería absurdo, carísimo y no le dejaría trabajar—. El banco de pruebas se usa al final de un ensamblaje importante o según un calendario, no tras cada cambio pequeño.
Los tests unitarios son la comprobación de mano: rápidos, baratos, en cada cambio. La prueba de carga es el banco de pruebas: cara, lenta, montada con cuidado, corrida en momentos elegidos. Poner la prueba de carga en cada PR es subir el coche al dinamómetro por cada tornillo. La cadencia correcta —nightly, pre-release, on-demand— es usar el banco de pruebas cuando de verdad aporta: periódicamente y antes de entregar.
Por qué la prueba de carga NO va en cada PR
Cuatro razones, cada una suficiente por sí sola:
- Es lenta. Una prueba de carga con sentido dura minutos, no segundos: hay que hacer ramp-up, sostener la carga un rato, ramp-down (los perfiles del módulo 4). Como referencia ejecutada de esta guía: incluso una corrida diminuta de 600 peticiones contra un endpoint de ~45 ms tardó 1.1 s de reloj (la baseline rápida, 0.11 s); una prueba realista de varios minutos a miles de VUs es mucho más. Multiplica eso por cada PR de un equipo activo y el pipeline se atasca.
- Es cara. Generar carga de verdad consume máquinas (a veces varias, para producir suficiente tráfico). Correrla en cada push es un gasto de infraestructura que no se justifica cuando la mayoría de los cambios no afectan el rendimiento.
- Es ruidosa. El p95 varía entre corridas por cómo el sistema operativo reparte recursos (lo viste en la lección 4: +0.8% entre dos corridas idénticas). En un runner de CI compartido, ese ruido es peor. Correrla en cada PR llenaría de falsos rojos por ruido, y un gate que grita en falso se acaba ignorando.
- Necesita un entorno estable. Para que los números signifiquen algo, el blanco debe estar en un entorno parecido a producción y aislado. Un runner efímero y compartido, distinto en cada PR, da mediciones que no se pueden comparar entre sí. Sin un entorno estable, no hay baseline confiable —y sin baseline, la detección de regresiones (lección 4) no funciona—.
La consecuencia es clara: el rendimiento no se protege corriendo la prueba más veces, sino corriéndola en los momentos correctos con un entorno confiable.
Las tres cadencias correctas (con sus disparadores)
Cada cadencia se expresa con un disparador on: de GitHub Actions. Contenido rotulado (aquí no se ejecuta git/gh):
Nightly — programada, cada noche. La red de seguridad de fondo: corre sin que nadie la dispare y atrapa regresiones que se colaron durante el día. Es donde vive el baseline (la corrida de anoche contra la que comparas hoy).
# CONTENIDO (no ejecutado aqui): disparador nightly. Ver docs.github.com/actions.
on:
schedule:
- cron: "0 3 * * *" # todos los dias a las 03:00 UTC (fuera de horas pico)
Pre-release — antes de desplegar. El gate que protege el lanzamiento: corre cuando se va a publicar una versión (un tag, una release), justo cuando más importa saber si el rendimiento aguanta. Aquí el threshold como gate (lección 6) tiene su máximo valor: bloquea un deploy que degradaría el rendimiento.
# CONTENIDO (no ejecutado aqui): disparador pre-release (al publicar un tag/release).
on:
release:
types: [published]
push:
tags:
- "v*" # cualquier tag de version, p.ej. v1.4.0
On-demand — a mano. La palanca para cuando un ingeniero sabe que su cambio toca el rendimiento (tocó una consulta, cambió un algoritmo) y quiere probar antes de mergear, sin esperar al nightly.
# CONTENIDO (no ejecutado aqui): disparador manual (boton "Run workflow" en la UI).
on:
workflow_dispatch: # se dispara a mano desde la interfaz de Actions
En la práctica se combinan los tres en un solo on: (como en el load.yml de la lección 6): nightly de fondo, pre-release como gate del lanzamiento, y on-demand para cuando alguien lo necesita. Lo que no está en esa lista es pull_request —el que corre en cada PR—, por las cuatro razones de arriba.
Dónde encaja en la pirámide de pruebas
Esto no es una regla aislada; es la pirámide de pruebas aplicada a la cadencia. La pirámide dice: muchas pruebas rápidas y baratas abajo, pocas lentas y caras arriba. La frecuencia con que corres cada tipo sigue esa forma:
| Tipo de prueba | Velocidad | Cuándo corre | Por qué |
|---|---|---|---|
| Unitaria | milisegundos | en cada cambio (cada push/PR) | rápida y barata: feedback inmediato |
| Integración | segundos | en cada PR o merge | algo más lenta, aún viable por cambio |
| E2E (navegador) | segundos–minutos | por PR importante / pre-merge | más lenta; se selecciona qué correr |
| Carga / rendimiento | minutos | nightly + pre-release + on-demand | lenta, cara, ruidosa, necesita entorno estable |
La prueba de carga está en la punta de la pirámide, junto a E2E (como estableció el módulo 1): pocas, valiosas, corridas en momentos elegidos. Correrla con la frecuencia de una prueba unitaria es invertir la pirámide —poner lo caro y lento donde va lo barato y rápido—, y una pirámide invertida es lenta, frágil y cara. La cadencia correcta mantiene la pirámide en pie.
Errores comunes
Poner on: pull_request en el workflow de carga. Qué pasa: la prueba de carga corre en cada PR, el pipeline se vuelve lentísimo, y los falsos rojos por ruido hacen que el equipo empiece a re-lanzar o a ignorar el gate. Por qué pasa: se copia el patrón de los tests unitarios sin pensar en el costo. Cómo detectarlo: PRs que tardan mucho por el paso de carga, o un gate de rendimiento que la gente saltea. Cómo corregirlo: quita pull_request; usa schedule + release + workflow_dispatch. El "en cada cambio" es para lo rápido y barato.
No tener ningún nightly (solo pre-release). Qué pasa: solo se corre la carga antes de un lanzamiento, así que una regresión que entró hace tres semanas se descubre el día del release —tarde, con muchos cambios candidatos, bajo presión—. Por qué pasa: se piensa que basta con el gate del lanzamiento. Cómo detectarlo: regresiones que aparecen "de golpe" antes de un release y cuesta atribuir. Cómo corregirlo: añade el nightly como red de fondo; atrapa las regresiones cerca del cambio que las causó, cuando hay un solo culpable candidato (la diferencia entre las corridas de ayer y hoy).
Correr en un entorno inestable y confiar en los números. Qué pasa: la prueba corre en un runner compartido y efímero, distinto cada vez, y los resultados no se pueden comparar entre corridas. Por qué pasa: se prioriza que "corra en CI" sobre que "signifique algo". Cómo detectarlo: p95 que salta erráticamente sin relación con los cambios de código. Cómo corregirlo: corre la carga contra un entorno estable y aislado (parecido a producción, dedicado), para que el baseline sea confiable y las comparaciones (lección 4) tengan sentido.
Ejercicios
Ejercicio 1 — Elige la cadencia. Para cada situación, di qué disparador usarías (schedule, release/push tags, workflow_dispatch, o pull_request) y por qué. (a) Una red de seguridad que atrape regresiones cerca de cuando entran. (b) Un gate que impida publicar una versión con el rendimiento degradado. (c) Un ingeniero que tocó una consulta y quiere probar antes de mergear. (d) Verificar la lógica de una función pura tras cada cambio.
Ver solución
- (a)
schedule(nightly). Corre cada noche sin intervención y atrapa regresiones con un solo día de cambios candidatos. - (b)
release/pushde tags (pre-release). El gate protege el lanzamiento justo antes de desplegar. - (c)
workflow_dispatch(on-demand). El ingeniero lo dispara a mano cuando sabe que su cambio toca el rendimiento. - (d)
pull_request—pero para un test unitario, no de carga—. Es rápido y barato, va en cada cambio. La prueba de carga no.
Ejercicio 2 — ¿Por qué no en cada PR? Nombra las cuatro razones por las que una prueba de carga no va en cada PR, y para cada una explica en una frase por qué un test unitario sí puede ir en cada PR.
Ver solución
- Lenta (minutos): el unitario tarda milisegundos, así que no atasca el pipeline.
- Cara (consume máquinas para generar carga): el unitario corre en el mismo runner sin infraestructura extra.
- Ruidosa (el p95 varía entre corridas): el unitario es determinista —misma entrada, misma salida—, sin falsos rojos.
- Necesita entorno estable (para que los números comparen): el unitario no depende del entorno; prueba lógica aislada, igual en cualquier runner.
La asimetría es la pirámide: lo rápido/barato/determinista va abajo y en cada cambio; lo lento/caro/ruidoso va arriba y en momentos elegidos.
Ejercicio 3 — Diseña el on:. Escribe (como contenido) el bloque on: de un load.yml que combine las tres cadencias correctas: nightly a las 02:00 UTC, al publicar una release, y a mano. ¿Por qué no incluyes pull_request?
Ver solución
# CONTENIDO: on: con las tres cadencias correctas.
on:
schedule:
- cron: "0 2 * * *" # nightly a las 02:00 UTC
release:
types: [published] # pre-release: al publicar una version
workflow_dispatch: # on-demand: a mano
No se incluye pull_request porque la prueba de carga es lenta, cara, ruidosa y necesita un entorno estable: correrla en cada PR atascaría el pipeline, gastaría infraestructura, generaría falsos rojos por ruido y daría números no comparables. El "en cada cambio" se reserva para los tests rápidos y baratos (unitarios); la carga vive en la punta de la pirámide, en momentos elegidos.
Resumen y siguiente paso
En esta lección aprendiste cuándo correr la prueba de carga. La respuesta no es "lo más seguido posible" sino "en los momentos correctos, con un entorno confiable": nightly (red de seguridad de fondo, donde vive el baseline), pre-release (el gate que protege el lanzamiento) y on-demand (la palanca para cuando un cambio toca el rendimiento). Y no en cada PR, por cuatro razones —es lenta, cara, ruidosa y necesita un entorno estable—, que son las mismas que la ubican en la punta de la pirámide, junto a E2E, y no en la base con los tests unitarios. Viste los disparadores on: que expresan cada cadencia (schedule, release, workflow_dispatch) y por qué pull_request no está entre ellos.
Con esto cierras el módulo 7: sabes producir una prueba de carga (M2–M6), analizarla —leer el resumen, exportar, detectar una regresión, localizar el cuello de botella (lecciones 2–5)— y automatizarla con el pipeline correcto y la cadencia correcta (lecciones 6–7). Antes de avanzar deberías poder: nombrar las tres cadencias y su disparador; dar las cuatro razones por las que la carga no va en cada PR; y ubicarla en la pirámide. Lo que sigue, en la lección 8, es el mini-proyecto que junta la mitad de análisis de este módulo con tus manos: corres dos cargas, las exportas, detectas la regresión con exit code, y escribes el load.yml de CI —el módulo entero, ejecutado de principio a fin—.
Recursos
- k6 — Running k6 in CI — la guía oficial de k6 sobre integrar la prueba de carga en CI, incluida la recomendación de cadencia (no en cada commit). La fuente del contenido de esta lección.
- GitHub Actions — Events that trigger workflows — la referencia de los disparadores
on:(schedule,release,workflow_dispatch,pull_request) con los que se expresa cada cadencia. - GitHub Actions — Schedule (cron) — la sintaxis del cron del
schedulepara el nightly. - Google SRE Book — Release Engineering — el fundamento de por qué se coloca un gate de rendimiento antes de un release y no en cada cambio; la ingeniería de entrega detrás de la cadencia pre-release.