R
Interview Deckreact · next.js · senior/staff
0·0·122
System Design

Real-time UI: WebSocket vs SSE vs Polling

Quando usar cada tecnologia de comunicação em tempo real. Trade-offs de latência, escala e complexidade.

WebSocket: protocolo full-duplex sobre TCP — servidor e cliente enviam dados a qualquer momento. Ideal para: chat, jogos, colaboração ao vivo (Google Docs), tracking em tempo real. SSE (Server-Sent Events): canal unidirecional servidor→cliente sobre HTTP. Ideal para: feeds de notícias, dashboards de métricas, status de processamento. Polling: cliente pede periodicamente. Mais simples, mas ineficiente. Long polling melhora ao manter conexão aberta até ter dado.

Problema
Usar WebSocket para um feed de notícias read-only (overkill e mais complexo) ou polling para um app de chat (latência alta, desperdício de requests).
Solução
Decision tree: bidirecional + latência crítica (< 50ms) → WebSocket. Servidor → cliente apenas, fluxo contínuo → SSE. Dados mudam raramente, simplicidade > tudo → polling. O Uber usa WebSocket para posição do motorista (bidirecional, alta frequência), SSE para atualizações de status da corrida (unidirecional).
Tip
SSE tem reconexão automática nativa (EventSource), roda sobre HTTP/2 (multiplexação), e é mais fácil de proxy/firewall do que WebSocket. Prefira SSE quando só o servidor precisa enviar dados. WebSocket puro sem biblioteca (socket.io) exige heartbeat manual para detectar conexões mortas.
// SSE — simples, reconexão automática
// Servidor (Next.js Route Handler)
export async function GET() {
  const stream = new ReadableStream({
    start(controller) {
      const send = (data: object) =>
        controller.enqueue(`data: ${JSON.stringify(data)}\n\n`)

      // Posição do motorista a cada 2s
      const interval = setInterval(async () => {
        const pos = await getDriverPosition()
        send({ lat: pos.lat, lng: pos.lng, ts: Date.now() })
      }, 2000)

      return () => clearInterval(interval) // cleanup
    }
  })
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    }
  })
}

// Cliente React
function useDriverLocation(rideId: string) {
  const [pos, setPos] = useState(null)

  useEffect(() => {
    const es = new EventSource(`/api/ride/${rideId}/position`)
    es.onmessage = (e) => setPos(JSON.parse(e.data))
    es.onerror = () => es.close() // EventSource reconecta sozinho
    return () => es.close()
  }, [rideId])

  return pos
}

// WebSocket — bidirecional (chat, colaboração)
function useWebSocket(url: string) {
  const ws = useRef<WebSocket | null>(null)
  const [connected, setConnected] = useState(false)

  useEffect(() => {
    const connect = () => {
      ws.current = new WebSocket(url)
      ws.current.onopen = () => setConnected(true)
      ws.current.onclose = () => {
        setConnected(false)
        setTimeout(connect, 2000) // reconectar
      }
    }
    connect()
    return () => ws.current?.close()
  }, [url])

  const send = useCallback((msg: object) => {
    ws.current?.send(JSON.stringify(msg))
  }, [])

  return { connected, send }
}
Q.Como detectar e reconectar uma WebSocket desconectada?
A.Implementar heartbeat: servidor manda ping a cada 30s, cliente responde pong. Se o client não receber ping em 60s, fecha e reabre a conexão com backoff exponencial (1s, 2s, 4s, max 30s). Adicionar reconnect logic no onclose handler. socket.io faz isso por padrão — é um dos motivos de usá-lo.
Q.Como o SSE lida com reconexão?
A.Automaticamente via EventSource API: quando a conexão cai, o browser reconecta em ~3s por padrão e envia o header Last-Event-ID com o ID do último evento recebido. O servidor usa esse ID para retransmitir eventos perdidos. Para isso funcionar, você precisa implementar um ID incremental nos eventos e guardar histórico no servidor.
Q.WebSocket escala horizontalmente?
A.WebSocket é stateful — conexão fica em um servidor específico. Para escalar: usar Redis Pub/Sub ou um message broker (Kafka) como bus compartilhado. Quando evento chega em server A, ele publica no Redis; todos os servidores subscrevem e entregam para seus clientes conectados. Socket.io tem adapter de Redis pronto. Sem isso, sticky sessions são necessárias (pior).
116/122← →