Por Performate
Cómo encontrar cuellos de botella en API más rápido con reportes de pruebas de carga estructurados
Convierte métricas k6 y requests con tag en una narrativa de cuello de botella—cliente vs red vs app vs datos—sin adivinar desde un solo gráfico.
Tu dashboard muestra p99 al alza y throughput plano, y tres ingenieros señalan tres subsistemas distintos. Un cuello de botella es el recurso más lento limitado en el path que tu escenario realmente ejercita—no el servicio con más CPU en abstracto. Las pruebas de carga muestran síntomas; métricas k6 etiquetadas más un orden de triage estructurado convierten esos síntomas en “revisa el pool”, “arregla el índice” o “el escenario está mal”.
En esta guía verás cómo validar primero la fidelidad del escenario, separar delay cliente vs servidor con tags k6, rankear rutas por contribución a la cola, mapear firmas de saturación a subsistemas y exportar evidencia accionable—sin repetir corridas a ciegas.
Por qué un solo gráfico no nombra el cuello de botella
http_req_duration agrega cada ruta, clase de status y reintento. Bajo carga, modos de fallo se superponen:
- Errores al alza, latencia estable suele ser auth, validación, cuotas o feature flags—not saturación de CPU.
- Errores estables, latencia explosiva suele ser pools de threads/conexiones o backpressure en colas.
- Errores planos, colas crecientes en una ruta suele ser hot path, índice faltante o dependencia downstream.
El workbook SRE de Google encuadra SLIs en lo que percibe el usuario (implementing SLOs); k6 da la capa de medición cuando las requests llevan tag de ruta y dependencia (métricas).
Antes del diagnóstico profundo, confirma que el escenario no miente—errores comunes en pruebas de carga. Alinea fidelidad del entorno y cómo leer reportes de pruebas de carga cuando los tags estén listos.
Implementación práctica en k6: tags, umbrales y ranking por ruta
Instrumenta cada familia de requests con tags para partir http_req_duration y http_req_failed por ruta.
Script de ejemplo (ilustrativo—no listo para producción). Paths y SLO ficticios.
Qué demuestra este ejemplo:
- Varias rutas en un escenario con funciones
execseparadas y claves de tag consistentes. - Umbrales por ruta para que un health check rápido no oculte la cola de checkout.
- Checks que separan 4xx (cliente/config) de 5xx (servidor/saturación) al extender el script.
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE = __ENV.API_BASE || 'https://staging.example.com';
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${__ENV.TOKEN}`,
};
export const options = {
scenarios: {
api_mix: {
executor: 'ramping-arrival-rate',
startRate: 5,
timeUnit: '1s',
preAllocatedVUs: 30,
maxVUs: 120,
stages: [
{ duration: '2m', target: 20 },
{ duration: '5m', target: 40 },
{ duration: '2m', target: 0 },
],
exec: 'mixedJourney',
},
},
thresholds: {
'http_req_duration{route:search}': ['p(95)<400', 'p(99)<700'],
'http_req_duration{route:checkout}': ['p(95)<900', 'p(99)<1400'],
'http_req_failed{route:checkout}': ['rate<0.02'],
http_req_failed: ['rate<0.01'],
},
};
export function mixedJourney() {
const search = http.get(`${BASE}/v1/search?q=load`, { headers, tags: { route: 'search' } });
check(search, { 'search 2xx': (r) => r.status >= 200 && r.status < 300 });
const cart = http.post(`${BASE}/v1/cart/items`, JSON.stringify({ sku: 'A1', qty: 1 }), {
headers,
tags: { route: 'cart' },
});
check(cart, { 'cart 2xx': (r) => r.status >= 200 && r.status < 300 });
const checkout = http.post(`${BASE}/v1/checkout`, JSON.stringify({ cartId: 'c-1' }), {
headers,
tags: { route: 'checkout' },
});
check(checkout, { 'checkout 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.4);
}
Patrones que funcionan
- Ordenar rutas etiquetadas por
p95/p99—si una domina la cola, acotas código y dependencias (p95 vs p99). - Comparar tiempos de iteración k6 con trazas de servidor cuando la política lo permita.
- Documentar el executor (
constant-vusvs arrival-rate) para reproducir el “cuello” (escenarios). - Exportar JSON de resumen con git SHA y parámetros del escenario en cada ticket.
Antipatrones a evitar
- Declarar “la base está lenta” desde una línea global sin tags de ruta.
- Rampar VUs cuando el producto mide requests por segundo—saturación falsa en el cliente.
- Abrir tickets de infra sin ADN del escenario—reruns incorrectos.
Pro tip (comando de ejemplo): stats de cola en el resumen CLI para war rooms.
k6 run api-mix.js --summary-trend-stats="p(95),p(99),max"
Qué demuestra este comando: tendencias de percentiles y máximo por grupo de tags muestran qué ruta rompió la cola antes de abrir APM.
Marco de decisión: síntoma → capa probable
| Ventana de señal | Suele implicar | Siguiente chequeo |
|---|---|---|
| Subida gradual de latencia, pocos errores | Agotamiento de pool, GC, profundidad de cola | Métricas de pool, thread dumps, lag de broker |
| Caída abrupta de latencia | Circuit breaker, throttling, deploy | Logs de gateway, contadores de rate limit |
| Picos periódicos | Cron, eviction de caché, batch | Anotar schedules; segmentar intervalos k6 |
| Errores arriba, latencia plana | Auth, validación, cuota | Desglose de status por tag de ruta |
Una ruta domina p99 | Handler caliente, N+1, índice faltante | Trace de esa ruta; pruebas de contrato vs rendimiento |
Detén y corrige el escenario si la forma de tráfico, think time o estado de caché no refleja producción.
Escala a datos si solo lecturas profundas o rutas de paginación divergen.
Observabilidad, documentación y próximos pasos
Antes del próximo war room de rendimiento:
- Etiquetar cada familia de requests con
route(ydependencyal llamar downstreams). - Registrar executor, duración, objetivos RPS/VU y fingerprint de entorno.
- Rankear rutas por contribución a
p99; adjuntar snapshots de las tres primeras a tickets. - Correlacionar timestamps de picos k6 con APM y slow queries.
- Especificar el siguiente experimento por ticket (índice, tamaño de pool, TTL)—no “investigar lentitud”.
- Re-ejecutar con el mismo ADN del escenario tras cada fix.
Cómo Performate acelera el triage de cuellos de botella
Hojas de cálculo distintas tras cada corrida frenan decisiones. Un export compartido con desglose por ruta alinea ingeniería y producto.
Ejemplo: triage de un journey search → cart → checkout
- Importar la colección Postman que refleja el path real. Problema resuelto: una definición de journey en lugar de tres scripts huérfanos.
- Crear un escenario ramping arrival-rate con la forma RPS del pico de la semana pasada. Problema resuelto: saturación honesta sin adivinar VUs (cuántos usuarios virtuales).
- Poner tags por request en el panel:
route:search,route:cart,route:checkout. Problema resuelto: cortes del reporte alineados al modelo de umbrales k6. - Ejecutar y abrir la vista de comparación—filtrar por tag y ordenar por
p99. Problema resuelto: la cola de checkout visible aunque search se vea bien. - Adjuntar umbrales por ruta para que las regresiones fallen fuerte en checkout antes de que search derive. Problema resuelto: gates alineados a SLIs visibles al usuario.
- Exportar resumen + script k6 para smoke en CI tras los fixes.
Ese flujo cumple el cta: aislar cuellos más rápido y alinear al equipo en correcciones con el mismo reporte estructurado.
Cierre
El análisis de cuellos de botella en API es triage ordenado: valida el escenario, etiqueta rutas, rankea colas, mapea firmas a subsistemas y ticketea evidencia. El recurso más lento está en el path que ejercitaste; hazlo medible.
Ejecuta esta semana tu journey mixto con tags de ruta y resúmenes por percentil—anota qué ruta única posee el p99 que nombra tu SLO.
¿Listo para optimizar el rendimiento de tu API?
Usa los reportes de Performate para aislar cuellos de botella más rápido y alinear al equipo en correcciones accionables.