Las pruebas de resistencia tienen un problema de credibilidad. Los equipos las ejecutan, obtienen un informe limpio, lanzan la compilación y luego ven aparecer una fuga de memoria en producción tres días después. La ejecución no estaba defectuosa. Midió lo que se configuró para medir. El problema es que la mayoría de las configuraciones de resistencia responden a una pregunta más estrecha de lo que los ingenieros creen, y por eso las ejecuciones aprobadas y la producción con fugas siguen coexistiendo.
Este artículo trata sobre por qué ocurre eso y cómo diseñar una ejecución que no lo haga. Si ya utilizas las pruebas de resistencia en tu canalización y sigues lanzando fugas, la solución casi nunca es una ejecución nocturna más larga. La solución es una forma distinta de prueba, y se enmarca en una práctica de pruebas de rendimiento más amplia que trata la trayectoria como criterio de aprobación en lugar de la duración. Datos recientes del sector concretan lo que está en juego: la encuesta de New Relic situó el coste medio de una interrupción operativa en 33 333 dólares por minuto, con una factura anual media de 76 millones de dólares por caídas. Una sola fuga que aparece al tercer día es un asunto que afecta a la cuenta de resultados.
Por qué las pruebas de resistencia aprobadas siguen teniendo fugas
La distancia entre un panel que aprueba y un incidente en producción casi siempre se remonta a uno de dos errores. O bien la prueba no puede ver la fuga porque no está observando la métrica correcta, o bien no puede verla porque nunca activó la ruta de código que produce la fuga en primer lugar.
La mayoría de las guías responden a esto prescribiendo una duración. Ocho horas, doce, setenta y dos durante un fin de semana. Esa prescripción es lo que mete las fugas en producción. Una fuga con un tiempo de duplicación de 40 horas parece plana durante las primeras 8 horas, ligeramente interesante a las 20 y aterradora a las 60. Una prueba que termina en la hora 8 con una curva de aspecto lineal no es prueba de un sistema sano. Es prueba de que dejaste de observar demasiado pronto. El informe State of Resilience 2025 de Cockroach Labs encuestó a 1000 altos ejecutivos de tecnología y descubrió que las organizaciones sufren una media de 86 caídas al año, y que el 70 % de las grandes empresas tardan 60 minutos o más en resolver cada una. La mayoría de esos incidentes se podían prevenir en el banco de pruebas.
La trayectoria por encima de la duración
El enfoque es pequeño, pero todo cambia cuando lo aceptas. Una prueba de resistencia no aprueba ni suspende en función del valor final de una métrica. Aprueba o suspende en función de la pendiente. Si tu montón (heap) termina en 2,1 GB después de empezar en 2,0 GB, la pregunta interesante no es la diferencia. La pregunta interesante es si la pendiente del suelo posterior a la recolección de basura es estadísticamente distinguible de cero a lo largo del último 60 % de la ejecución.
Ese enfoque cambia lo que construyes. Dejas de preguntar «¿cuánto debería durar la ejecución?» y empiezas a preguntar «¿cuál es el menor crecimiento por hora que necesito detectar y qué duración de ejecución me da la sensibilidad para detectarlo por encima del ruido?». Dejas de preguntar «¿el gráfico se veía plano?» y empiezas a ajustar regresiones. Dejas de fiarte de un único panel y empiezas a correlacionar el montón (heap), la duración de las pausas de recolección de basura (GC), el máximo histórico del grupo de conexiones y el recuento de descriptores de archivo con el mismo eje temporal.
Todo lo que sigue es una forma concreta en que se manifiesta el enfoque de la trayectoria. Cuatro puntos ciegos, cuatro métricas, cuatro decisiones de diseño.
Los cuatro puntos ciegos
La mayoría de las ejecuciones cortas y uniformes pasan por alto las fugas por una de cuatro razones estructurales. Cada una tiene una firma diagnóstica, y cada una tiene una solución que no consiste en alargar la ejecución porque sí.
Demasiado corta para el tiempo de duplicación
Una fuga que se duplica cada 36 horas es invisible dentro de una ventana de 8 horas. La curva está en su zona exponencial temprana y plana. Para la hora 40 en producción, es un muro. Esta es la razón más común por la que las pruebas de resistencia en las pruebas de software pasan por alto lo que se diseñaron para detectar.
La señal que realmente quieres no es el montón en bruto. El montón en bruto oscila con cada ciclo de asignación y recolección. La señal es el suelo del montón posterior a la GC, muestreado cada 5 minutos, con una regresión lineal ajustada a lo largo del último 60 % de la ejecución. Si la pendiente es mayor que cero con p < 0,05, tienes una fuga, por muy modesta que parezca la cifra absoluta.
La solución de diseño es fijar la duración de tu ejecución según la sensibilidad y no según la convención. Decide el menor crecimiento del montón por hora que importaría en producción y luego calcula la duración mínima de la ejecución que te permite distinguir esa pendiente de las fluctuaciones de la GC. Si no puedes diferenciar 2 MB por hora del ruido dentro de tu ventana actual, tu ventana es demasiado corta, y por mucho que mires el gráfico no lo vas a arreglar.
Carga plana: el tráfico real tiene forma
El tráfico de producción tiene picos y valles. Las horas de menor actividad son cuando la recolección de basura se vuelve agresiva, cuando los grupos de conexiones se vacían, cuando entra en acción el desalojo de la caché y activa cualquier escucha que tenga enganchada. Las fugas que se esconden tras la presión de la GC en los picos quedan al descubierto en los valles. Las fugas que solo se activan al desalojar la caché nunca reservan memoria bajo una carga plana de 100 usuarios virtuales, porque la caché se mantiene caliente y nunca se desaloja nada.
Una carga uniforme también oculta la métrica que deberías observar con más atención: la duración y la frecuencia de las pausas de la GC a lo largo de la curva de carga, no el promedio de toda la ejecución. Lo que buscas es una deriva en la duración de las pausas correlacionada con las transiciones en la forma de la carga. Los promedios ocultan la deriva; las transiciones la ponen al descubierto.
La solución es dar a la prueba de resistencia la forma de tu curva de tráfico real. Aumenta la carga de forma gradual, mantenla en el pico durante un par de horas, baja al 20 % durante 90 minutos y vuelve a subir. Repite el ciclo a lo largo de la ejecución. Si tu producción tiene una hora tranquila cada noche, mete una hora tranquila en la prueba. Una línea plana en el generador de carga es una prueba de carga con ambiciones de la duración de una de resistencia, no una prueba de resistencia de verdad.
La tarea programada que olvidaste ejecutar
Las rutas de código con más probabilidad de tener fugas son las que se ejecutan pocas veces: tareas cron, conciliaciones de medianoche, generación de informes semanales, reequilibrio horario de Kafka, vaciado nocturno de la base de datos. Reciben menos revisiones, reservan grandes estructuras transitorias y retienen referencias durante más tiempo del que deberían. Y casi nunca aparecen en el escenario de resistencia, que normalmente solo cubre el flujo de solicitudes HTTP.
Aquí, las métricas que hay que superponer a la línea de tiempo son funciones escalonadas más que pendientes:
- El máximo histórico del grupo de conexiones, muestreado en torno a cada ejecución de una tarea programada.
- El recuento de descriptores de archivo antes, durante y después de la tarea.
- El recuento de hilos si la tarea genera procesos de trabajo.
- Cualquier profundidad de cola que la tarea produzca o consuma.
Si alguna de estas sube de forma escalonada en cada ejecución de la tarea y nunca vuelve a bajar, has encontrado una fuga que la ejecución basada solo en tráfico nunca sacaría a la luz.
La solución de diseño es un cambio de definición. Tu escenario no es «tráfico». Es «tráfico más el calendario». Enumera cada tarea programada que se dispararía durante una semana de producción y, o bien comprime su calendario dentro de la ventana de prueba, o bien actívala de forma explícita a intervalos realistas. Una ejecución que nunca completa un ciclo entero de tu tarea programada más lenta es una ejecución de carga con ambiciones de la duración de una de resistencia.
Discrepancia en la política de reinicios
Esta es la más sutil y la que más a menudo se pasa por alto. Los entornos de prueba se reciclan de forma agresiva. La CI levanta un contenedor nuevo en cada ejecución. El orquestador mata cualquier cosa con un pico de errores 5xx. Los umbrales de las comprobaciones de estado están ajustados de forma estricta para obtener respuesta rápida. La producción, en cambio, puede funcionar 30 días entre reinicios. Una fuga que tardaría 200 horas en importar nunca tiene la oportunidad de importar en una prueba que se reinicia en la hora 12 porque una comprobación de estado dio un falso positivo.
La métrica que hay que vigilar es trivial: el tiempo de actividad del contenedor o del proceso junto con la métrica de la fuga. Cualquier reinicio durante la ejecución invalida la trayectoria a partir de ese punto. Sin embargo, en la mayoría de las configuraciones de CI, esta correlación nunca se representa, y la ejecución se marca en verde porque las métricas posteriores al reinicio dieron la casualidad de verse bien.
La solución es replicar la política de reinicios de producción en el entorno de prueba, no la de la CI. Amplía los umbrales de disponibilidad (liveness). Desactiva la eliminación agresiva por comprobación de estado durante toda la ejecución. Trata cualquier reinicio a mitad de la ejecución como una prueba de resistencia fallida, que requiere investigación y una nueva ejecución, en lugar de como un contratiempo que pasas por alto en el panel. Si tu orquestador toleraría un proceso de 30 días en producción, tu banco de pruebas debería tolerar uno durante toda la ejecución.
Cómo es una prueba de resistencia de verdad
Usa esto como lista de auditoría para tu próxima ejecución. Si tu canalización actual falla en más de tres de estos puntos, el número de ocho horas no es tu problema.
- La duración de la ejecución se fija según la sensibilidad de detección objetivo, calculada a partir del crecimiento por hora aceptable, no según «una noche entera».
- La curva de carga tiene la forma de la producción, con picos, valles y transiciones, en lugar de una meseta plana.
- Cada tarea programada que se dispararía en una semana de producción se comprime dentro de la ventana de ejecución o se activa de forma explícita.
- La política de reinicios y de disponibilidad del entorno de prueba coincide con la de producción, no con los valores predeterminados de la CI.
- El suelo del montón posterior a la GC es la señal principal del montón, no el montón en bruto, y se ajusta con una regresión lineal a lo largo del último 60 % de la ejecución.
- El máximo histórico del grupo de conexiones, el recuento de descriptores de archivo y el recuento de hilos se representan como series temporales con el mismo eje que la carga y el calendario de tareas.
- La duración de las pausas de la GC se rastrea a lo largo de la curva de carga, no se resume como un promedio de toda la ejecución.
- Cualquier reinicio de proceso a mitad de la ejecución invalida automáticamente la ejecución y requiere investigación antes de poder marcarse en verde.
- El criterio de aprobación se redacta como un umbral de pendiente y un techo de máximo histórico, no como «el gráfico se veía plano».
Si te saltas cualquiera de estos, habrás construido una ejecución capaz de producir un verde falso. Cúmplelos los nueve y las ejecuciones que aprueben significarán algo.
Dónde encaja la resistencia con el resto
Las pruebas de carga sostenida responden a una sola pregunta: qué se acumula con el tiempo. No sustituyen a las demás disciplinas de rendimiento, y dependen de ellas. Las pruebas de carga te dicen si el sistema mantiene el rendimiento máximo; sin esa referencia, una prueba de resistencia con forma no tiene una carga objetivo que mantener. El comportamiento ante ráfagas es una cuestión completamente aparte, y si no has sometido el sistema a tráfico repentino, estás lanzando a ciegas hacia cada lanzamiento de marketing, evento viral y avalancha de inicios de sesión del lunes por la mañana.
Las arquitecturas distribuidas añaden su propia dimensión. Un monolito tiene fugas en un solo proceso; una malla de servicios puede tener fugas en las formas en que se comunican entre sí, y el rastreo tiene que seguir la solicitud a través de cada salto. Ejecutar cargas sostenidas en microservicios con correlación de rastreo distribuido es una disciplina distinta de someter a resistencia un único servicio, y merece su propio diseño de escenario. Lo mismo ocurre una capa más abajo, en la interfaz: los cuellos de botella a nivel de API que aparecen bajo carga sostenida amplifican todos los modos de fallo de los cuatro puntos ciegos anteriores, porque un contrato lento agrava cada fuga que desencadena la carga de trabajo.
Diseña para la sensibilidad de detección
El número de ocho horas es una manta que da seguridad. Lo que importa es si tu ejecución puede ver pendiente en las métricas que tienen fugas: el suelo del montón posterior a la GC, la profundidad del grupo, el recuento de descriptores de archivo, la duración de las pausas de la GC, en formas de carga y tareas programadas que reflejen lo que producción hace en realidad. Diseña para la sensibilidad de detección. Si tu última ejecución aprobó y producción aun así tuvo fugas, la siguiente no necesita ser más larga. Necesita observar lo correcto, con la forma correcta y con la política de reinicios correcta. Cuando estés listo para construir una que sí detecte la fuga, contáctanos y la diseñaremos contigo.
Preguntas frecuentes
¿Cuánto debería durar una prueba de resistencia?
Lo suficiente para distinguir el menor crecimiento de recursos por hora que importaría en producción de las fluctuaciones de la recolección de basura. Si una pendiente del montón de 2 MB por hora tumbaría tu servicio en una semana de tiempo de actividad, tu ejecución debe durar lo suficiente para detectar esa pendiente por encima del ruido con una regresión lineal sobre el suelo posterior a la GC. Eso suele ser más de 8 horas y a menudo menos de 72.
¿Cuál es la diferencia entre las pruebas de resistencia (soak testing) y las pruebas de aguante (endurance testing)?
Son la misma práctica con dos nombres. Algunos equipos y proveedores prefieren «aguante» (endurance) y otros prefieren «resistencia» (soak); el desacuerdo es regional y estilístico, no técnico. Ambos significan una carga sostenida durante un periodo prolongado, observando la acumulación.
¿Por qué mi prueba de resistencia aprobó pero producción tuvo una fuga de memoria?
Casi siempre por una de cuatro razones: la ejecución fue más corta que el tiempo de duplicación de la fuga, la carga era plana mientras que producción tiene una forma diurna, la ejecución nunca ejecutó una tarea programada que tiene fugas, o el entorno de prueba reinició un proceso que el orquestador habría mantenido con vida en producción. Arregla el diseño, no la duración.
Descubre cómo QAwerk mantuvo estables 8 portales con mucho contenido bajo el tráfico sostenido de 110 millones de estudiantes al año, antes de que Keystone escalara aún más.