Por Qué Importa el Performance Testing de API

Toda aplicación moderna depende de APIs. Cuando una API se vuelve lenta, toda la experiencia de usuario se degrada — las páginas tardan más en cargar, las apps móviles se congelan y las integraciones generan timeouts. El performance testing de API asegura que tus endpoints puedan manejar el tráfico esperado y los picos sin degradar la experiencia del usuario.

A diferencia del performance testing de UI, el testing de rendimiento de API aísla el backend. Eliminas el rendering del navegador, la variabilidad de red y el código frontend de la ecuación. Esto te da mediciones precisas de cómo tu servidor procesa las solicitudes.

Métricas Clave de Rendimiento

Antes de escribir una sola prueba, necesitas entender qué estás midiendo.

Throughput (Requests Per Second)

El throughput mide cuántas solicitudes tu API puede manejar por segundo. Un endpoint REST que retorna perfiles de usuario podría sostener 5,000 RPS en un servidor bien optimizado. Si tu tráfico pico esperado es 2,000 RPS, tienes margen cómodo.

Percentiles de Latencia

La latencia promedio es engañosa. Si el 95% de las solicitudes toman 50ms pero el 5% toman 3 segundos, el promedio podría ser 200ms — lo cual oculta la terrible experiencia de ese 5%.

Usa percentiles en su lugar:

PercentilSignificado
p50 (mediana)La mitad de las solicitudes son más rápidas que esto
p90El 90% de las solicitudes se completan dentro de este tiempo
p95El umbral que la mayoría de SLAs apuntan
p99Captura la experiencia de casi-peor-caso

Tasa de Errores

El porcentaje de solicitudes que retornan errores 5xx o timeout bajo carga. Una API saludable mantiene menos del 0.1% de tasa de errores en carga esperada. A medida que la carga aumenta más allá de la capacidad, la tasa de errores se dispara — este punto de inflexión revela tu verdadera capacidad.

Concurrencia

El número de conexiones simultáneas que tu API maneja. Esto difiere del throughput: una API podría manejar 1,000 RPS con 50 conexiones concurrentes (respuestas rápidas) o 1,000 RPS con 500 conexiones concurrentes (respuestas lentas).

Tipos de Pruebas de Rendimiento de API

Load Test

Simula el tráfico esperado de producción. Valida que el rendimiento cumple con los SLAs bajo condiciones normales.

// Ejemplo de load test con k6
export const options = {
  stages: [
    { duration: '2m', target: 100 },  // ramp up
    { duration: '5m', target: 100 },  // estado estable
    { duration: '2m', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

Stress Test

Empuja más allá de la carga esperada para encontrar puntos de quiebre. Incrementa usuarios virtuales continuamente hasta que la API se degrada o falla.

Spike Test

Simula picos repentinos de tráfico — como una venta flash o un post viral en redes sociales. Prueba cómo la API maneja saltos instantáneos en tráfico y se recupera después.

Soak Test

Ejecuta carga moderada durante horas (4-12+). Revela memory leaks, agotamiento del pool de conexiones, crecimiento de archivos de log y otros problemas que solo aparecen con el tiempo.

Configurando k6 para Testing de API

k6 es ideal para performance testing de API porque es liviano, programable en JavaScript y proporciona métricas detalladas de forma nativa.

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<300', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get('https://api.example.com/users');

  check(res, {
    'status es 200': (r) => r.status === 200,
    'tiempo de respuesta < 500ms': (r) => r.timings.duration < 500,
    'body contiene data': (r) => r.json().data !== undefined,
  });

  sleep(1);
}

Escenarios Multi-Endpoint

El tráfico real impacta múltiples endpoints. Modela esto ponderando solicitudes proporcionalmente a los patrones de tráfico de producción.

import http from 'k6/http';
import { group, sleep } from 'k6';

export default function () {
  group('Navegar Productos', () => {
    http.get('https://api.example.com/products');
    sleep(0.5);
  });

  group('Ver Detalle de Producto', () => {
    const id = Math.floor(Math.random() * 100) + 1;
    http.get(`https://api.example.com/products/${id}`);
    sleep(0.3);
  });

  group('Búsqueda', () => {
    http.get('https://api.example.com/search?q=widget');
    sleep(0.5);
  });
}

Interpretando Resultados

Después de una ejecución de k6, obtienes un resumen como este:

http_req_duration..........: avg=120ms  min=15ms  med=95ms  max=4200ms  p(90)=250ms  p(95)=380ms
http_req_failed............: 0.23%  ✓ 46  ✗ 19954
http_reqs..................: 20000  666.5/s

Qué buscar:

  1. p95 vs threshold — ¿380ms está dentro de tu SLA de 500ms? Sí, pasas.
  2. Brecha p95 vs promedio — 380ms vs 120ms promedio. La latencia de cola es 3x el promedio, sugiriendo que algunas solicitudes toman rutas lentas.
  3. Tasa de errores — 0.23% puede parecer bajo, pero si tu SLA requiere < 0.1%, fallas.
  4. Latencia máxima — 4,200ms significa que al menos una solicitud tomó más de 4 segundos. Investiga por qué.

Ejercicio: Suite Completa de Performance Testing de API

Construye una prueba de rendimiento completa para una API REST con estos requisitos:

Configuración

Usa la API pública JSONPlaceholder (https://jsonplaceholder.typicode.com) o configura una API local.

Parte 1: Prueba de Línea Base

Crea un script de k6 que establezca el rendimiento base:

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Trend, Rate, Counter } from 'k6/metrics';

// Métricas personalizadas
const listLatency = new Trend('list_latency');
const detailLatency = new Trend('detail_latency');
const createLatency = new Trend('create_latency');
const errorRate = new Rate('errors');
const requestCount = new Counter('total_requests');

export const options = {
  scenarios: {
    baseline: {
      executor: 'constant-vus',
      vus: 10,
      duration: '2m',
    },
  },
  thresholds: {
    list_latency: ['p(95)<500'],
    detail_latency: ['p(95)<300'],
    create_latency: ['p(95)<800'],
    errors: ['rate<0.01'],
  },
};

export default function () {
  // GET /posts (lista)
  let res = http.get('https://jsonplaceholder.typicode.com/posts');
  listLatency.add(res.timings.duration);
  check(res, { 'lista 200': (r) => r.status === 200 }) || errorRate.add(1);
  requestCount.add(1);
  sleep(0.5);

  // GET /posts/:id (detalle)
  const id = Math.floor(Math.random() * 100) + 1;
  res = http.get(`https://jsonplaceholder.typicode.com/posts/${id}`);
  detailLatency.add(res.timings.duration);
  check(res, { 'detalle 200': (r) => r.status === 200 }) || errorRate.add(1);
  requestCount.add(1);
  sleep(0.3);

  // POST /posts (crear)
  res = http.post('https://jsonplaceholder.typicode.com/posts',
    JSON.stringify({ title: 'Test', body: 'Contenido', userId: 1 }),
    { headers: { 'Content-Type': 'application/json' } }
  );
  createLatency.add(res.timings.duration);
  check(res, { 'crear 201': (r) => r.status === 201 }) || errorRate.add(1);
  requestCount.add(1);
  sleep(0.5);
}

Parte 2: Load Test con Ramping

Extiende la línea base para simular tráfico similar al de producción:

export const options = {
  scenarios: {
    load_test: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 50 },
        { duration: '5m', target: 50 },
        { duration: '1m', target: 100 },
        { duration: '3m', target: 100 },
        { duration: '2m', target: 0 },
      ],
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<1000'],
    http_req_failed: ['rate<0.05'],
  },
};

Parte 3: Spike Test

Simula un aumento repentino de tráfico:

export const options = {
  scenarios: {
    spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 20 },
        { duration: '10s', target: 200 },
        { duration: '2m', target: 200 },
        { duration: '10s', target: 20 },
        { duration: '2m', target: 20 },
        { duration: '30s', target: 0 },
      ],
    },
  },
};

Tareas de Análisis

Después de ejecutar cada prueba:

  1. Registra la tabla de métricas — Copia p50, p90, p95, p99 y latencia máxima para cada endpoint.
  2. Compara línea base vs carga — ¿Cuánto aumentó p95 cuando agregaste más usuarios virtuales?
  3. Analiza el spike — ¿Mantuvo la API latencia aceptable durante el spike? ¿Cuánto tardó en regresar a la línea base después de que bajó el spike?
  4. Identifica el cuello de botella — ¿Qué endpoint se degradó más rápido bajo carga? ¿Por qué podría ser? (Pista: los endpoints de lista típicamente consultan más datos que los de detalle.)

Observaciones Esperadas

  • Los endpoints de lista (GET /posts) probablemente mostrarán mayor latencia que los de detalle (GET /posts/:id) porque retornan payloads más grandes.
  • Las solicitudes POST típicamente tienen mayor latencia porque involucran operaciones de escritura.
  • Durante el spike test, deberías observar un aumento de latencia, luego estabilización o degradación adicional si la API no puede manejar la carga.
  • Después de que el spike baja, la latencia debería regresar a niveles cercanos a la línea base — si no lo hace, la API tiene un problema de recuperación.

Checklist de Performance Testing

Usa este checklist antes de declarar una API lista para producción:

  • Latencia base registrada para todos los endpoints críticos
  • Load test pasa los thresholds del SLA en tráfico pico esperado
  • Stress test identifica el punto de quiebre (qué RPS causa degradación)
  • Spike test confirma recuperación dentro de tiempo aceptable
  • Soak test (4+ horas) no muestra memory leaks ni degradación gradual
  • Tasa de errores se mantiene por debajo del threshold del SLA en carga esperada
  • Pools de conexiones a base de datos no se agotan bajo carga
  • Llamadas a APIs de terceros tienen timeouts y circuit breakers