Como tornar conexões WebSocket mais resilientes em aplicações Node.js
WebSockets são uma ótima solução quando uma aplicação precisa trocar informações em tempo real.
Chats, dashboards, notificações, jogos online, sistemas de monitoramento e plataformas de negociação são alguns exemplos comuns.
Criar uma conexão WebSocket básica é relativamente simples.
O desafio começa quando precisamos garantir que ela continue funcionando bem fora do cenário ideal.
A internet do usuário cai.
O servidor reinicia.
Uma mensagem deixa de chegar.
Milhares de clientes tentam reconectar ao mesmo tempo.
É nesse ponto que uma implementação simples começa a precisar de uma estratégia mais robusta.
1. Conectar é fácil. Permanecer sincronizado é mais difícil.
Imagine que um cliente esteja recebendo eventos continuamente:
101 → 102 → 103 → 104
Agora imagine que, por algum problema de rede, ele receba:
101 → 102 → 104
A conexão pode continuar aberta normalmente.
O problema é que o evento 103 desapareceu.
Isso significa que o cliente pode continuar exibindo dados sem perceber que seu estado local já está incorreto.
Por isso, sistemas em tempo real frequentemente utilizam sequence IDs.
Cada mensagem recebe um número crescente e o cliente verifica se o próximo valor corresponde ao esperado.
Se isso não acontecer, existe uma boa indicação de perda de dados.
2. Não confie apenas no evento close
Nem sempre uma conexão quebrada é encerrada de forma limpa.
Às vezes ela simplesmente para de responder.
Isso pode acontecer por causa de:
- redes móveis instáveis;
- proxies;
- roteadores;
- mudança entre Wi-Fi e 4G/5G;
- timeout de infraestrutura intermediária;
- suspensão da aba do navegador.
Uma solução comum é implementar um mecanismo de heartbeat.
O servidor envia periodicamente uma mensagem de verificação e espera uma resposta do cliente.
Em Node.js, a lógica pode ser algo parecido com:
socket.on("pong", () => {
socket.isAlive = true;
});
Periodicamente:
socket.isAlive = false;
socket.ping();
Se o cliente não responder dentro do intervalo esperado, a conexão pode ser considerada inválida.
Isso é melhor do que esperar indefinidamente por uma conexão que aparentemente continua aberta.
3. Reconectar imediatamente pode piorar o problema
Quando uma conexão cai, a primeira reação costuma ser tentar novamente o mais rápido possível.
Mas imagine um servidor com 50 mil clientes.
Se ele reiniciar e os 50 mil tentarem reconectar no mesmo segundo, a recuperação pode gerar uma nova sobrecarga.
Uma técnica bastante utilizada é o exponential backoff.
Em vez de tentar reconectar continuamente:
1s → 2s → 4s → 8s → 16s
O intervalo aumenta progressivamente.
Também podemos adicionar um pequeno valor aleatório, conhecido como jitter.
Assim, milhares de clientes não executam a tentativa exatamente no mesmo instante.
4. Reconexão não significa recuperação
Existe uma diferença importante entre:
reconectar
e
recuperar o estado correto
Suponha que um cliente fique offline durante dez segundos.
Nesse intervalo, dezenas de eventos podem ter ocorrido.
Quando a conexão volta, continuar processando apenas os novos eventos pode não ser suficiente.
Uma estratégia comum é:
- detectar a desconexão;
- reconectar;
- buscar novamente o estado atual;
- validar a sequência;
- somente então continuar recebendo eventos incrementais.
Esse padrão é especialmente útil quando existe uma combinação de:
snapshot + eventos incrementais
O snapshot fornece o estado completo.
Os eventos posteriores mantêm esse estado atualizado.
5. O cliente também pode ser o gargalo
Nem todo problema acontece na rede ou no servidor.
Imagine que o backend envie 1.000 mensagens por segundo.
O Node.js pode receber todas elas corretamente.
Mas o cliente precisa:
- atualizar estados;
- recalcular valores;
- redesenhar componentes;
- atualizar gráficos;
- reorganizar elementos da interface.
Se cada evento causar uma renderização completa, o navegador pode começar a ficar para trás.
Nesse cenário temos um problema de backpressure.
Os dados chegam mais rápido do que o consumidor consegue processar.
Uma solução possível é desacoplar:
frequência dos eventos
de
frequência da renderização
O sistema pode receber centenas de eventos, atualizar seu estado interno e renderizar apenas o valor mais recente em intervalos curtos.
6. Nem todos os eventos precisam ser tratados da mesma forma
Alguns eventos são críticos.
Outros podem ser substituídos rapidamente por um estado mais recente.
Por exemplo, se um preço muda cinco vezes em 20 milissegundos:
100.01
100.02
100.03
100.04
100.05
talvez não faça sentido redesenhar toda a interface cinco vezes.
Dependendo da aplicação, pode ser suficiente processar internamente as alterações e apresentar apenas o estado mais recente.
Já eventos como alteração de saldo, execução de ordem ou mudança de permissão podem exigir processamento individual.
A arquitetura precisa distinguir esses casos.
7. Autenticação também precisa sobreviver ao tempo
WebSockets públicos costumam ser relativamente simples.
Mas conexões privadas podem transmitir:
- informações de conta;
- notificações pessoais;
- saldos;
- ordens;
- posições;
- eventos administrativos.
Nesse caso, a conexão precisa estar autenticada.
E isso cria novas perguntas:
O que acontece quando o token expira?
É possível renovar a sessão sem fechar o socket?
O cliente precisa autenticar novamente depois de uma reconexão?
Uma conexão antiga pode continuar recebendo dados depois de uma mudança de permissões?
Essas questões fazem parte da segurança do sistema, não apenas da camada de rede.
8. Observabilidade facilita muito a depuração
Problemas em tempo real podem ser difíceis de reproduzir.
Por isso, vale registrar métricas como:
- quantidade de conexões ativas;
- número de reconexões;
- tempo médio de conexão;
- mensagens enviadas e recebidas;
- falhas de heartbeat;
- diferença entre sequence IDs;
- latência média;
- tamanho de filas internas;
- eventos descartados.
Sem observabilidade, o problema pode chegar como:
“Às vezes os dados param de atualizar.”
Com boas métricas, podemos descobrir que:
“Após aproximadamente 15 minutos em determinada rede, o cliente deixa de responder aos heartbeats e demora 40 segundos para iniciar uma nova conexão.”
Essa diferença torna a investigação muito mais objetiva.
9. Teste também os cenários ruins
É comum testar apenas:
conectar → receber mensagem → mostrar resultado
Mas sistemas WebSocket precisam ser testados em cenários menos confortáveis.
Alguns exemplos:
- desligar a rede por alguns segundos;
- reiniciar o servidor;
- duplicar mensagens;
- remover uma mensagem da sequência;
- alterar a ordem dos eventos;
- expirar o token de autenticação;
- simular milhares de reconexões;
- deixar a aba em segundo plano;
- alternar entre Wi-Fi e rede móvel.
Esses testes normalmente revelam mais problemas do que o fluxo ideal.
Aplicações práticas
Esse tipo de arquitetura aparece especialmente em produtos que precisam apresentar informações continuamente atualizadas.
Dashboards de infraestrutura, ferramentas colaborativas e plataformas de negociação são bons exemplos.
Em plataformas de ativos digitais como BYDFi, por exemplo, diferentes fluxos podem envolver preços de mercado, execução, ordens e informações relacionadas à conta, exigindo que dados públicos e privados permaneçam sincronizados.
Nesse cenário, uma conexão rápida é importante, mas uma conexão capaz de perceber quando perdeu consistência e recuperar seu estado é ainda mais importante.
Conclusão
WebSocket não é apenas uma conexão que permanece aberta.
Um sistema realmente resiliente precisa pensar em:
- heartbeat;
- sequence IDs;
- reconexão;
- exponential backoff;
- jitter;
- autenticação;
- recuperação de estado;
- backpressure;
- observabilidade.
O cenário ideal é simples.
A engenharia interessante aparece quando a conexão cai, mensagens desaparecem ou o cliente deixa de acompanhar o servidor.
No fim, uma boa arquitetura em tempo real não é aquela que nunca falha.
É aquela que detecta a falha rapidamente, recupera um estado confiável e continua funcionando sem depender da intervenção do usuário.

