Existe uma questão interessante na Engenharia de Software: algumas decisões que parecem simples durante o desenvolvimento de uma funcionalidade podem se tornar problemas consideráveis quando pensamos na aplicação funcionando em escala.
Em uma discussão arquitetural sobre atualização dos status de serviços, surgiu uma preocupação: e se deixássemos cada frontend consultar constantemente o backend para descobrir se alguma informação mudou?
A princípio, parece uma solução perfeitamente razoável. Afinal, precisamos atualizar a interface, então por que não fazer uma requisição de tempos em tempos?
O problema é que essa decisão não pode ser analisada considerando apenas um usuário ou uma aplicação aberta no navegador. Precisamos pensar no comportamento de centenas ou milhares de clientes simultâneos.
Essa discussão nos leva a questões importantes sobre escalabilidade, comunicação entre aplicações e, principalmente, decisões arquiteturais.
O problema de consultar constantemente o backend
Uma das maneiras mais simples de manter informações atualizadas em uma aplicação é utilizar uma técnica chamada Polling.
Seu funcionamento é direto: o cliente realiza uma requisição ao servidor, recebe uma resposta, aguarda determinado intervalo e realiza uma nova consulta.
Imagine uma aplicação que acompanha o estado de diversos serviços. O frontend precisa saber se eles estão disponíveis, indisponíveis ou operando com alguma degradação.
Poderíamos implementar uma consulta periódica: o frontend solicita os status ao backend, recebe os dados, atualiza a interface e, após determinado intervalo, realiza uma nova requisição. Esse processo continua enquanto a aplicação estiver ativa.
Não existe nada de errado com essa abordagem por si só. O polling é amplamente utilizado e resolve muitos problemas de comunicação. A questão aparece quando começamos a aumentar a quantidade de clientes.
Imagine que cada usuário consulte o backend a cada cinco segundos:
| Usuários simultâneos | Requisições por segundo |
|---|---|
| 100 | 20 |
| 500 | 100 |
| 1.000 | 200 |
| 5.000 | 1.000 |
| 10.000 | 2.000 |
Esses números representam apenas as consultas periódicas para atualização dos status. Não estamos considerando autenticação, operações de negócio, consultas de dados ou qualquer outra funcionalidade da aplicação.
Com dez mil usuários, por exemplo, seriam aproximadamente duas mil requisições por segundo destinadas a essa única finalidade.
E existe outro detalhe: muitas dessas consultas podem retornar exatamente os mesmos dados. Ou seja, o sistema continua recebendo solicitações mesmo quando nenhum status sofreu alteração.
Isso nos leva a uma pergunta: será que precisamos realmente consultar o backend tantas vezes?
O efeito manada: quando todos fazem a mesma coisa ao mesmo tempo
Além do volume total de requisições, precisamos considerar quando elas chegam.
Imagine que mil usuários abram a aplicação praticamente ao mesmo tempo. Se todos os navegadores iniciarem suas consultas com um intervalo fixo de 30 segundos, existe a possibilidade de que essas requisições continuem chegando em grandes grupos.
Em vez de distribuir o tráfego uniformemente, o sistema pode receber picos de solicitações em determinados momentos.
Esse comportamento está relacionado a um problema conhecido como Thundering Herd.
O termo descreve situações em que muitos clientes ou processos tentam acessar simultaneamente um mesmo recurso, provocando uma concentração de solicitações que pode sobrecarregar o componente responsável por atendê-las.
Uma analogia simples seria imaginar centenas de pessoas tentando entrar por uma única porta ao mesmo tempo. Mesmo que a porta consiga atender todas elas, a concentração provoca um gargalo.
Em sistemas distribuídos, esse comportamento pode aparecer em consultas periódicas, tentativas de reconexão, retentativas após falhas e outros mecanismos que fazem vários clientes agirem simultaneamente.
Portanto, precisamos considerar duas questões diferentes: o volume de requisições recebido ao longo do tempo e a concentração dessas requisições em determinados momentos. Resolver uma dessas questões não significa necessariamente resolver a outra.
O que acontece quando o comportamento dos clientes sobrecarrega o servidor?
Um exemplo interessante aconteceu com o Google Voice em agosto de 2025.
De acordo com o relatório oficial publicado pelo Google, uma atualização de software provocou uma quantidade muito grande de dispositivos tentando se conectar simultaneamente a um componente crítico da infraestrutura de comunicação.
O volume de tentativas sobrecarregou esse componente, provocando falhas no processamento das solicitações.
O cenário foi agravado porque os dispositivos estavam configurados para repetir imediatamente as tentativas de conexão quando encontravam falhas. Isso criou um ciclo de novas solicitações que intensificou ainda mais a sobrecarga.
O incidente durou aproximadamente quatro horas e afetou usuários globalmente, com chamadas interrompidas e problemas de conexão.
Entre as medidas anunciadas pelo Google estava a implementação de intervalos maiores entre as tentativas de conexão após falhas HTTP 503.
Esse caso demonstra que a disponibilidade de um sistema não depende exclusivamente da capacidade de processamento dos servidores. O comportamento dos clientes também precisa ser considerado.
Um sistema pode ter uma infraestrutura robusta e, ainda assim, sofrer com uma estratégia inadequada de comunicação e retentativas.
Distribuindo as requisições com Jitter
Durante nossa discussão arquitetural, uma das propostas levantadas foi randomizar os intervalos entre as consultas.
Essa estratégia está relacionada a um conceito chamado Jitter.
Imagine novamente mil usuários consultando o backend a cada 30 segundos. Se todos iniciarem suas consultas no mesmo instante, teremos uma concentração de requisições.
Com o jitter, adicionamos uma variação aleatória ao intervalo de cada cliente. Por exemplo:
- Cliente A: consulta após 27 segundos.
- Cliente B: consulta após 34 segundos.
- Cliente C: consulta após 31 segundos.
- Cliente D: consulta após 25 segundos.
Em vez de todos realizarem a próxima solicitação no mesmo instante, as consultas ficam mais distribuídas ao longo do tempo.
É como organizar a entrada de pessoas em um estabelecimento, evitando que todas tentem entrar simultaneamente.
O jitter é bastante utilizado em sistemas distribuídos, especialmente em estratégias de retentativa (retry), justamente para evitar que vários clientes tentem novamente ao mesmo tempo.
Existe, porém, uma observação importante: randomizar os intervalos não reduz necessariamente a quantidade total de requisições. O objetivo principal é evitar a concentração delas.
No contexto discutido, essa estratégia poderia ajudar a distribuir melhor as consultas realizadas pelos frontends.
Gerenciando a comunicação entre frontend e backend
Outra questão levantada envolveu o que acontece quando frontend e backend passam algum tempo sem trocar informações.
Imagine que existe uma conexão estabelecida entre os dois componentes. Depois de determinado período sem atividade, algum elemento da infraestrutura pode encerrar essa conexão.
Isso pode acontecer por configurações de timeout em servidores, proxies, gateways ou balanceadores de carga. Quando o frontend precisar se comunicar novamente, poderá ser necessário estabelecer uma nova conexão.
Nesse contexto, existe um mecanismo conhecido como Heartbeat.
O heartbeat funciona como um sinal periódico de vida. Uma das partes envia uma pequena mensagem para verificar se a outra continua ativa ou responsiva.
Podemos imaginar uma ligação telefônica em que, depois de algum tempo sem conversa, uma pessoa pergunta:
— Você ainda está aí?
E a outra responde:
— Estou!
Essa troca ajuda a identificar se a comunicação continua funcionando.
É importante não confundir heartbeat com polling. Embora ambos possam envolver mensagens periódicas, seus objetivos são diferentes: o polling busca informações ou atualizações, enquanto o heartbeat verifica se uma conexão ou componente continua ativo.
Também não devemos confundir heartbeat com Keep-Alive. São mecanismos relacionados à manutenção e ao gerenciamento de conexões, mas possuem finalidades e comportamentos diferentes.
Em uma conexão WebSocket, por exemplo, podemos utilizar mensagens de Ping e Pong para verificar se o outro lado continua responsivo.
Na discussão arquitetural, essa questão apareceu como uma preocupação sobre a inatividade da comunicação. A ideia era compreender como frontend e backend poderiam reconhecer essa situação e lidar com ela.
Outra proposta discutida foi a utilização de um Handshake.
Em sistemas computacionais, handshake representa uma etapa inicial de comunicação na qual duas partes trocam informações para estabelecer ou negociar determinados aspectos da comunicação.
Podemos fazer uma analogia com uma apresentação antes de iniciar uma conversa. Antes de prosseguir, as partes precisam estabelecer algum reconhecimento e combinar as condições necessárias para a interação.
No contexto discutido, levantamos a necessidade de que frontend e backend tivessem algum tipo de reconhecimento mútuo antes de iniciar o processo de comunicação relacionado à atualização dos status.
O conceito é importante, mas precisamos fazer uma distinção: handshake não significa necessariamente autenticação, registro de clientes ou manutenção de conexão. O funcionamento depende do protocolo e da arquitetura adotados.
Como ainda não definimos exatamente como esse mecanismo funcionaria em nossa proposta, não seria correto apresentar uma implementação específica. O ponto principal foi reconhecer que a comunicação entre os componentes precisava ser pensada arquiteturalmente, em vez de simplesmente permitir que cada frontend realizasse consultas indiscriminadamente.
Existem outras formas de atualizar informações?
Sim. O polling é apenas uma das estratégias disponíveis para manter frontend e backend sincronizados.
Outras abordagens apresentam características diferentes e podem ser adequadas a determinados cenários:
| Estratégia | Como funciona |
|---|---|
| Polling | O cliente consulta o servidor periodicamente. |
| Long Polling | O servidor mantém uma requisição aberta até existir uma atualização ou ocorrer um timeout. |
| Server-Sent Events (SSE) | O servidor envia atualizações ao cliente por meio de uma conexão HTTP persistente. |
| WebSockets | Permite comunicação bidirecional entre cliente e servidor. |
No polling tradicional, o cliente controla o momento das consultas.
No Long Polling, o cliente realiza uma solicitação, mas o servidor pode aguardar até ter alguma informação disponível antes de responder. Depois da resposta, o cliente normalmente inicia outra solicitação.
Já o SSE permite que o servidor envie eventos ao frontend por uma conexão persistente. É uma alternativa interessante quando precisamos de atualizações unidirecionais, do servidor para o cliente.
O WebSocket, por sua vez, estabelece um canal de comunicação bidirecional, permitindo que cliente e servidor troquem mensagens sem precisar iniciar uma nova requisição HTTP para cada atualização.
Mas existe um detalhe: nenhuma dessas tecnologias elimina automaticamente os problemas de escalabilidade.
Uma aplicação com milhares de conexões WebSocket, por exemplo, ainda precisa administrar conexões, consumo de memória, distribuição de eventos e capacidade de processamento.
Trocar polling por WebSockets não é, por si só, uma solução para todos os problemas.
A escolha precisa considerar o comportamento esperado, a infraestrutura disponível e os requisitos da aplicação.
Será que precisamos realmente de atualizações em tempo real?
Talvez esta seja a pergunta mais importante de toda a discussão.
É comum associarmos uma aplicação de qualidade à menor latência possível. Queremos respostas rápidas, informações atualizadas e uma interface que pareça estar sempre sincronizada.
Mas será que todos os negócios precisam disso?
Imagine duas empresas.
A primeira possui um painel administrativo utilizado para acompanhar o estado de equipamentos internos. Uma atualização a cada três minutos pode ser perfeitamente suficiente. Nenhuma decisão importante depende de saber se determinado equipamento mudou de estado nos últimos segundos.
A segunda empresa trabalha com operações financeiras. Nesse cenário, alguns segundos de atraso podem comprometer decisões, gerar prejuízos ou afetar operações importantes.
As duas possuem a necessidade de atualizar informações, mas o impacto da latência é completamente diferente.
Para a primeira empresa, implementar uma arquitetura complexa de comunicação em tempo real pode significar mais custos de infraestrutura e manutenção sem entregar um benefício proporcional.
Para a segunda, investir em mecanismos de atualização mais rápidos pode ser essencial para a operação.
Isso demonstra que não existe uma frequência universal de atualização que sirva para todas as aplicações.
Antes de escolher uma estratégia, precisamos entender quanto tempo de atraso o negócio consegue tolerar, se uma informação desatualizada pode gerar prejuízo, quantos usuários simultâneos são esperados e qual é o custo de manter determinada infraestrutura.
Essas perguntas ajudam a transformar uma necessidade do negócio em requisitos técnicos.
Engenharia de Software também é saber o que não precisa ser feito
A discussão arquitetural me fez refletir sobre algo que vai além da implementação de uma funcionalidade.
Quando pensamos em uma aplicação, é fácil concentrar nossa atenção no comportamento esperado da interface: os dados precisam aparecer, os status precisam ser atualizados e o usuário precisa visualizar as informações.
Mas existe uma camada que nem sempre fica evidente: o custo de manter esse comportamento funcionando para muitos usuários simultaneamente.
Uma solução pode funcionar perfeitamente durante o desenvolvimento e apresentar problemas quando o número de clientes aumenta.
Por isso, decisões como a frequência das consultas, a manutenção de conexões, a distribuição de requisições e a escolha do mecanismo de comunicação precisam ser consideradas antes mesmo de começar a escrever o código.
E existe uma reflexão ainda mais importante: nem sempre precisamos da solução mais sofisticada.
Se o negócio aceita uma atualização a cada três minutos, talvez não exista justificativa para construir uma infraestrutura de comunicação em tempo real.
Se alguns segundos de atraso podem representar prejuízo, talvez seja necessário investir em uma estratégia mais robusta.
O trabalho da Engenharia de Software não é simplesmente escolher a tecnologia mais moderna ou implementar a menor latência possível.
É compreender o problema, avaliar alternativas e tomar decisões que façam sentido para o contexto em que o sistema será utilizado.
Uma boa arquitetura não é necessariamente aquela que faz tudo mais rápido. É aquela que entrega o que o negócio precisa, com confiabilidade, eficiência e complexidade compatível com sua realidade.
Referências
- Google Workspace Status Dashboard — Incidente do Google Voice, 25 de agosto de 2025.
- MDN — Server-Sent Events
- MDN — WebSockets
- RFC 6455 — The WebSocket Protocol
- RFC 9110 — HTTP Semantics
- AWS Architecture Blog — Exponential Backoff and Jitter
Top comments (0)