- Era necessário lidar com atualizações em tempo real em larga escala em um backend baseado em Node.js/TypeScript
- Usando PostgreSQL como backend, centenas de nós workers precisavam verificar continuamente novos jobs, e os agentes precisavam receber atualizações de status de execução e chat
- A investigação começou com WebSocket, mas acabou chegando a uma solução "à moda antiga" surpreendentemente eficaz
→ "HTTP Long Polling com Postgres"
O problema: atualizações em tempo real em larga escala
- Atualizações dos nós workers :
- Havia centenas de nós workers executando SDKs de Node.js/Golang/C#
- Eles precisavam saber assim que um novo job estivesse disponível, então era necessária uma estratégia de consulta que não derrubasse o banco de dados Postgres
- Sincronização de estado dos agentes :
- Os agentes precisavam de atualizações em tempo real sobre status de execução e chat, e isso precisava ser transmitido de forma eficiente
Comparação entre long polling e WebSocket
- Short polling é como um trem que parte rigidamente no horário, saindo em intervalos fixos independentemente de haver passageiros ou não
- Long polling faz o servidor esperar para responder e, quando surgem dados, devolve a resposta imediatamente; se passar um certo tempo, responde por timeout
- Ou seja, é como um trem que "espera até aparecer passageiro para partir". Só sai vazio quando ninguém aparece dentro de um tempo específico (TTL)
- Quando há dados (passageiros), parte imediatamente; quando não há, permite usar os recursos de forma eficiente, reunindo as duas vantagens
- WebSocket mantém a conexão aberta continuamente para troca de dados bidirecional
- Em ambientes corporativos, por questões de infraestrutura e firewall, o long polling era mais simples e mais compatível do que configurar WebSocket
Detalhes da implementação de long polling
- A função
getJobStatusSync desempenha um papel importante
- Ela recebe parâmetros como
jobId, owner e ttl e consulta repetidamente o estado de um job específico durante um período determinado
- A consulta repetida continua até que uma das condições abaixo seja atendida
- O status do job se torne
success ou failure
- O
ttl (timeout) expire
- O banco de dados é consultado em intervalos de 500ms e, se o resultado ainda não estiver definido, espera-se e consulta-se novamente
- Se o timeout for excedido, um erro é lançado; em caso de sucesso, o resultado é retornado
Otimização do banco de dados
- Índices apropriados foram criados no Postgres para minimizar o custo das consultas
- Exemplo:
CREATE INDEX idx_jobs_status ON jobs(id, cluster_id);
Vantagens do long polling
- Facilidade para manter o monitoramento : é possível reutilizar a pilha existente de logging e monitoramento baseada em HTTP
- Simplicidade na autenticação : é possível usar a autenticação HTTP existente sem implementar um novo método
- Compatibilidade com a infraestrutura : não são necessárias configurações especiais em firewalls ou load balancers, pois é tratado como tráfego HTTP comum
- Simplicidade operacional : mesmo em reinícios do servidor, não é preciso tratar o estado da conexão separadamente, e o debugging fica mais fácil
- Facilidade de implementação no cliente : funciona apenas adicionando lógica de retry sobre a estrutura padrão de requisição-resposta HTTP
Comparação com o ElectricSQL
- ElectricSQL é uma solução para sincronizar dados do Postgres com o frontend
- Ele tem uma estrutura que garante comportamento em tempo real usando HTTP em vez de WebSocket
- Na prática, quando não é necessário um controle extremo ou uma estrutura de baixo nível para lidar com atualizações em tempo real, o ElectricSQL é recomendado
Por que escolhemos raw long polling
- O mecanismo de entrega de mensagens não é um simples detalhe de implementação, mas um elemento central do produto
- Não era possível depender de uma biblioteca de terceiros para uma funcionalidade central (por melhor que fosse a biblioteca)
- Requisitos
- Controle do produto central : era necessário controlar completamente o mecanismo de entrega de mensagens. Não é algo de nível de infraestrutura, e sim do próprio produto
- Eliminação de dependências externas : minimizar dependências externas para simplificar o self-hosting
- Controle de baixo nível : controlar diretamente o mecanismo de polling e o gerenciamento de conexões
- Máximo nível de controle : era preciso poder ajustar finamente detalhes como intervalos de polling dinâmicos
- Simplicidade do código : projetar de forma simples para que os usuários possam entender e modificar facilmente a base de código
- Em conclusão, ao escolher uma implementação simples de HTTP Long Polling, garantimos controle direto e simplicidade
Cuidados ao implementar long polling
- Configuração de TTL : o servidor deve impor obrigatoriamente um TTL máximo, garantindo que o TTL solicitado pelo cliente não o ultrapasse
- Considerar timeouts da infraestrutura : o TTL deve ser suficientemente menor do que os timeouts configurados em load balancers, edge servers e proxies
- Intervalo de polling no BD : usar um atraso de cerca de 500ms para reduzir a carga no banco
- Estratégia de backoff (opcional) : aumentar gradualmente o intervalo de polling pode usar os recursos do sistema com mais eficiência
Situações em que vale considerar WebSocket
- O WebSocket em si não está errado, e pode ser útil em outros aspectos
- Quando é necessário monitorar conexões com muito estado e trocar eventos complexos continuamente
- Quando há recursos e tempo suficientes para resolver questões de autenticação, infraestrutura e observabilidade
- Existe a complexidade de precisar construir manualmente operação e logging, tratamento de reconexão, mecanismos de autenticação etc.
WebSockets: falando de outra opção
- Embora o long polling tenha sido adequado para nossas necessidades, WebSockets também merecem consideração
- WebSockets em si não são ruins; apenas exigem muita atenção e gestão
- Principais desafios dos WebSockets e direções de solução
- Visibilidade : como WebSockets são baseados em estado, é preciso adicionar logging e monitoramento para conexões persistentes
- Autenticação : é necessário implementar um novo mecanismo de autenticação para conexões WebSocket
- Infraestrutura : é preciso configurar adequadamente load balancers, firewalls e outros componentes para suportar WebSocket
- Gestão operacional : gerenciamento de conexões e reconexões WebSocket, além de tratamento de timeout e erros
- Implementação no cliente : implementação de uma biblioteca WebSocket no cliente, incluindo reconexão e gerenciamento de estado
5 comentários
Estamos usando a estrutura de “short polling” mencionada aqui para servir modelos de ML, e tenho pensado bastante sobre o que seria mais eficiente. Pelo que pesquisei aqui e ali por conta própria, ouvi dizer que, por causa do alto custo do tratamento de reconexão em WebSockets ou SSE, o short polling costuma ser mais seguro no geral, então acabei escolhendo short polling.. 😭
Parece que muita gente evita long polling porque ele soa meio hacky. No navegador, provavelmente vai parecer que a requisição nunca foi concluída. Às vezes existem sites em que o carregamento nunca termina, e eu fico pensando: será que nem todo o conteúdo foi carregado? Acho isso bem ruim.
No aplicativo, no fim das contas também vai acabar existindo algum ponto em que fica em hang aguardando a resposta, então isso parece um pouco estranho.
"o agente precisa receber atualizações de status de execução e do chat"
Ao ver isso, pensei imediatamente em SSE, e de fato há muitas menções a SSE nas opiniões do Hacker News.
Comentários do Hacker News
Long polling tem seus próprios problemas
libcurl, e timeouts podem acontecerÉ um prazer usar Phoenix e LiveView todos os dias
Fico curioso se há alguma vantagem técnica em relação ao uso de Server-Sent Events (SSE)
Este artigo conecta "Websocket" e "Long-polling" como se fossem decisões independentes
Uma forma mais fácil de usar
setTimeoutno Node.jsimport { setTimeout } from "node:timers/promises"; await setTimeout(500);Gosto de long polling, é fácil de entender e, do ponto de vista do cliente, funciona como uma conexão muito lenta
Server-Sent Events ou WebSockets não substituem todos os casos de uso de long polling
É melhor usar o recurso de notificações assíncronas do Postgres
LISTENem um canal e, quando os dados mudarem, o PG pode acionarTRIGGEReNOTIFYNão sei se long polling ainda faz sentido com timeouts curtos e requisições encerradas de forma elegante
É revigorante ser lembrado de uma alternativa relativamente simples aos WebSockets
Quero experimentar usar WebSockets com Elixir, o framework Phoenix e o LiveView.