System Design

WebSockets e SSE

Protocolos para comunicação em tempo real entre browser e servidor, sem que o cliente precise solicitar dados constantemente. WebSocket para fluxo bidirecional; SSE para push unidirecional simples; Long Polling como fallback universal.

Intenção

HTTP clássico é request-response: o cliente pergunta, o servidor responde e a conexão fecha. Para tempo real — chat, jogos, notificações, dashboards ao vivo — esse modelo exige polling contínuo, que desperdiça largura de banda e aumenta a latência. WebSocket, SSE e Long Polling eliminam esse desperdício de formas diferentes.

A escolha entre os três depende principalmente de duas dimensões: a direção do fluxo de dados (bidirecional ou só servidor→cliente) e os requisitos de infraestrutura (proxies corporativos, balanceadores de carga, HTTP/2 suportado ou não).

Problema

O HTTP clássico foi projetado para o modelo "documento na web": o browser pede um recurso, o servidor entrega e a conexão é encerrada. Isso é eficiente para páginas estáticas, mas cria três problemas para aplicações em tempo real:

  • Latência de polling: se o browser verifica por novos dados a cada segundo, a latência efetiva é até 1 segundo mesmo quando o servidor tem dados imediatos. Reduzir o intervalo aumenta o custo de rede.
  • Overhead por requisição: cada request HTTP carrega headers de dezenas a centenas de bytes (cookies, User-Agent, Accept, etc.). Em uma aplicação de chat com 100 mensagens por minuto, esse overhead supera em muito o conteúdo útil.
  • Servidor não consegue iniciar a comunicação: no modelo clássico, o servidor só pode responder, nunca pode notificar. Para "você recebeu uma mensagem" ou "sua ordem foi executada", o cliente precisa perguntar continuamente.

Como funciona

WebSocket — conexão full-duplex persistente

WebSocket começa com um handshake HTTP: o cliente envia uma requisição com Upgrade: websocket e o servidor responde com 101 Switching Protocols. A partir daí, a conexão TCP permanece aberta e ambos os lados podem enviar frames em qualquer direção, a qualquer momento, sem overhead de headers HTTP por mensagem.

// Handshake HTTP → ws://
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

// A partir daqui: frames binários ou texto em ambas direções
// sem overhead HTTP por mensagem

O frame WebSocket tem overhead mínimo: 2–10 bytes de cabeçalho versus 200–800 bytes de headers HTTP. Em aplicações de alta frequência (trading, jogos), essa diferença é significativa.

Escalabilidade: conexões WebSocket são stateful. O mesmo cliente deve sempre chegar à mesma instância do servidor (sticky session) ou o servidor deve usar um broker externo (Redis Pub/Sub, Kafka) para propagar mensagens entre instâncias — broadcasting funciona somente dentro de uma instância sem coordenação externa.

SSE — Server-Sent Events

SSE usa HTTP comum: o cliente faz um GET e o servidor responde com Content-Type: text/event-stream, mantendo a conexão aberta e enviando eventos no formato data: ...\n\n conforme surgem. É unidirecional: apenas o servidor envia dados; o cliente usa a EventSource API nativa do browser.

// Servidor envia eventos no formato SSE
data: {"type":"price","value":142.5}\n\n
data: {"type":"price","value":143.1}\n\n
: keepalive comment (evita timeout de proxies)\n\n
data: {"type":"alert","msg":"target atingido"}\n\n

Vantagens do SSE sobre WebSocket para casos unidirecionais:

  • Reconexão automática: se a conexão cair, o browser reabre automaticamente e envia o último ID recebido via Last-Event-ID, permitindo que o servidor retome do ponto correto.
  • HTTP/2 multiplexado: em HTTP/2, múltiplos streams SSE compartilham uma única conexão TCP, eliminando o limite de 6 conexões paralelas do HTTP/1.1.
  • Mais simples de implementar e depurar: é HTTP puro, visível nas ferramentas de rede do browser, compatível com qualquer proxy e sem handshake especial.

Long Polling — server push via pull repetitivo

Long Polling simula server push sem protocolo especializado: o browser faz um request HTTP normal, mas o servidor não responde imediatamente — ele segura a conexão aberta até ter dados para enviar (ou até um timeout configurado). Quando responde, o browser imediatamente abre um novo request para esperar a próxima atualização.

É a abordagem de maior overhead (headers HTTP completos a cada ciclo) e maior latência (latência = tempo de resposta do servidor + tempo de estabelecer novo request), mas funciona em qualquer proxy corporativo e qualquer ambiente de rede restrito, o que a torna um fallback confiável.

WebSocket Browser Servidor GET (Upgrade: websocket) 101 Switching Protocols ws:// ATIVO frame → {msg: "oi"} frame ← {broadcast} frame → {ping} frame ← {pong} Full-duplex · Stateful sticky session ou Redis Pub/Sub Chat · Jogos · Colaboração · Trading SSE (Server-Sent Events) Browser Servidor GET /events 200 text/event-stream (stream aberto) data: {"price":142.5} data: {"price":143.1} : keepalive data: {"alert":"done"} Unidirecional: servidor → browser Reconexão automática · EventSource API HTTP/2: múltiplos streams numa conexão Notificações · Feeds · Progresso · Dashboard Long Polling Browser Servidor GET /poll aguardando... 200 OK {event: data} GET /poll (retry imediato) 204 No Content (timeout) GET /poll... Overhead HTTP por ciclo · Maior latência Funciona com qualquer proxy corporativo Fallback quando SSE/WS não é viável Fallback · Ambientes restritos · Compatibilidade máxima
Comparação de sequência entre WebSocket (full-duplex, conexão persistente), SSE (servidor para browser, HTTP puro com reconexão automática) e Long Polling (pull repetitivo que simula server push sem protocolo especial).

Tabela comparativa

                   WebSocket          SSE                Long Polling
  ─────────────────────────────────────────────────────────────────────
  Protocolo          ws:// / wss://     HTTP               HTTP
  Direção            Full-duplex        Servidor → cliente  Servidor → cliente
  Overhead/msg       2–10 bytes         ~50 bytes           200–800 bytes
  Reconexão auto     Não (manual)       Sim (nativa)        Não (manual)
  HTTP/2             Não compatível     Compatível          Compatível
  Proxies corp.      Pode bloquear      Geralmente OK       Sempre funciona
  Complexidade       Média              Baixa               Baixa
  ─────────────────────────────────────────────────────────────────────
  Ideal para         Chat, jogos,       Notificações,       Fallback, envs
                     colaboração,       feeds, progresso,   restritos
                     trading, IoT       dashboards ao vivo  corporativos

Escalabilidade WebSocket com múltiplas instâncias

WebSocket cria conexões stateful: se o usuário A está conectado à instância 1 e o usuário B está na instância 2, uma mensagem do usuário B para o usuário A não chega por padrão — o servidor da instância 1 não sabe que a instância 2 tem algo para entregar.

As duas soluções padrão:

  • Sticky session: o balanceador garante que o mesmo cliente sempre chega à mesma instância. Simples, mas reduz a efetividade do load balancing e complica deploys sem downtime.
  • Pub/Sub externo: cada instância publica mensagens em um broker (Redis Pub/Sub, Kafka, NATS) e assina os tópicos dos seus clientes conectados. Qualquer instância pode receber uma mensagem e entregá-la ao cliente correto. Mais robusto e escalável, porém mais complexo de operar.

Quando usar cada protocolo

  • WebSocket: quando há comunicação genuinamente bidirecional em tempo real — chat, edição colaborativa, jogos multiplayer, dashboards de trading onde o cliente também envia dados frequentemente (posição do cursor, comandos de negociação). Latência mínima é essencial.
  • SSE: quando o servidor precisa enviar atualizações ao cliente mas o cliente não envia dados de volta continuamente. Notificações, feeds de atividade, barra de progresso de jobs, dashboards de monitoramento. Prefira SSE a WebSocket nesses casos — é mais simples e aproveita HTTP/2.
  • Long Polling: use como fallback quando SSE não funciona (proxies corporativos que bufferizam respostas, ambientes com suporte antigo). Muitas bibliotecas (Socket.IO, SignalR) implementam Long Polling como fallback automático.

Quando evitar

  • WebSocket quando SSE seria suficiente: WebSocket tem mais overhead operacional, exige sticky session ou pub/sub, e não aproveita HTTP/2. Para fluxo unidirecional, SSE é a escolha mais simples e robusta.
  • WebSocket para 1 M+ conexões simultâneas sem arquitetura dedicada: cada conexão mantém estado no servidor. Em alta escala, isso exige servidores de gateway dedicados (como o Pushpin, Fanout ou arquiteturas event-driven específicas), não servidores de aplicação convencionais.

Prós e contras

WebSocket — Prós

  • Full-duplex real: ambos os lados enviam dados sem esperar o outro terminar.
  • Overhead mínimo por mensagem (2–10 bytes vs. 200+ de headers HTTP).
  • Latência mais baixa que qualquer solução baseada em HTTP clássico.
  • Suportado nativamente em todos os browsers modernos e a maioria dos frameworks.

WebSocket — Contras

  • Stateful: exige sticky session ou pub/sub externo para múltiplas instâncias.
  • Não compatível com HTTP/2 multiplexing.
  • Alguns proxies corporativos bloqueiam ou inspecionam ws:// — use sempre wss://.
  • Reconexão precisa ser implementada manualmente no cliente.

SSE — Prós

  • HTTP puro: transparente para proxies, balanceadores e ferramentas de rede.
  • Reconexão automática nativa via EventSource + Last-Event-ID.
  • Aproveita HTTP/2: múltiplos streams numa única conexão TCP.
  • Mais simples de implementar e depurar que WebSocket.

SSE — Contras

  • Unidirecional: o cliente não pode enviar dados pelo stream SSE — precisa de requests HTTP separados.
  • Alguns proxies antigos bufferizam a resposta, quebrando o streaming (mitigável com comentários de keepalive).

Armadilhas comuns

1. WebSocket sem sticky session e sem pub/sub

O erro mais comum em deploys horizontais: o usuário A conecta à instância 1, o usuário B conecta à instância 2, e quando B envia uma mensagem para A, a instância 2 tenta entregá-la — mas A não está conectado lá. A mensagem é perdida silenciosamente. O sintoma é "às vezes as mensagens não chegam" e piora exatamente quando há mais tráfego (e portanto mais instâncias).

Solução: antes de escalar horizontalmente, implemente Redis Pub/Sub ou equivalente. Cada instância publica mensagens no canal do destinatário e todas as instâncias assinam todos os canais dos seus clientes conectados.

2. ws:// sem TLS em produção

Conexões ws:// (sem TLS) são transparentes para proxies intermediários, que podem injetar conteúdo, modificar frames ou simplesmente bloquear a conexão. Em produção, use sempre wss:// (WebSocket sobre TLS), que é tratado como HTTPS pelos proxies.

3. SSE bloqueado por proxies corporativos

Alguns proxies corporativos bufferizam respostas HTTP antes de repassar ao cliente, o que quebra o streaming do SSE (o cliente nunca recebe eventos intermediários, só o final). A mitigação é enviar comentários periódicos (: keepalive\n\n) a cada 15–30 segundos, que forçam o flush em muitos proxies. Se não resolver, Long Polling é o fallback.

4. Escala de conexões subestimada

10 000 conexões WebSocket simultâneas são perfeitamente gerenciáveis em um servidor node.js bem configurado. 1 000 000 de conexões é uma arquitetura completamente diferente: exige servidores de gateway dedicados, kernel tuning (ulimit, net.core.somaxconn), e possivelmente serviços gerenciados (AWS API Gateway WebSocket, Ably, Pusher). Não trate 10 k e 1 M como o mesmo problema.

Arquiteturas e padrões relacionados

Filas de Mensagem complementam WebSocket na camada de backend: o servidor de websocket recebe mensagens dos clientes, publica numa fila e processa assincronamente, desacoplando a camada de conexão da camada de processamento.

Load Balancing é diretamente afetado pela escolha de protocolo: WebSocket exige sticky sessions ou algoritmos de balanceamento que respeitem afinidade de sessão (IP hash, cookie-based), enquanto SSE funciona com qualquer algoritmo de balanceamento, incluindo round-robin puro.

REST vs GraphQL vs gRPC cobre os protocolos síncronos request-response. WebSocket e SSE são complementares a eles — muitas arquiteturas usam REST para operações CRUD e WebSocket/SSE para atualizações em tempo real.