De romper a reparar: Usando pruebas de rendimiento para fortalecer tu sistema. ❤️‍🩹

Andrea Fuentes
Monoku
Published in
9 min readOct 6, 2023

En el desarrollo de software hay un área importante que nos apasiona a algunos, pero que lamentablemente es relegada o ignorada por muchos, sea por la cultura de la empresa o porque no han despertado aún su curiosidad. Estoy hablando del aseguramiento de la calidad. ¿Qué es esto?

Imagen generada en Midjourney-Bot, prompt: woman developer repairing software as ilustration — ar 7:4

El mundo de las pruebas y el aseguramiento de la calidad busca evaluar un sistema en diferentes enfoques, desde pruebas unitarias que verifican el correcto funcionamiento de una función, hasta pruebas de punto a punto que pueden estar evaluando la comunicación entre diferentes sistemas.

Es fundamental definir el objetivo de lo que desees probar, considerar el requerimiento de tu sistema y entender las expectativas de uso. Lamentablemente el área de las pruebas es enorme y no se puede abarcar todo en un artículo, por ahora te contaré que existen muchos tipos, hoy me enfocaré en hablar sobre load testing, también llamadas pruebas de carga, esperando que apartir de aquí despiertes un poco tu curiosidad por adentrarte en este mundo.

El desastre del día sin IVA podría haber sido evitado

Contexto: En Colombia durante pandemia del COVID-19, hubo un evento de ofertas en línea similares al blackfriday, llamado “día sin IVA” (donde el IVA se refiere al Impuesto al Valor Agregado).

A nivel nacional, muchos productos estaban exentos de este impuesto. Esta promoción llevó a que numerosos colombianos colapsaran los sitios web con sus visitas. Lamentablemente, muchos de estos sitios no estaban preparados para el masivo flujo de usuarios, lo que resultó en interrupciones y caídas de servicio por varias horas debido a la falta de recursos para gestionar la inesperada carga.

Imagen generada en Midjourney-Bot, prompt: Angry shoppers faced virtual stores that were down, leaving frustration in their wake as ilustration — ar 7:4

¿Esto se pudo evitar? Definitivamente, después de ese incidente evidenciamos la implementación de “estrategias” para regular la cantidad de ususarios simultáneos como “turnos” o “filas virtuales”. Esta solución pudo ser propuesta desde el primer evento si se hubieran llevado a cabo pruebas de carga y escalabilidad, permitiendo anticipar y preparar el sistema para el limite de usuarios concurrentes evitando fallos en ambientes productivos.

Pruebas de carga

Una prueba es la evaluación de características mínimas deseadas de algo, o sea en otras palabras “la expectativa del sistema” o “el mínimo requerido para un correcto funcionamiento”, ¿Suena complejo? lo que te trato de explicar es que el software que escribes debería tener un comportamiento deseado bajo ciertos escenarios, y para eso, lo sometemos a distintas pruebas para confirmar si actúa según lo esperado.

Aunque muchos sitios describen las pruebas de distintas maneras, todas coinciden en que quieren evaluar el sistema mediante una simulación. En mis palabras las pruebas de carga son las que nos permiten evaluar cómo se “comporta” o “funciona” mi sistema bajo condiciones como cierta cantidad usuarios accediendo al mismo tiempo o un número determinado de solicitudes, todo esto utilizando herramientas especializadas.

Te preguntarás: ¿Para qué deseo saber esto? Por lo que comentaba, puedes anticiparte, planear los pasos a seguir bajo ciertos escenarios. Por ejemplo, si tu sistema recibe más solicitudes de las que puede manejar, ¿cuál sería el paso a seguir? Implementar una fila virtual es una opción. Pero dependiendo de los resultados de las pruebas se puede conocer cuándo el sistema debe escalar sea horizontal o verticalmente.

Caso practico: Weather service

Manos a la obra, para poder entender mejor el tema de las pruebas de carga he creado un servicio que se encargará de darnos la información del clima actual de una ciudad. Este servicio se conectará a un proveedor (un servicio remoto) para obtener esta información.

El repositorio: https://github.com/AFU92/weather-service

Herramientas que usaré para las pruebas

Jmeter

url: https://jmeter.apache.org/
Escogí esta herramienta de código abierto porque es muy utilizada para pruebas de carga en la industria ya que nos permite simular múltiples escenarios y usuarios para evaluar el rendimiento de una aplicación. Adicional, al ser una aplicación de Java, no es necesario su instalación sino que podemos descargar el binario directamente de la página de apache y ejecutarlo como cualquier otra app de Java o utilizando los archivos ejecutables (.sh) que trae incluídos.
En mi caso estoy trabajando en un sistema operativo macOs e instalé mediante brew con el comando:

brew install jmeter

Para los escenarios de pruebas que vamos a desarrollar más adelante utilizaremos las siguientes funcionalidades de Jmeter:

  • Plan de prueba (Test Plan): Es un archivo o documento estructurado (.xml) que abarca toda la configuración de las pruebas, detalla los objetivos y los pasos específicos para ejecutar una prueba. Este plan en mi caso se llama “Weather Service Load Test”.
  • Grupo de hilos (Thread group): Representan a múltiples usuarios realizando las mismas acciones sobre el servicio a probar. Pueden existir varios grupos de hilos y pueden correr de manera simultánea o consecutiva.
  • Solicitud HTTP (HTTP request): En este caso ejecutaré una solicitud HTTP de tipo GET ya que el servicio a probar es RestFul.
  • Oyentes (Listeners): Componentes en JMeter que te permiten ver y analizar los resultados de la ejecución de una prueba.
    - View Results Tree: Muestra los datos de solicitud y respuesta de cada muestra.
    - Summary Report: Proporciona una visión general de los detalles de la solicitud y respuesta, incluyendo promedios, medianas y porcentajes de error.
    - Aggregate Graph: Representación gráfica de los tiempos de respuesta agregados de todas las solicitudes.
    - Response Time Graph: Un gráfico que visualiza los tiempos de respuesta de las solicitudes durante la duración de una prueba.
Configuración del grupo de hilos (Thread group configuration)

Mockserver

- url: https://www.mock-server.com/
Cuando realizamos pruebas de carga y el servicio que queremos probar se conecta a otros servicios o dependencias externas lo deseable o común es utilizar mocks que simulen el comportamiento normal de esos otros servicios para evitar enviar la carga a los servicios reales. En este caso, utilizaré MockServer para mockear el servicio externo que esta utilizando mi weather-service.
En este caso usé docker para correrlo dentro de un contenedor:


docker run -d — name mockserver -p 1080:1080 mockserver/mockserver

Y adicional use el comando para enviar el mock del servicio externo que estoy utilizando a mockserver en la terminal:

curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/your-path"
},
"httpResponse": {
"statusCode": 200,
"body": {
"your": "response"
}
}
}'v

Escenarios de Carga Progresiva

El propósito de este caso práctico es evaluar la capacidad y el rendimiento de nuestro weather service mediante la simulación de diferentes cargas de usuarios. Mediante la aplicación progresiva de escenarios de carga, se buscará identificar posibles cuellos de botella, limitaciones de recursos y puntos de fallo.

1. Escenario: Carga Básica

Objetivo: Evaluar la respuesta del sistema bajo una carga base o nominal.
Configuración:
- Usuarios concurrentes: 100
- Duración: 10 minutos
- Tiempo de ramp-up: 2 minutos
Resultado Esperado: Se anticipa que la aplicación maneje este nivel de carga sin mostrar signos de tensión, con tiempos de respuesta consistentes y sin errores significativos.
Resultado obtenido: La aplicación logró manejar la carga enviada ya que no hubo respuestas fallidas (como se evidencia en el listener View Results Tree). Sin embargo, los tiempos de respuesta fueron muy superiores a los esperados encontrando que sí hubo tensión en la aplicación (como se evidencia en el Response Time Graph).

Response Time Graph
Aggregate Graph
Summary Report
View Results Tree

Propuesta de mejora

Durante el escenario 1 se encontró que la actual implementación del servicio no es suficiente para los resultados esperados. Por esto, y dado que nuestro servicio depende de un servicio externo que nos da información que no cambia tan repentinamente, se propone agregar una solución de cache con un tiempo inicial de 10 minutos para evitar el llamado reiterado cuando los clientes del servicio piden la misma información reiteradamente, buscando mejorar el tiempo de respuesta y comprobarlo repitiendo escenario 1 y de ser exitoso mantenerlo para los escenarios 2 y 3.

Resultado obtenido repetición escenario 1: Con la mejora aplicada del cache para evitar solicitudes reiteradas sobre el servicio externa, se logró mejorar los tiempos de respuesta a los niveles esperados para el escenario 1, reduciendo los tiempos de respuesta a aproximadamente 500 ms 🎉🎉

Response Time Graph
Aggregate Graph
Summary Report
View Results Tree

2. Escenario: Carga Media

Objetivo: Determinar cómo el sistema se comporta cuando se incrementa la carga a un nivel medio, lo cual simula un incremento típico en el uso.
Configuración:
- Usuarios concurrentes: 150
- Duración: 15 minutos
- Tiempo de ramp-up: 3 minutos
Resultado Esperado: Puede que se observe un aumento en los tiempos de respuesta, pero la aplicación aún debería funcionar correctamente sin errores críticos. Es una oportunidad para identificar si hay algún recurso que se esté agotando o acercando a sus límites.
Resultado Obtenido: Se cumple con el resultado esperado, se incrementaron los tiempos de respuesta a 800ms pero se mantuvo estable y proceso adecuadamente todos los requests.

Response Time Graph
Aggregate Graph
Summary Report
View Results Tree

3. Escenario: Carga Pico

Objetivo: Estresar la aplicación a su máxima capacidad, llevándola a su límite para entender cuáles son sus puntos de ruptura.
Configuración:
- Usuarios concurrentes: 200
- Duración: 20 minutos
- Tiempo de ramp-up: 4 minutos
Resultado Esperado: Bajo esta carga extrema, es probable que se observe un aumento significativo en los tiempos de respuesta y potencialmente algunos fallos. El objetivo es identificar los puntos de quiebre y entender cómo el sistema se comporta bajo el máximo estrés.
Resultado Obtenido: Se cumple con el resultado esperado, se incrementaron los tiempos de respuesta a 1800ms, algunos requests no fueron procesados y fallaron.

Response Time Graph
Aggregate Graph
Summary Report
View Results Tree

Conclusiones

Al finalizar los tres escenarios de carga progresiva, obtuve información sobre el comportamiento y la capacidad de respuesta del weather service bajo diferentes niveles de demanda:

  • Durante las pruebas, el ambiente de desarrollo (mi equipo), mostró limitaciones, en un ambiente productivo, donde van a existir más recursos dedicados, mi servicio podría beneficiarse del escalamiento vertical debido a que implicaría incrementar la potencia de la máquina, como memoria, CPU y/o almacenamiento. Para manejar una carga de usuarios más grande, se espera al tener más recursos el servicio pueda atender a más usuarios simultáneamente sin degradar el rendimiento o la calidad.
  • Es necesario considerar el escalamiento horizontal, hago referencia a añadir más instancias (o servidores) para distribuir la carga de trabajo y mejorar la capacidad de respuesta. A partir de los resultados, sugiero que cuando se observe que los usuarios concurrentes superen los 150 o cuando la latencia del servicio exceda los 800ms, lo más adecuado sería introducir más instancias. Esto permitiría manejar una mayor cantidad de usuarios y adicional a ofrecer tiempos de respuesta más consistentes y rápidos.
  • Debido a que las pruebas se llevaron a cabo en mi ambiente de desarrollo, o sea un equipo de uso personal debemos considerar que los resultados me brindan información sobre cómo se comportaría el servicio bajo ciertas condiciones. En la práctica, un ambiente productivo con capacidades de escalamiento reales podría presentar comportamientos diferentes.
  • Basándome en las pruebas y resultados obtenidos, es muy importante planificar una estrategia de escalamiento adecuada para el ambiente productivo. Las decisiones sobre si escalar vertical u horizontalmente deben basarse en métricas específicas y objetivas, considerando las características y demandas propias del negocio como las del servicio o aplicación.

En conclusión, las pruebas me proporcionaron la información para planear y preparar mi servicio para un lanzamiento exitoso en producción permitiéndome tener la capacidad de escalarlo adecuadamente, sea vertical u horizontalmente, para que el servicio o aplicación pueda manejar las demandas futuras de manera eficiente con el objetivo de garantizar una experiencia de usuario óptima. 💙

--

--

Andrea Fuentes
Monoku
Writer for

Ingeniera de sistemas, administradora de empresas. Soy backend developer y fui QA.