Regresar
501

WebSockets vs Server-Sent Events vs Polling

Actualizado: 13/09/2026

Cuando una aplicación necesita reflejar cambios en tiempo real, un mensaje nuevo, el estado de un pedido, una notificación, hay tres formas distintas de resolverlo. La IA suele proponer siempre la misma sin preguntar cuál encaja con tu caso, y casi siempre propone la más compleja de las tres.

Las tres funcionan. La diferencia está en la latencia que necesitas, en si el cliente también envía datos o solo los recibe, y en cuánta complejidad operativa estás dispuesto a sostener.

Latencia, dirección e infraestructura de cada mecanismo

Antes del detalle, el cuadro completo. La columna de infraestructura es la que más se subestima al decidir.

PollingSSEWebSockets
DirecciónCliente preguntaServidor envíaAmbos, en cualquier momento
Latencia típicaLa mitad del intervaloCasi inmediataCasi inmediata
ProtocoloHTTP normalHTTP normalProtocolo propio tras un upgrade
ReconexiónNo aplicaAutomática del navegadorLa programas tú
Proxies y balanceadoresSin cambiosSin cambiosRequieren configuración
Varios servidoresTrivialTrivialNecesita capa de mensajería
Costo de quitarlo despuésBajoBajoAlto

Las pestañas de abajo resumen cada mecanismo. El detalle de implementación viene después, en su propia sección.

Tres columnas que comparan la direccion de la conexion: en polling el navegador pregunta una y otra vez al servidor; en SSE el servidor envia hacia el navegador cuando hay novedades, en un solo sentido sobre HTTP normal; en WebSockets ambos hablan en cualquier momento, de forma bidireccional
La pregunta que decide: ¿el cliente necesita enviar continuamente, o solo recibir?

La pregunta que ordena la decisión

Antes de comparar latencias conviene responder una sola cosa: ¿el cliente necesita enviar datos en tiempo real, o solo recibirlos?

Si solo recibe, WebSockets queda descartado por sobredimensionado, y la elección se reduce a polling o SSE según qué tan rápido tenga que enterarse. Esa única pregunta elimina la mayoría de las propuestas infladas que devuelve un modelo cuando le pides algo en tiempo real sin más contexto.

Casi nadie necesita bidireccional. Casi todos piden WebSockets.

Qué significa realmente la latencia de polling

Con polling, el usuario no espera el intervalo completo ni cero: espera, en promedio, la mitad. Con un intervalo de 10 segundos, la espera media es de 5 y la peor de 10.

Ese número importa porque define si polling alcanza. Para el estado de un pedido que cambia cada varios minutos, 5 segundos de retraso medio no los nota nadie. Para un chat, sí.

El otro número es el desperdicio. Con 500 usuarios y un intervalo de 10 segundos, el servidor atiende 3.000 peticiones por minuto, y si los datos cambian pocas veces por hora, casi todas responden que no hay nada nuevo.

3.000 peticiones por minuto con 500 usuarios y polling cada 10 segundos
500 conexiones abiertas para lo mismo con SSE, sin peticiones repetidas

Ese contraste es el argumento real para pasar de polling a SSE, y aparece mucho antes por costo de servidor que por latencia.

Cómo se implementa cada uno

El código de los tres es más corto de lo que su reputación sugiere. Lo que cambia entre ellos no es el volumen, es qué tienes que resolver tú.

Polling: pedir solo lo nuevo

// Guarda la marca del último cambio recibido y pregunta solo por lo nuevo
let desde = Date.now();

setInterval(async () => {
    const r = await fetch(`/api/pedidos/cambios?desde=${desde}`);
    const { cambios, ahora } = await r.json();
    desde = ahora;              // el servidor manda su reloj, no el del cliente
    cambios.forEach(pintar);
}, 10000);

Dos detalles que se olvidan seguido. El primero es pedir solo los cambios desde la última consulta, no la lista completa cada vez. El segundo es que la marca de tiempo la debe fijar el servidor: el reloj del navegador puede estar desfasado y hacer que se pierdan o se repitan eventos.

SSE: cuatro líneas en el cliente

const flujo = new EventSource('/api/notificaciones');

flujo.addEventListener('pedido', (e) => pintar(JSON.parse(e.data)));
flujo.onerror = () => { /* el navegador reintenta solo */ };

Del lado del servidor el formato es texto plano con una estructura mínima:

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');   // evita que nginx acumule la salida

foreach ($eventos as $e) {
    echo "event: pedido\n";
    echo 'data: ' . json_encode($e) . "\n";
    echo "id: {$e['id']}\n\n";     // el navegador lo reenvía al reconectar
    flush();
}

El campo id es el que hace que la reconexión no pierda eventos: el navegador lo devuelve en la cabecera Last-Event-ID y el servidor puede retomar desde ahí. Sin él, cada corte de red deja un hueco.

WebSockets: lo que hay que escribir a mano

const ws = new WebSocket('wss://ejemplo.com/sala/42');

ws.onmessage = (e) => pintar(JSON.parse(e.data));
ws.onopen    = () => ws.send(JSON.stringify({ tipo: 'entrar', sala: 42 }));

// La reconexión hay que escribirla: el navegador no la hace por ti
ws.onclose = () => setTimeout(conectar, espera);

Esa última línea resume buena parte del costo. Una reconexión correcta necesita espera creciente entre intentos, un tope para no reintentar infinito, y recuperar el estado perdido durante el corte. Son tres cosas que en SSE no escribes.

La pieza que casi nadie cuenta

Un chat con miles de usuarios necesita que cada servidor sepa a qué otros avisar cuando llega un mensaje, porque remitente y destinatario pueden estar conectados a servidores distintos.

Eso se resuelve con un sistema de mensajería intermedio, por ejemplo Redis Pub/Sub. Es infraestructura adicional que hay que desplegar, monitorear y pagar, y rara vez aparece en la propuesta inicial cuando uno pide un chat en tiempo real.

Dos límites que aparecen en producción

Ninguno de los dos sale en las comparativas y los dos se descubren tarde.

El tope de conexiones por dominio. Sobre HTTP/1.1 los navegadores permiten unas 6 conexiones simultáneas por dominio, y cada pestaña abierta con SSE consume una. Un usuario con cuatro pestañas de tu sitio puede agotarlas. Con HTTP/2 la restricción desaparece.

El costo por conexión en el servidor. En PHP sobre hosting compartido, cada conexión SSE abierta ocupa un proceso durante todo el tiempo que dure. Con pocos usuarios no se nota; con cientos, se agota el conjunto de procesos disponibles y el sitio deja de responder para todos, no solo para quienes usan tiempo real.

Cómo probar esto antes de desplegarlo

Los tres mecanismos se comportan bien en desarrollo, donde hay un usuario, la red no falla y el servidor tiene todos los recursos para él solo. Los problemas aparecen en condiciones que no se dan en tu máquina, así que conviene provocarlas a propósito.

Corta la red a mitad de camino. Con las herramientas del navegador se puede poner la conexión en modo desconectado unos segundos y volver a activarla. Ahí se ve si tu interfaz avisa del corte, si reconecta, y sobre todo si recupera lo que pasó mientras estuvo fuera.

Abre varias pestañas del mismo sitio. Es la forma más rápida de toparse con el tope de conexiones por dominio con SSE sobre HTTP/1.1, y de descubrir si tu servidor aguanta varias conexiones simultáneas del mismo usuario.

Deja la pestaña en segundo plano media hora. Los navegadores suspenden las pestañas inactivas para ahorrar batería, y al volver puede haber un hueco de eventos que nunca llegaron. Si tu aplicación no lo detecta, el usuario ve datos viejos creyendo que están al día.

Con polling estas tres pruebas casi siempre pasan sin cambios, porque cada consulta es independiente y pregunta por todo lo posterior a la última marca. Es otra forma de ver por qué sigue siendo una opción legítima y no un lugar de paso.

Lo que ninguna de las tres resuelve sola

Elegir el mecanismo es la mitad del problema. La otra mitad son tres cuestiones que aparecen igual con polling, con SSE y con WebSockets, y que casi nunca vienen en la propuesta inicial.

Quién puede escuchar qué

Una conexión de tiempo real es un canal de lectura al servidor, y hereda exactamente los mismos problemas de autorización que un endpoint normal. Si un usuario puede suscribirse al canal de otro cambiando un identificador, acabas de construir una fuga de datos que además es continua.

Con SSE y WebSockets esto se agrava porque la comprobación se hace una sola vez, al abrir la conexión. Si los permisos del usuario cambian mientras la conexión sigue viva, el canal sigue emitiendo con los permisos de antes hasta que algo lo cierre.

Qué pasa con lo que ocurrió mientras no estabas

Ninguno de los tres mecanismos garantiza por sí solo que no se pierdan eventos. Un túnel se cae, el teléfono se bloquea, el navegador suspende la pestaña, y durante ese hueco el servidor siguió generando cambios.

SSE trae una respuesta parcial con el identificador de evento, siempre que el servidor lo emita y sepa retomar desde ahí. Con WebSockets hay que diseñarlo a mano. Con polling el problema casi no existe, porque cada consulta pregunta por todo lo posterior a la última marca conocida, y esa es una ventaja real que rara vez se menciona.

La pregunta práctica es qué es peor para tu caso: perder un evento o mostrarlo dos veces. Si perderlo es inaceptable, el cliente necesita poder pedir lo que se perdió, y eso es un endpoint normal además del canal en vivo.

Qué ve el usuario cuando se cae

Una interfaz en tiempo real que pierde la conexión y no lo dice es peor que una que nunca la tuvo, porque el usuario sigue creyendo que lo que ve está al día.

El mínimo es un indicador de estado y, al reconectar, una recarga de lo que haya cambiado. Con SSE el navegador reintenta solo, pero tu interfaz igual tiene que enterarse de que hubo un corte para volver a pedir lo perdido.

El cliente le pregunta al servidor cada cierto intervalo si hay algo nuevo, con una petición HTTP común y corriente cada 5, 10 o 30 segundos.

Ventaja: no necesita nada especial. Cualquier servidor que ya sirve una API REST responde a polling sin cambios, sin librerías nuevas y sin conexiones abiertas.

Costo: hay latencia, porque el cliente se entera en el siguiente intervalo, y hay desperdicio, porque la mayoría de las peticiones responden que no hay nada nuevo.

Úsalo cuando los cambios son poco frecuentes y la simplicidad pesa más que la latencia. Evítalo cuando el usuario tiene que ver el cambio en menos de un par de segundos.

El servidor mantiene abierta una conexión HTTP simple y empuja datos al cliente según ocurren, pero solo en una dirección: del servidor hacia el cliente.

Ventaja: al ser HTTP normal funciona con los proxies y balanceadores que ya tienes, sin configuración especial, y el navegador reconecta solo si la conexión se corta.

Costo: el cliente no puede responder por el mismo canal, y cada conexión abierta ocupa recursos del servidor mientras dure.

Úsalo para notificaciones, barras de progreso o feeds que se actualizan solos. Evítalo cuando el cliente necesita enviar datos en tiempo real.

Abre una conexión persistente y bidireccional. Una vez establecida, ambos lados pueden enviarse mensajes en cualquier momento, sin esperar una petición del otro.

Ventaja: la menor latencia posible, y la única de las tres que sirve cuando el cliente también envía datos constantemente: chats, juegos, edición colaborativa.

Costo: la reconexión la programas tú, el balanceo entre servidores se complica, y repartir mensajes entre varios servidores exige una capa de mensajería adicional.

Úsalo cuando necesitas baja latencia en ambas direcciones. Evítalo cuando solo necesitas notificar al cliente: ahí SSE resuelve lo mismo con menos piezas.

Cómo decidir en tres preguntas

El orden importa, porque la primera descarta la mayoría de los casos.

  1. ¿El cliente necesita enviar datos en tiempo real? Si no, descarta WebSockets aquí mismo. Si sí, es tu única opción de las tres y conviene presupuestar la infraestructura que pide.
  2. ¿Cuánto retraso tolera el usuario? Si acepta varios segundos, polling alcanza y es lo más barato de quitar después. Si necesita verlo al instante, SSE.
  3. ¿Cuántas conexiones simultáneas vas a tener? Con SSE sobre hosting compartido conviene medir el consumo de procesos antes de comprometerse.

Una recomendación que va contra el instinto: si dudas entre polling y algo más sofisticado, empieza por polling. Es la opción más fácil de reemplazar cuando el proyecto crezca, no la más difícil.

Lo contrario, desmontar WebSockets y la capa de mensajería que arrastró para volver a algo simple, es un trabajo que rara vez se hace, y por eso tantos proyectos cargan con infraestructura de tiempo real que nadie necesitaba.


Comparativa intermedio Vigente para: HTML Living Standard, 2026
Indica la versión de la herramienta o estándar sobre la que está escrita esta comparativa. El resto de las conclusiones puede variar en versiones futuras.