Cómo hacer pruebas de carga para APIs

Cómo hacer pruebas de carga para APIs
Gerónimo
Gerónimo
Fractional CTO
21 min de lectura

Si alguna vez has desarrollado una aplicación web destinada a manejar un volumen significativo de usuarios, te habrás encontrado ante la cuestión de determinar qué recursos necesita tu aplicación para manejar el tráfico esperado con los níveles de servicio deseados. Las pruebas de rendimiento y carga permiten conocer cómo se comportará tu aplicación en diferentes escenarios de uso respondiendo a las siguientes preguntas:

  • ¿Cuántos recursos (CPU, RAM, disco, ancho de banda, GPU, etc.. ) consume mi aplicación para procesar el tráfico?

  • ¿Qué infraestructura necesito para dar respuesta al tráfico esperado?

  • ¿Cómo de rápida es mi aplicación?

  • ¿Cuán rápido escala mi infraestructura? ¿Qué impacto tiene el escalado en los usuarios?

  • ¿El rendimiento es estable o se degrada con el tiempo?

  • ¿Cómo de resiliente es mi aplicación frente a picos de tráfico?

  • ¿Cómo cambia el rendimiento de mi aplicación con las nuevas funcionalidades que se van introduciendo?

El objetivo de este post es dar herramientas para responder a estas preguntas para tu aplicación. Para ello en la primera parte hacemos una introducción teórica sobre los tests de rendimiento y carga, para después pasar a una segunda parte práctica, donde veremos cómo hacer las pruebas de carga sobre una API de un proyecto de ejemplo.

Estrategia de testing (Parte teórica)

Antes de abordar la creación del plan de pruebas hay que analizar cómo está definida la API y el uso que se le va a dar en producción, para determinar cómo hacer las pruebas.

En este caso concreto vamos a testear la API de una aplicación web para un Blog. Esta API está compuesta por varios endpoints para la gestión de usuarios y de los posts. Consideraciones:

  • Como cada endpoint tiene una lógica de negocio diferente es de esperar que su rendimiento y el throughput soportados sean diferentes.

  • Las pruebas deben hacerse en base a los números de usuarios esperados para nuestra aplicación. No tiene sentido probar con 100k usuarios concurrentes, si seguramente nuestra aplicación no pase de 100.

  • Primero es recomendable probar cada endpoint de forma separada, para entender los límites y capacidades de cada uno de forma aislada. Y como siguiente paso, probar con patrones de llamadas (a varios endpoints) que reproduzcan casos de uso reales (Por ejemplo, un usuario hace login, luego lista los posts, recupera 2 o 3 posts concretos, etc..).

  • Si la aplicación todavía no está en producción, tendremos que crear los tests en base a las hipótesis que creamos pueden representar la realidad. Una vez que la aplicación se despliegue en producción, podremos utilizar los datos de uso real para actualizar los tests.

  • Si el sistema dispone de autoescalado, el test permitirá probar el autoescalado y entender la velocidad del mismo, los errores entre eventos de auto-escalado, etc.. En este caso concreto la aplicación que vamos a probar no tiene autoescalado.

Qué medir

  • Uso de recursos: CPU, RAM, tráfico de red, …

  • Latencia de las llamadas: la latencia de la llamada (incluyendo la respuesta) a cada endpoint de la API. Hay diferentes formas de medir la latencia, normalmente se suelen utilizar percentiles. Por ejemplo, si el p95 (o percentil 95) es de 85ms, quiere decir que el 95% de las llamadas se completan en como máximo 85ms.

  • Throughput, medido como el número de llamadas a un endpoint por segundo (RPS o requests-per-second) que acepta el backend bajo distintas condiciones.

  • Error rate o número de llamadas que se responden con error frente al número de llamadas totales.

Tipos de pruebas de rendimiento

Dentro de las pruebas de performance se distinguen diferentes tipos en función de la carga o el volumen de datos. El objetivo es representar las diferentes situaciones de uso que puede experimentar nuestro sistema en producción:

  • Pruebas baseline o average. Sirven para entender cómo se comporta la API ante un número de llamadas que consideramos normal. En nuestro ejemplo, consideraremos que en condiciones normales tendremos 20 usuarios concurrentes haciendo peticiones a la API.

  • Pruebas de carga (Load testing). Nos permiten entender cómo responde la API cuando se producen los picos de uso esperados. Por ejemplo, si hemos calculado que en la hora punta del día tendremos 50 usuarios haciendo peticiones a la API, el test reproducirá un patrón de tráfico que alcanza ese pico.

  • Pruebas de stress (Stress testing). Sirven para entender la capacidad máxima de nuestra API (medida en RPS, requests por segundo) con la infraestructura actual, cómo se comporta el auto escalado (en caso de tenerlo) y cómo se degrada el rendimiento (la latencia) y aumenta la tasa de errores (en caso de tener una capacidad fija), según aumenta el tráfico. Algunos ejemplos de situaciones que podrían llevar a un sistema al estrés serían un Black Friday en un e-commerce o un evento deportivo en un servicio de reparto de comida.

  • Pruebas de pico (Spike testing). Permiten ver cómo se comporta el sistema ante un pico esporádico en el tráfico. Y ver qué número de llamadas podemos responder correctamente y cuántas con error. También permiten comprobar si el servicio es resiliente y se mantiene activo o por el contrario se cae. En el mundo real estos picos pueden producirse por diferentes motivos, como por ejemplo, si alguien popular escribe un tweet referenciando nuestra web y se producen muchas visitas de forma simultánea. (es lo que también se conoce como “HackerNews hug of death”).

  • Pruebas de durabilidad (Soak testing). Permiten entender el comportamiento de la API en periodos prolongados de tiempo, que pueden durar días o semanas. Pretenden comprobar si existen degradaciones en el sistema que puedan afectar al servicio a medio-largo plazo. Por ejemplo, en el caso de que tengamos un pequeño memory leak en la aplicación, podrían pasar días hasta consumir toda la RAM asignada, lo cual seguramente provocaría un reinicio del servicio y el fallo de todas las llamadas que estuviese atendiendo el servicio en ese momento (y las llamadas que se produjeran en el periodo de tiempo hasta que el servicio esté levantado de nuevo, si no hay otras instancias del servicio disponibles para absorber el tráfico).

Objetivos y plan (Parte práctica)

Por brevedad nos centraremos en los tests de baseline y los tests de stress. Sin embargo, no es complicado diseñar pruebas de carga y de durabilidad a partir de la información que compartiremos.

Por no extendernos demasiado en el post, los tests consistirán en probar un único endpoint (listado de posts). Dejando al lector la tarea de ampliar y adaptar los tests a su aplicación y casos de uso concretos.

El plan será el siguiente:

  1. Descripción del stack y proyecto de pruebas
  2. Descripción del entorno
  3. Despliegue del entorno
  4. Baseline Testing
  5. Stress Testing
  6. Notas finales

1 — Descripción del stack y proyecto de pruebas

Para la ejecución del tests hemos decidido crear un backend en GO que implementa una API Rest sencilla y que utiliza una base de datos MariaDB para la persistencia de los datos. El código junto con las instrucciones para desplegarlo pueden encontrarse aquí:

https://github.com/gerodp/blog-sample-backend-go-grafana

**Nota: **se trata de un backend de prueba para uso exclusivamente ilustrativo y que no está pensado para utilizarse en entornos productivos.

Por otro lado, hemos creado otro repositorio con los diferentes tests, que explicamos a continuación:

https://github.com/gerodp/blog-sample-perf-test-k6-grafana

Stack

  • API Rest Backend en GO con las librerías GIN (HTTP Server) y GORM (ORM)
  • Load testing tool: Grafana K6.
  • Monitorización del sistema con** Prometheus y Grafana**
  • Base de datos MariaDB
  • Ejecución y orquestación con Docker y Docker Compose

Sobre Grafana K6 — Load Testing Tool

K6 es una solución Open Source desarrollada por Grafana Labs. La hemos seleccionado porque es fácil de usar, soporta todos los tipos de test de performance, tiene una buena integración con las herramientas de monitorización Grafana y Prometheus, y tiene una documentación excelente en varios idiomas, incluyendo el español.

Sin embargo hay una gran número de herramientas de este tipo que también se podrían utilizar, como por ejemplo: JMeter, Locust, Taurus, Artillery u otros.

2 — Descripción del entorno

Por simplicidad, desplegaremos el backend que implementa la API en un EC2 de AWS. Este tipo de despliegue es muy sencillo y no está pensado para entornos productivos; en esos casos lo recomendable es explorar otras soluciones como un despliegue en un clúster u otras alternativas.

En el README del repositorio podemos leer los pasos para instalarlo en un EC2 o máquina Linux compatible.

3— Despliegue del entorno

Como parte de la prueba tenemos 2 componentes diferenciados: 1) el backend que implementa la API y 2) los tests de carga de K6

Despliegue del backend que implementar la API

1 — Lanzamos un AWS EC2 con Linux. En nuestro caso lo hemos probado en una t3.large y m5.large con Ubuntu, pero otras instancias más pequeñas valen perfectamente (lo único que tardará un poco más en arrancar el backend por la compilación en Go). Y tendremos que asignarle una dirección IP pública para acceder desde fuera.

2 — En el Security Group añadimos 2 reglas de tráfico inbound a los puertos 22 (SSH) y 9494 (puerto donde se expone la API) desde nuestra IP de casa o la de la máquina donde lanzaremos las pruebas de K6.

3 — Nos conectamos via SSH a la máquina y ejecutamos los siguientes comandos (en el README):

https://github.com/gerodp/blog-sample-backend-go-grafana#deployment-in-aws-ec2

4 — Podemos abrir un túnel SSH para acceder a Grafana con este comando:

ssh -i "/path/to/pemfile" -N ubuntu@<PUBLIC\_IP> -L 8800:localhost:3000

5 — Y nos dirigimos a http://localhost:8800 con un navegador para abrir Grafana.

Despliegue de los tests

Los tests los podemos ejecutar en nuestra máquina directamente, si el sistema que vamos a testear es pequeño o lanzar una infraestructura dedicada, cuando el sistema sea más grande. K6 tiene un Operator de Kubernetes que permite esto. Para este post los lanzaremos desde nuestra máquina por simplicidad (En este caso un Mac con chip M1).

1- Clonamos el repositorio:

https://github.com/gerodp/blog-sample-perf-test-k6-grafana

2- Lanzamos un test ejecutando este comando con make:

API\_URL=http://EC2\_IP\_PUBLICA:9494 TEST=testfile.js make start

El comando levanta varios contenedores:

  • Un contenedor con K6 que ejecuta el test especificado en testfile.js, y se queda escuchando a cambios en ese fichero para relanzarlo cada vez que se guarde
  • Un contender con Prometheus y otro con Grafana para la monitorización

3- Abrimos Grafana en un navegador visitando http://localhost:3000

4- Podemos terminar la ejecución de los contenedores de test, ya que los lanzaremos de nuevo en la siguiente sección, con un test específico.

Nota: Como vemos, tenemos 2 instancias de Grafana, 1 para la API (con las métricas de uso de recursos del backend de la API )y otra para los Tests (con las métricas que genera K6 sobre la ejecución de los tests: throughputs, latencias, error rates, etc..). De aquí en adelante nos referiremos a cada instancia de Grafana especificando si es la de Tests o la de API.

4 — Baseline Testing

Vamos a suponer que en condiciones normales nuestro Blog tendrá entorno a 20 usuarios concurrentes los cuales hacen una llamada a la API de listado de posts por segundo (esto es un escenario ficticio, ya que el patrón de tráfico real será probablemente diferente, pero nos sirve a modo ilustrativo).

Si nos dirigimos al repositorio con los tests de K6 y abrimos el fichero:

Veremos el siguiente contenido:

import http from "k6/http";  
import { check, sleep } from "k6";  
  
export const options = {  
 thresholds: {  
   http\_req\_failed: \['rate<0.01'\], // http errors should be less than 1%  
   http\_req\_duration: \['p(95)<200'\], // 95% of requests should be below 200ms  
 },  
 scenarios: {  
   read\_posts\_constant: {  
     executor: 'constant-arrival-rate',  
  
     // Our test should last 10 minutes in total  
     duration: '600s',  
  
     // It should start 20 iterations per \`timeUnit\`. Note that iterations starting points  
     // will be evenly spread across the \`timeUnit\` period.  
     rate: 20,  
  
     // It should start \`rate\` iterations per second  
     timeUnit: '1s',  
  
     // It should preallocate 55 VUs before starting the test  
     preAllocatedVUs: 55,  
  
     // It is allowed to spin up to 80 maximum VUs to sustain the defined  
     // constant arrival rate.  
     maxVUs: 80,  
   }  
 }  
};  
  
//This function runs only once per Test  
//and performs a login  
export function setup() {  
 let loginParams = { username: 'testint1', password: 'testint1'};  
  
 let loginRes = http.post(\_\_ENV.SERVICE\_URL+'/login', JSON.stringify(loginParams), {  
   headers: { 'Content-Type': 'application/json' },  
 });  
 check(loginRes, {  
   "status is 200": (r) => r.status == 200,  
 });  
  
 return { token: loginRes.json().token };  
}  
  
  
export default function(data) {  
 const token = data.token;  
  
 let resp = http.get(\_\_ENV.SERVICE\_URL+"/auth/post?page\_size=5",{  
   headers: {  
     'Content-Type': 'application/json',  
     'Authorization': 'Bearer ' + token,  
    },  
 });  
  
 check(resp, {  
   "status is 200": (r) => r.status == 200,  
 });  
  
 sleep(1);  
}

No vamos a entrar a explicar todos los detalles del código, ya que se pueden consultar en la excelente documentación de k6. Pero vamos a comentar las partes más importantes:

thresholds: {  
   http\_req\_failed: \['rate<0.01'\], // http errors should be less than 1%  
   http\_req\_duration: \['p(95)<200'\], // 95% of requests should be below 200ms  
 },

K6 permite definir nuestros SLOs (Service level objectives) directamente en el código. Para nuestra prueba hemos fijado que queremos que la latencia percentil 95 sea menor de 200ms y que la tasa de error sea menor del 1%. En caso de que no se cumpla alguno de los SLOs K6 lo indicará en el reporte de resultados que muestra al finalizar la ejecución de la prueba.

 executor: 'constant-arrival-rate',  
  
 // Our test should last 10 minutes in total  
 duration: '600s',  
  
 // It should start 20 iterations per \`timeUnit\`. Note that iterations starting points  
 // will be evenly spread across the \`timeUnit\` period.  
 rate: 20,  
  
 // It should start \`rate\` iterations per second  
 timeUnit: '1s',

Como queremos un rate de llamadas constante por segundo, elegimos este executor que garantiza que se inician ‘rate’ iteraciones por ‘timeUnit’. Para saber más acerca de los executors disponibles podemos visitar la documentación de K6.

Para lanzarlo ejecutamos el siguiente comando:

API\_URL=http://EC2\_IP\_PUBLICA:9494 TEST=baseline\_test.js make start

Después de 10 minutos obtendremos el resultado del test en los logs:

La línea de ‘checks’ en el reporte nos dice que el 100% de los chequeos han pasado.

‘http_req_duration’ nos muestra la latencia de las llamadas (medida desde el cliente de K6, como nota indicar que la latencia será altamente proporcional a la distancia entre la máquina donde se ejecuta el cliente de K6 que lanza el test y la máquina donde corre el sistema que se está testeando). El p(095) o percentil 95 es de 135.53ms lo que quiere decir que el 95% de las llamadas se han completado en como máximo ese tiempo. Lo cual está por debajo del SLO que nos habíamos marcado de p95<200ms. Por otro lado la latencia media es de 105.28ms.

Si nos vamos al Grafana de los Tests (http://localhost:3000) y abrimos el dashboard de Test Result, podemos ver las curvas con los RPS, Active VUs (Virtual Users de K6) y el Response Time medio (latencia media)

Como se observa los RPS (curva naranja discontinua) se han mantenido en 20 RPS, como lo habíamos especificado en el test. El Average Response Time (curva verde) si bien es cierto que a simple vista muestra un mayor cambio al principio luego se vuelve más o menos constante, la diferencia entre el máximo y mínimo valor de la media es menor del 10% lo cual está en línea a los esperado. En general es de esperar pequeñas variaciones en las mediciones puesto que hay muchos factores que pueden afectar a los tiempos, desde la carga del sistema en el momento de la prueba hasta el estado de la red.

Por otro lado, podemos ver que no se han producido errores, o lo que es lo mismo que el error rate ha sido del 0%, lo cual implica que hemos cumplido con el otro SLO que establecía que la tasa de errores debía estar por debajo del 1% de las llamadas.

Hasta aquí todo parece indicar que la API se comporta de la manera esperada, sin errores y con una latencia de respuesta constante. Lo cual no es concluyente de que no pueda haber otros problemas que puedan afectar a futuro. Para ello nos interesa comprobar que el uso de recursos CPU, RAM de los servicios es adecuado y no se muestran tendencias ascendentes que podrían señalar un problema futuro.

Si nos dirigimos al dashboard de grafana de Cadvisor exporter encontramos las métricas de uso de recursos. En la primera gráfica se puede observar el consumo de CPU para los diferentes servicios. Como se ve el uso de CPU es muy bajo para ambos servicios, con un pequeño incremento al comienzo del test, y luego se estabiliza

El uso de memoria es de 18.4 MB para el backend y 96.9 para MariaDB, y las curvas se mantienen planas.

Con estos checks parece que el sistema se comporta de forma estable en cuanto a rendimiento, y sin indicadores de que pueda producirse un problema con 20 usuarios concurrentes y el patrón de uso definido en el test. De todas formas, de cara a tener una mayor certeza de que el sistema se comporta de forma estable en periodos más prolongados de tiempo, lo recomendable es hacer un soak testing.

Dependiendo de las tecnologías que utilicemos podemos ampliar la monitorización a más métricas. Por ejemplo si nuestra base de datos es MySQL o MariaDB podemos usar un exporter de Prometheus que expone gran cantidad de métricas, desde el número de conexiones activas hasta la latencia de las queries, y son útiles para conocer más en detalle la carga del sistema. En esta página podemos encontrar una lista detallada de exporters de prometheus para la colección de métricas en función de tecnología.

A continuación pasaremos a hacer pruebas de estrés para ver cómo se comporta la API cuando la carga es mucho mayor.

5 — Stress Testing

Como hemos observado en el test anterior con 20 usuarios el sistema se comporta de la forma esperada. Podemos empezar con 25 RPS, por ejemplo, e ir subiendo la carga a 50, 100, 150, …. K6 soporta varias formas de hacer esto, pero para entender cómo hacerlo, antes tenemos que introducir algunos conceptos sobre K6.

K6 Virtual Users (VUs) e Iteraciones

En K6 cada Virtual User consiste en un while loop que ejecuta en bucle la función de test que hemos definido en el fichero de K6. Cada ejecución del bucle es lo que se llama una iteración. Si definimos 10 VUs tendremos 10 bucles while ejecutando iteraciones en paralelo. Un VU solo ejecuta una iteración de forma concurrente, y la siguiente iteración no comienza hasta que acabe la anterior. **Por lo tanto el número de iteraciones que puede ejecutar un VU viene definido por lo rápido o lento que responda el sistema que se está testeando. Esto quiere decir que si en nuestro test queremos reproducir un número N de usuarios tendremos que utilizar un tipo de executor de VUs y si lo que queremos es reproducir un número N de RPS (requests per second o llamadas por segundo) lo ideal es utilizar un tipo de executor de iteraciones, que adaptará dinámicamente el número de VUs para alcanzar las iteraciones especificadas en el test. **K6 disponibiliza distintos tipos de executors, que determinan cómo se ejecutan los tests. Para saber más acerca de los executors disponibles podemos visitar la documentación de K6.

Para este caso concreto vamos a usar el executor **Ramping Arrival Rate que permite especificar el número de iteraciones a ejecutar por unidad de tiempo, ya que nos interesa conocer la capacidad de nuestro sistema con respecto a las RPS. **Como en nuestro test sólo hacemos una llamada, el número de iteraciones será equivalente a los RPS.

Vamos a hacer que cada escalón sea de 60 segundos de subida más 60 segundos con rate estable. Es importante tener en cuenta cada cuanto Prometheus muestrea los datos, si el test es demasiado rápido, Prometheus no será capaz de captar bien las métricas y los cambios.

Escalón 1: 0 -> 25 RPS.

Para conseguir 25 RPS en 60 segundos necesito ejecutar 25*60 iteraciones -> 1500 iteraciones

Escalón 2: 25 -> 50 RPS -> 50*60 = 3000 iteraciones

etc..

Si nos dirigimos al repositorio con los tests de K6 y abrimos el fichero:

import http from "k6/http";  
import { check, sleep } from "k6";  
  
export const options = {  
  thresholds: {  
    http\_req\_failed: \['rate<0.01'\], // http errors should be less than 1%  
    http\_req\_duration: \['p(95)<200'\], // 95% of requests should be below 200ms  
  },  
  scenarios: {  
    read\_posts\_stress\_test: {  
      executor: 'ramping-arrival-rate',  
  
      // Our test with at a rate of 50 iterations started per \`timeUnit\` (e.g minute).  
      startRate: 25,  
  
      // It should start \`startRate\` iterations per second  
      timeUnit: '1m',  
  
      // It should preallocate 300 VUs before starting the test.  
      preAllocatedVUs: 300,  
  
      // It is allowed to spin up to 1500 maximum VUs in order to sustain the defined  
      // constant arrival rate.  
      maxVUs: 5000,  
  
      stages: \[  
        { target: 25\*60, duration: '1m' },  
        { target: 25\*60, duration: '2m' },  
        { target: 50\*60, duration: '1m' },  
        { target: 50\*60, duration: '2m' },  
        { target: 100\*60, duration: '1m' },  
        { target: 100\*60, duration: '2m' },  
        { target: 150\*60, duration: '1m' },  
        { target: 150\*60, duration: '2m' },  
        { target: 25\*60, duration: '3m' },  
      \],  
    }  
  }  
};  
  
//This function runs only once per Test  
export function setup() {  
  let loginParams = { username: 'testint1', password: 'testint1'};  
  
  let loginRes = http.post(\_\_ENV.SERVICE\_URL+'/login', JSON.stringify(loginParams), {  
    headers: { 'Content-Type': 'application/json' },  
  });  
  check(loginRes, {  
    "status is 200": (r) => r.status == 200,  
  });  
  
  return { token: loginRes.json().token };  
}  
  
export default function(data) {  
  
  const token = data.token;  
  
  let resp = http.get(\_\_ENV.SERVICE\_URL+"/auth/post?page\_size=5",{  
    headers: {   
      'Content-Type': 'application/json',  
      'Authorization': 'Bearer ' + token,  
     },  
  });  
  
  check(resp, {  
    "status is 200": (r) => r.status == 200,  
  });  
  
  sleep(1);  
}

Para lanzarlo ejecutamos el siguiente comando:

API\_URL=http://EC2\_IP\_PUBLICA:9494 TEST=stress\_test.js make start

Después de 15 minutos obtendremos el resultado del test en los logs:

Como se puede ver en los resultados en la última línea indica que algunos de los thresholds (con los SLOs) definidos han fallado. La latencia p95 es de 28,4s que está muy por encima a los 200ms marcados como objetivo. Sin embargo el error rate de 0,27% se ha mantenido por debajo del 1% fijado como objetivo.

Si abrimos el dashboard de Test Result en Grafana, podemos ver en la gráfica la evolución de la latencia según aumentan los RPS.

Si hacemos zoom sobre la gráfica de API Call Response Time vemos el momento en el que la latencia p95 sobrepasa el umbral de 200ms (línea roja). Si observamos el valor de RPS en la otra gráfica sobre ese instante de tiempo vemos que está alrededor de las 22 RPS. Lo que quiere decir que con la infraestructura actual no deberíamos sobrepasar este límite si queremos cumplir con el SLO de latencia.

Sin embargo, pasado este punto, el sistema sigue funcionando sin errores (aunque con latencia elevada) hasta alcanzar alrededor de las 115 RPS, que empiezan a producirse algunos errores.

Otra cosa que podemos observar es que los RPS máximos alcanzados están entorno a los 138, aunque nuestro objetivo era llegar a los 150 RPS. Esto se produce porque la latencia aumenta tanto que el número máximo de VUs no es suficiente para alcanzar los RPS especificados. Aquí podríamos aumentar el número de VUs máximo en el test, pero deberíamos tener en cuenta que la máquina desde donde se lance el test tenga suficiente capacidad para soportarlo. En un sistema más grande se podrían lanzar los tests desde un cluster y escalar el número de contenedores. En cualquier caso, con las RPSs alcanzadas nos es suficiente para ver que el sistema empieza a degradarse y devolver llamadas con error, aunque no se caiga. Y cuando el tráfico vuelve a niveles normales, el sistema vuelve a responder dentro de los umbrales fijados.

Por otro lado, si abrimos el dashboard de Grafana de la API, podemos comprobar que la memoria y la CPU han aumentado con respecto a lo observado en el test de baseline, pero aún a cargas altas las curvas se mantienen planas, lo cual indica que no tenemos problemas de memory leaks o similares para la funcionalidad testeada.

Como posibles siguientes pasos, el lector podría investigar dónde se encuentran los cuellos de botella que hacen que la latencia aumente a partir de unas RPS y por qué se producen los errores que hacen que la API no responda con código 200.

6-Notas finales

En este post hemos introducido los conceptos teóricos de cómo definir una estrategia de pruebas de carga y rendimiento, y después hemos mostrado un par de ejemplos de cómo realizar algunas de estas pruebas sobre una aplicación que implementa una API. Aunque nos hemos centrado en APIs, estas pruebas son extrapolables a otros sistemas cuya carga sea variable.

Esperamos que el lector sienta que tiene más herramientas para responder a las preguntas que planteabamos al inicio:

  • ¿Cuántos recursos (CPU, RAM, disco, ancho de banda, GPU, etc.. ) consume mi aplicación para procesar el tráfico?

  • ¿Qué infraestructura necesito para dar respuesta al tráfico esperado?

  • ¿Cómo de rápida es mi aplicación?

  • ¿Cuán rápido escala mi infraestructura? ¿Qué impacto tiene el escalado en los usuarios?

  • ¿El rendimiento es estable o se degrada con el tiempo?

  • ¿Cómo de resiliente es mi aplicación frente a picos de tráfico?

  • ¿Cómo cambia el rendimiento de mi aplicación con las nuevas funcionalidades que se van introduciendo?

El tipo de pruebas que implementemos para nuestro sistema final dependerá mucho de la naturaleza del sistema, el uso esperado y su infraestructura.

Sobre mi

  • Los últimos 4 años he estado trabajando como CTPO de una startup que desarrolla productos basados en IA y computer vision para varios sectores como el retail, la construcción, el media y el transporte.

  • He participado en el diseño de diferentes productos, su desarrollo y puesta en producción para clientes en 8 países en Europa, Oriente Medio y Estados unidos.

  • He liderado la escalabilidad y transformación del equipo, pasando de un grupo inicial de 4 ingenieros a un departamento de 25 profesionales, incluidos desarrolladores, data scientists, devops y customer success managers.

  • Previamente he trabajado como lead architect y software engineer en diferentes clientes (Vodafone, LaLiga, Orange - Optiva Media) y companías grandes, como Amadeus.

  • Actualmente trabajo como Fractional CTO ayudando a startups y empresas en cualquier fase, que tengan necesidades y problemas con la estrategia de tecnología y producto, la productividad de los equipos y la promoción de ingenieros en puestos de gestión.

Si te interesa explorar como un Fractional CTO te puede ayudar en aspectos concretos de tu empresa/startup, puedes reservar una llamad

api Blog performance perftest tests