Por Performate
SSE y long-polling bajo carga: patrón práctico de prueba con k6
Prueba en carga SSE y long-polling: streams concurrentes, timeouts idle de proxy y patrones k6—MDN, HTTP/1.1 y comparación con WebSockets.
Los dashboards que usan SSE o long-polling y se cuelgan bajo streams concurrentes fallan de forma distinta a las ráfagas REST: dominan los timeouts idle del proxy, los topes de conexión y el buffer bloat (MDN SSE, HTTP keep-alive). Un pico de GETs cortos no dice nada sobre diez mil streams abiertos detrás del mismo balanceador.
k6 puede abrir conexiones HTTP de larga duración; hay que modelar conteo de streams concurrentes y duración de requests largos, no solo RPS de GETs de menos de un segundo. En esta guía verás modos de fallo, un patrón de VUs concurrentes con timeouts extendidos y cuándo comparar con patrones de carga WebSocket.
Modos de fallo bajo streams concurrentes
Las UIs orientadas a eventos y los feeds en vivo estresan infraestructura que los benchmarks REST no tocan:
- El proxy cierra conexiones idle a los 60 s mientras el servidor cree que el stream sigue abierto: la tormenta de reconexiones amplifica la carga.
- Agotamiento de descriptores de archivo en gateways e ingress: los errores parecen 502 aleatorios.
- Fan-out: un evento del servidor dispara N polls de clientes o fetches downstream—multiplica la carga más allá del conteo de VUs de k6.
- Buffer bloat en consumidores lentos: la memoria sube en intermediarios mientras k6 sigue recibiendo 200.
- Límites de reutilización de conexión HTTP/1.1 por host—el conteo de VUs no es igual al de pestañas del navegador, pero la dirección es similar.
El long-polling difiere ligeramente: GETs largos repetidos por ciclo de VU en lugar de un stream infinitamente abierto—modela ambos si el producto usa patrones híbridos.
SSE vs WebSocket vs long-poll
| Patrón | Modelo de carga | Nota k6 |
|---|---|---|
| SSE | Muchos GET abiertos concurrentes | Accept: text/event-stream, timeout largo |
| Long-poll | GET largos repetidos por VU | Bucle más corto con timeout cerca del hold del servidor |
| WebSocket | Frames bidireccionales | Ver guía WebSocket k6; casos avanzados pueden requerir xk6 |
Patrón k6: GET largo con timeout extendido
Ilustrativo—no listo para producción. El cliente HTTP de k6 lee el body completo por defecto; para validación de parser SSE de grado producción considera herramientas complementarias o extensiones xk6. Este patrón sigue estresando tablas de conexión y timeouts de proxy.
Qué demuestra este ejemplo:
constant-vusequivale a streams abiertos concurrentes—ajustaSTREAM_VUSa viewers en vivo esperados, no a RPS REST.timeoutextendido enhttp.get—120 s de ejemplo; alinea con hold de long-poll del servidor + margen.- Header
Accept: text/event-stream—misma ruta que usan los navegadores. - Tags
protocol:sseseparan umbrales de escenarios REST en suites mixtas.
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE = __ENV.API_BASE || 'https://staging.example.com';
export const options = {
scenarios: {
sse_clients: {
executor: 'constant-vus',
vus: Number(__ENV.STREAM_VUS || 100),
duration: '5m',
tags: { protocol: 'sse' },
},
rest_baseline: {
executor: 'constant-arrival-rate',
rate: 20,
timeUnit: '1s',
duration: '5m',
tags: { protocol: 'rest' },
},
},
thresholds: {
http_req_failed: ['rate<0.02'],
'http_req_duration{protocol:sse}': ['p(95)<5000'],
'http_req_duration{protocol:rest}': ['p(95)<400'],
},
};
export default function () {
const res = http.get(`${BASE}/events/stream`, {
headers: { Accept: 'text/event-stream' },
timeout: '120s',
tags: { route: 'stream', protocol: 'sse' },
});
check(res, { connected: (r) => r.status === 200 });
sleep(Number(__ENV.RECONNECT_SEC || 5));
}
Patrones que funcionan
- Rampa de VUs por stages—encuentra la rodilla del límite de conexiones antes del conteo completo de viewers.
- Alinear timeouts idle de proxy/load balancer en el runbook—si el proxy mata a 60 s, no uses timeout de 120 s sin esperar churn de reconexión.
- Monitorizar FDs abiertos en el gateway durante la prueba—aborta si la pendiente es lineal.
- Escenarios REST y SSE separados—no mezcles umbrales; los modos de fallo difieren.
Anti-patrones a evitar
- Medir solo health REST corto mientras el dolor en producción son streams concurrentes.
- Poner VUs igual a números de RPS REST—los streams son conexiones concurrentes, no req/s.
- Ignorar tormentas de reconexión tras timeout de proxy—la segunda ola puede superar la primera.
- Correr el conteo completo de viewers en producción sin control de cambios.
Pro tip (comando de ejemplo): rampa VUs de stream para encontrar la rodilla.
k6 run sse-load.js --env STREAM_VUS=50 --duration 3m --tag protocol=sse
Qué demuestra este comando: pruebas incrementales de VUs en escritorio localizan límites de FD del gateway antes de programar el ensayo completo de 5k streams con platform on-call.
Marco de decisión: patrón vs modelo de carga
| Patrón | Modelo de carga | Métrica principal |
|---|---|---|
| SSE push | Muchos GET abiertos concurrentes | VUs concurrentes + errores de stream |
| Long-poll | GET largos repetidos por VU | Tiempo de ciclo + tasa de timeout |
| REST + SSE mixto | Escenarios separados | No mezclar p95 |
| Fan-out backend | Mock downstream o monitor | Profundidad de broker/cola si aplica |
Corre escenario REST baseline en paralelo—demuestra que la API sigue sana mientras los streams están abiertos. Algunos equipos solo rompen REST cuando la tabla de conexiones está llena.
Observabilidad y checklist pre-corrida
- Timeouts idle de proxy/load balancer documentados en el runbook.
- Monitorizar FDs abiertos y conteo de conexiones en el gateway durante la prueba.
- Abortar si la tasa de error sube en los primeros 10 minutos—no quemar staging ciegamente.
- Coordinar con el equipo de red—ingress compartido puede afectar servicios no relacionados.
- Anotar limitaciones HTTP de k6 para parsing de eventos—documentar si la prueba es solo conexión vs validación de payload.
- Comparar resultados con la ruta WebSocket si el producto evalúa migración.
Cómo Performate apoya pruebas SSE/long-poll
Abajo hay un ejemplo concreto de flujo para carga del endpoint de stream—adapta conteos de VU a tu pico de viewers concurrentes.
Ejemplo: rampa de VUs de stream con export para networking
- Importa el endpoint de stream desde Postman—headers incluyendo
Accept. Problema resuelto: la misma ruta que QA usa funcionalmente se convierte en objetivo de carga. - Configura escenario VU + timeout largo en la UI—120 s o según runbook. Problema resuelto: evita editar strings de timeout a mano en cada iteración.
- Corre rampa por stages en VUs—50, 100, 200 con tripwires de abort. Problema resuelto: encuentra la rodilla antes del ensayo a escala completa.
- Exporta timeouts y errores de conexión filtrados por
protocol:sse. Problema resuelto: el equipo de plataforma recibe desglose por código de estado, no un genérico «lento». - Comparte el export con platform—captura de FDs con el mismo timestamp. Problema resuelto: el ticket de capacidad enlaza la prueba de app con evidencia de infra.
- Guarda plantilla para release—retag
protocol:ssepor evento. Problema resuelto: repite antes de cada lanzamiento de dashboard en vivo.
Ese flujo encaja con el cta de este post: escenarios de conexión larga ejecutables sin semanas de glue code.
Cierre
La carga SSE/long-poll es un problema de presupuesto de conexiones: rampa streams concurrentes, alinea timeouts con reglas de proxy y nunca mezcles umbrales SSE con baselines REST.
Programa una rampa de VUs esta semana en staging, observa métricas de conexión del gateway durante la corrida y anota el conteo de VU donde empiezan los errores—ese número pertenece al checklist de lanzamiento.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para convertir este playbook en escenarios k6 ejecutables, umbrales y reportes compartibles sin perder días en código de integración.