Por Performate
Correlation IDs y trazabilidad distribuida en pruebas de carga: corridas k6 depurables
Propaga headers de contexto de traza desde k6, alinea con spans del backend y depura colas de latencia más rápido.
Tu prueba soak muestra p99 de checkout a 2,4 segundos durante una hora—pero cuando on-call abre el dashboard APM, cada span parece tráfico anónimo desde 10.0.0.0. Sin contexto de traza, los fallos bajo carga se vuelven arqueología: grep en logs, adivinar números de VU y esperar que alguien guardó el timestamp correcto.
Los picos de latencia se vuelven accionables cuando cada iteración de k6 emite traceparent y baggage opcional compatibles con OpenTelemetry (W3C Trace Context). En esta guía verás por qué los correlation IDs cambian la respuesta a incidentes bajo carga, cómo generar headers de traza válidos en k6, y qué checks de observabilidad demuestran que tu generador y backend acuerdan la misma traza.
Por qué las pruebas de carga necesitan contexto de traza—no solo tags
Los tags de k6 separan métricas por ruta y escenario; el APM separa spans por trace ID. Sin un puente entre ambos, optimizas a ciegas:
- Tráfico anónimo: los backends agrupan generadores en una cuenta de servicio—los spans carecen del contexto de iteración que necesitas.
- La cola de latencia oculta causalidad: picos p99 pueden ser waits de BD, saltos de mesh o cachés frías—los tags solos no los distinguen (análisis de cuellos de botella).
- Los soak corren largo: fugas de memoria y acumulación en colas aparecen tras decenas de minutos; necesitas trace IDs duraderos por familia de request (playbook soak).
- Flujos multi-salto: checkout puede tocar auth, inventario y pago—un correlation ID debe coser el grafo.
Piensa en los headers de traza como códigos de barras en cada request sintético: los escáneres downstream deben leer el mismo ID que imprimió tu herramienta de carga.
Cuando las métricas dicen lento pero los logs no dicen nada
Los equipos suelen añadir X-Request-Id manualmente mientras el service mesh espera traceparent W3C. Los gateways eliminan headers desconocidos; el APM muestra trazas parciales. Alinea con el estándar que exporta tu plataforma—Jaeger, Tempo, Datadog y New Relic ingieren contexto W3C cuando está configurado (tags de observabilidad).
Implementación práctica con k6: traceparent W3C por iteración
Genera un trace ID fresco por request (o por iteración de VU en flujos multi-paso), formatea traceparent correctamente y pasa baggage opcional con metadata de carga como vu e iter. El middleware del backend debe continuar la traza—no iniciar un root span nuevo en silencio.
Ejemplo de script (ilustrativo—no listo para producción). El fragmento usa URLs, tokens y números SLO ficticios. Adapta base URL, auth, payloads y umbrales a tu entorno.
Qué demuestra este ejemplo:
- traceparent W3C válido: versión
00, trace ID hex de 32 chars, span ID hex de 16 chars, flag sampled01. - Baggage para metadata de carga:
vu,iteryscenariopropagan cuando tu collector soporta baggage W3C. - Header de correlación legacy:
X-Correlation-Idopcional para servicios aún sin OpenTelemetry. - Métricas etiquetadas + trace IDs: tags de ruta k6 para resúmenes; trace IDs para flame graphs APM en la misma corrida.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { randomBytes } from 'k6/crypto';
const BASE = __ENV.API_BASE || 'https://staging.example.com';
const TOKEN = __ENV.TOKEN || 'staging-token-replace-me';
function hex(bytes) {
return Array.from(new Uint8Array(bytes))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
function traceparent() {
const traceId = hex(randomBytes(16)); // 32 hex chars
const spanId = hex(randomBytes(8)); // 16 hex chars
return `00-${traceId}-${spanId}-01`;
}
function correlationId() {
return `k6-${__VU}-${__ITER}-${hex(randomBytes(4))}`;
}
function traceHeaders() {
const tp = traceparent();
const cid = correlationId();
return {
traceparent: tp,
tracestate: 'k6=load',
baggage: `vu=${__VU},iter=${__ITER},scenario=checkout`,
'X-Correlation-Id': cid,
};
}
export const options = {
scenarios: {
traced_checkout: {
executor: 'constant-arrival-rate',
rate: Number(__ENV.CHECKOUT_RPS || 8),
timeUnit: '1s',
duration: '10m',
preAllocatedVUs: 10,
maxVUs: 40,
tags: { route: 'checkout', trace: 'w3c' },
exec: 'checkoutTraced',
},
},
thresholds: {
'http_req_duration{route:checkout}': ['p(95)<900', 'p(99)<1400'],
http_req_failed: ['rate<0.01'],
},
};
export function checkoutTraced() {
const body = JSON.stringify({ sku: `SKU-${__VU}`, qty: 1 });
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${TOKEN}`,
...traceHeaders(),
};
const res = http.post(`${BASE}/checkout`, body, {
headers,
tags: { route: 'checkout', trace: 'w3c' },
});
check(res, { 'checkout 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.5);
}
Patrones que funcionan
- Una traza por journey de usuario cuando los flujos llaman auth y luego checkout—reutiliza traceparent entre pasos en la misma iteración.
- Filtra APM por baggage
vu=para saltar de outliers del resumen k6 a flame graphs exactos. - Depura PII del baggage antes de exportar trazas a tenants compartidos (datos de prueba GDPR).
- Combina con sampling específico de env para que collectors de staging retengan 100% de trazas de carga durante investigaciones (variables de entorno).
Anti-patrones a evitar
- Reutilizar un
X-Request-Idestático para todos los VUs—colapsa miles de requests en una traza lógica. - Enviar
traceparentmal formado (hex de longitud incorrecta)—el APM descarta contexto en silencio. - Loguear trace IDs completos en artefactos CI públicos cuando las trazas contienen payloads de clientes.
- Ignorar sidecars de mesh que sobrescriben headers de traza salvo que inyectes en el salto correcto.
Pro tip (comando de ejemplo): la línea siguiente exporta JSON de resumen k6 junto a una corrida que correlacionarás manualmente en APM—no es flag obligatorio en cada soak.
k6 run traced-checkout.js --summary-export=run-summary.json --tag git_sha=$GIT_SHA
Qué demuestra este comando: los resúmenes exportados incluyen tags de escenario y git SHA—pega timestamps de ventanas lentas en búsqueda APM con baggage git_sha o marcadores de deploy coincidentes.
Marco de decisión: request ID vs traza W3C completa
| Situación | Acción recomendada |
|---|---|
| Monolito con solo correlación en logs | X-Correlation-Id + logs estructurados; migra a W3C cuando llegue APM |
| OpenTelemetry / mesh ya desplegado | traceparent + baggage en cada request k6; verifica retención del collector |
| Flujos multi-paso checkout/auth | Un trace ID por iteración en todas las llamadas HTTP del default function |
| Soak largo buscando fugas | Traza única por request; filtra outliers p99 en APM por ventana temporal + tag de ruta |
| Datos regulados en payloads | Minimiza baggage; depura IDs de logs exportados (datos GDPR) |
Usa traceparent W3C si tu backend o service mesh ya exporta OpenTelemetry—es el default en plataformas API modernas.
Usa headers de correlación legacy si servicios antiguos solo loguean X-Request-Id—pero planifica migración antes de que headers duales confundan a on-call.
Usa metadata en baggage si tu collector lo soporta y necesitas mapear __VU/__ITER de k6 a spans sin adivinar timestamps.
Observabilidad, documentación y próximos pasos
La propagación de trazas solo ayuda cuando collectors, retención y runbooks coinciden. Antes de escalar tráfico:
- Verifica que APM de staging retenga trazas durante todo el soak—no solo los primeros quince minutos.
- Documenta qué headers pasan los gateways; listas de strip han bloqueado
traceparentantes. - Añade paso en runbook: «filtrar trazas por ventana temporal
route:checkout, abrir trace IDs del peor p99». - Confirma que IPs del generador están allowlisted sin forzar todos los spans a un nodo de servicio anónimo.
- Archiva JSON de resumen k6, git SHA y tags de escenario por corrida para que postmortems enlacen métricas y trazas.
Cómo Performate simplifica pruebas de carga alineadas a trazas
Escribir headers de traza a mano en cada script exportado deriva cuando alguien copia una plantilla vieja sin baggage. Abajo un ejemplo concreto de flujo para checkout trazado bajo soak.
Ejemplo: corridas etiquetadas que mapean a APM
- Importa requests de checkout desde Postman u OpenAPI—el mismo flujo que soakas por fugas de memoria. Problema resuelto: una definición de request; sin bloques HTTP duplicados «trazado» vs «sin trazar».
- Añade headers custom en el panel de escenario: notas placeholder de
traceparentytracestate=k6=loadestático donde documentas estándares—or exporta y añade el helper una vez. Problema resuelto: los revisores ven intención de headers antes del export, no enterrada en git. - Aplica tags
route:checkoutytrace:w3cen el escenario para que resúmenes k6 coincidan con filtros APM. Problema resuelto: compara colas de métricas y de trazas con el mismo vocabulario. - Corre soak de 10 minutos y exporta reporte integrado más script k6 para CI. Problema resuelto: comparte un enlace con on-call que incluye tags de escenario y fallos de umbral.
- Pega timestamps de ventana lenta en APM y busca por correlation ID o baggage
vu=de la peor iteración. Problema resuelto: minutos hasta causa raíz en lugar de arqueología en logs. - Re-exporta tras cambios de headers para que CI y desktop queden alineados—especialmente cuando la plataforma mejora soporte baggage W3C.
Ese flujo mapea al cta: corridas etiquetadas donde los correlation IDs mapean limpio a trazas APM.
Conclusión
Las pruebas de carga sin contexto de traza optimizan agregados, no incidentes. Propaga traceparent W3C, añade baggage seguro con metadata k6 y verifica que tu mesh reenvíe headers antes del próximo soak.
Corre un soak de checkout trazado esta semana—filtra APM por el peor minuto p99 y confirma que puedes abrir un flame graph de un checkout sintético sin adivinar números de VU.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para compartir corridas etiquetadas donde los correlation IDs mapean limpio a trazas APM.