1

O que quebra primeiro em um sistema de dados em tempo real?

Quando “tempo real” começa a dar errado

Quando alguém olha para uma tela de trading, o comportamento parece simples:

  • o preço muda;
  • o livro de ofertas se movimenta;
  • novas negociações aparecem;
  • os candles continuam sendo desenhados.

Só que mostrar dados em tempo real não significa apenas abrir um WebSocket e atualizar a interface sempre que uma mensagem chega.

Na prática, os problemas começam justamente quando alguma coisa deixa de funcionar perfeitamente.

A conexão cai.
Uma mensagem chega fora de ordem.
O navegador fica alguns segundos em segundo plano.
O cliente não consegue processar os eventos na mesma velocidade em que eles chegam.

E, de repente, uma interface que parecia correta começa a mostrar um estado que não corresponde mais ao servidor.

1. Receber dados não significa ter o estado correto

Imagine um order book.

Ao abrir a página, o cliente precisa saber quais ofertas existem naquele momento.

Uma estratégia comum é receber primeiro um snapshot, algo como:

  • preço 100: 2 unidades;
  • preço 99: 5 unidades;
  • preço 98: 1 unidade.

Depois disso, o servidor começa a enviar apenas alterações:

  • nível 100 mudou para 1 unidade;
  • nível 99 foi removido;
  • surgiu uma nova oferta em 101.

Isso é muito mais eficiente do que enviar o livro inteiro a cada mudança.

O problema aparece quando uma dessas mensagens nunca chega.

Se o cliente recebe:

1540 → 1541 → 1543

há uma lacuna.

A atualização 1542 sumiu.

Continuar aplicando eventos depois disso pode produzir um estado que parece válido, mas já está incorreto.

Esse tipo de bug é particularmente ruim porque a aplicação não necessariamente quebra.

Ela continua funcionando — apenas mostra informação errada.

2. Sequence IDs são mais importantes do que parecem

Por isso, muitos streams em tempo real incluem algum tipo de número de sequência.

O cliente mantém o último número processado e valida o próximo evento.

Se recebeu 1541, espera 1542.

Caso apareça 1543, existe evidência de que alguma informação foi perdida.

Nesse momento, existem algumas opções:

  1. ignorar a inconsistência;
  2. tentar recuperar apenas o evento ausente;
  3. descartar o estado local;
  4. pedir um novo snapshot ao servidor.

Para sistemas em que consistência importa, a quarta opção costuma ser uma das mais seguras.

Ela custa mais dados, mas evita manter silenciosamente um estado corrompido.

3. Reconectar não é simplesmente abrir outro WebSocket

Outro erro comum é pensar que, quando a conexão cai, basta executar novamente:

connect

A questão é:

o que aconteceu enquanto o cliente estava offline?

O mercado continuou mudando.

Ordens podem ter sido executadas.
Saldos podem ter sido alterados.
Posições podem ter mudado.

Se o cliente simplesmente reconectar e continuar de onde acha que parou, pode carregar uma visão antiga do sistema.

Uma reconexão mais robusta geralmente precisa fazer algo parecido com:

  1. detectar a desconexão;
  2. aguardar antes de tentar novamente;
  3. reconectar;
  4. recuperar o estado atual;
  5. comparar identificadores ou versões;
  6. só então voltar a aplicar eventos incrementais.

Em outras palavras, a reconexão é também um processo de ressincronização.

4. Backoff evita transformar um problema em dois

Agora imagine milhares de clientes desconectando ao mesmo tempo.

Talvez o servidor tenha acabado de reiniciar.

Se todos tentarem reconectar imediatamente, podem gerar uma nova carga justamente no momento em que a infraestrutura está tentando se recuperar.

Por isso, uma estratégia comum é usar exponential backoff.

Em vez de tentar novamente a cada 100 ms, o cliente espera progressivamente mais:

  • 1 segundo;
  • 2 segundos;
  • 4 segundos;
  • 8 segundos.

Normalmente também se adiciona um pouco de aleatoriedade, chamada de jitter, para evitar que milhares de clientes tentem reconectar exatamente no mesmo instante.

É um detalhe pequeno que faz muita diferença em escala.

5. E se o cliente for mais lento que o servidor?

Esse é outro problema interessante.

Suponha que o servidor envie 500 atualizações por segundo.

O navegador consegue processar 500?

Talvez.

Mas e se, para cada mensagem, a aplicação:

  • atualizar o estado global;
  • recalcular vários valores;
  • renderizar componentes;
  • atualizar um gráfico;
  • reorganizar o DOM?

Nesse caso, a interface pode começar a acumular trabalho.

A rede continua funcionando perfeitamente, mas o cliente fica cada vez mais atrasado.

Depois de alguns segundos, o que aparece na tela pode estar centenas de eventos atrás do servidor.

Isso é um problema de backpressure.

6. Nem toda mensagem precisa gerar uma renderização

Uma solução é separar duas coisas:

frequência de entrada dos dados

e

frequência de atualização visual

O sistema pode receber centenas de eventos por segundo, atualizar o estado interno e redesenhar a interface apenas algumas dezenas de vezes por segundo.

Se o preço mudou:

100.01 → 100.02 → 100.03 → 100.04 → 100.05

em poucos milissegundos, talvez o usuário não precise ver cinco renderizações diferentes.

Mostrar diretamente o estado mais recente pode ser suficiente.

Isso reduz trabalho no frontend sem necessariamente perder informação relevante.

7. Dados públicos e dados privados seguem fluxos diferentes

Dados públicos são relativamente simples.

Preço, trades e order book podem ser distribuídos para milhares de usuários.

Mas uma plataforma de trading também possui streams privados:

  • ordens do usuário;
  • saldo;
  • posições;
  • depósitos;
  • retiradas;
  • alterações na conta.

Esses canais precisam de autenticação.

E aí aparecem outras perguntas:

  • O que acontece quando o token expira?
  • O WebSocket precisa ser recriado?
  • É possível renovar a autenticação sem fechar a conexão?
  • Eventos privados e públicos usam a mesma infraestrutura?
  • Uma desconexão deve bloquear temporariamente certas ações da interface?

O frontend precisa distinguir:

“não houve nenhuma alteração”

de

“eu não sei se houve alteração porque perdi a conexão”

Essa diferença é muito importante.

8. “Sem atualização” e “sem conexão” não são a mesma coisa

Imagine que o saldo exibido seja 1.000.

Se nenhuma mensagem nova chegou nos últimos 30 segundos, isso pode significar duas coisas:

  1. o saldo continua em 1.000;
  2. a conexão caiu e o cliente simplesmente não sabe o saldo atual.

Visualmente, os dois estados podem parecer iguais.

Uma boa aplicação precisa conseguir distinguir entre eles.

Por isso, indicadores de conexão, timestamps de última atualização e mecanismos de heartbeat podem ser úteis.

9. Heartbeat ajuda a descobrir conexões que parecem vivas

Às vezes uma conexão não fecha explicitamente.

Ela simplesmente para de transmitir dados.

Isso pode acontecer por:

  • proxies;
  • redes móveis;
  • roteadores;
  • mudanças de conexão;
  • timeout intermediário.

Uma técnica comum é enviar mensagens periódicas de ping/pong.

Se o cliente não receber resposta dentro de determinado intervalo, considera a conexão inválida e inicia a recuperação.

Sem isso, a aplicação pode passar minutos acreditando que ainda está conectada.

10. O que testar além do “happy path”?

O caminho feliz costuma ser simples:

conecta → recebe evento → atualiza tela

Mas os testes mais interessantes são outros.

Por exemplo:

  • perder uma mensagem no meio da sequência;
  • receber duas vezes o mesmo evento;
  • receber eventos fora de ordem;
  • ficar offline durante 20 segundos;
  • reconectar com um snapshot diferente;
  • receber 10 vezes mais eventos que o normal;
  • suspender a aba do navegador;
  • trocar de Wi-Fi para rede móvel;
  • expirar a autenticação durante a sessão.

Esses casos revelam muito mais sobre a robustez de um sistema em tempo real do que apenas verificar se o WebSocket conecta.

11. Observabilidade também deveria existir no frontend

Quando algo dá errado, “o gráfico ficou estranho” não ajuda muito a descobrir a causa.

Algumas métricas podem ser bastante úteis:

  • tempo desde o último evento;
  • número de reconexões;
  • diferença entre sequence IDs;
  • tamanho da fila de eventos;
  • latência entre timestamp do servidor e processamento no cliente;
  • quantidade de eventos descartados;
  • duração média de cada renderização.

Com essas informações fica mais fácil descobrir se o problema está:

  • na rede;
  • no servidor;
  • no processamento local;
  • ou simplesmente na renderização.

Tempo real é mais sobre recuperação do que sobre velocidade

Talvez essa seja a parte mais contraintuitiva.

Um sistema de dados em tempo real não é robusto porque consegue entregar uma mensagem rapidamente quando tudo está funcionando.

Ele é robusto quando consegue detectar que deixou de estar correto e voltar para um estado confiável.

Latência importa.

Mas também importam:

  • ordenação;
  • consistência;
  • recuperação;
  • controle de fluxo;
  • observabilidade;
  • reconexão.

Quando tudo isso funciona bem, o usuário vê apenas números mudando na tela.

E provavelmente nem imagina quantos mecanismos estão trabalhando para garantir que aqueles números ainda sejam verdadeiros.

Carregando publicação patrocinada...