Eu tenho um app de barra de menu que precisa saber de um número. Uma porcentagem de 0 a 100. Para obter isso, ele consulta um servidor a cada 30 segundos.
Faça as contas: 30 segundos são 2 chamadas por minuto, 120 por hora, 960 em um dia de 8 horas. Quase mil requisições HTTP por dia para obter um número que às vezes não muda por 20 minutos.
Isso não é monitoramento. É perseguição ao servidor.
O problema de verdade não é técnico. É político.
Quando você depende de uma API que você não controla — que não é pública, que não tem rate limits documentados, que pertence a uma empresa que pode mudar seus Termos de Serviço numa terça-feira qualquer — cada requisição desnecessária é um risco. Não de timeout. Mas de ter sua conexão cortada.
O endpoint que eu uso não é documentado. Funciona hoje. Funciona já há meses. Mas a cada requisição que eu envio, há uma nova linha em um log que alguém na Anthropic pode perceber e decidir que um app de terceiros está fazendo barulho demais.
Então, a pergunta não é “como faço polling mais rápido?”, mas sim “como faço polling o menos possível sem perder informação?”
E é aí, meu amigo, que um engenheiro razoável escreveria um if e um engenheiro com um guilty pleasure por sobreengenharia monta um filtro de Kalman.
A solução ingênua (e por que falha)
O primeiro instinto é simples: se o número não mudou, não pergunte.
se o_valor == o_valor_anterior:
espera mais
senão:
volta para 30 segundos
Funciona mal demais. O valor muda quando você faz algo (envia mensagens, usa tokens). Mas também muda quando você não faz nada — a cota tem uma janela deslizante de 5 horas, então os tokens antigos expiram sozinhos. E, se você estiver usando o serviço a partir de outro dispositivo, o valor sobe sem que você saiba.
Não basta olhar se mudou. Você precisa prever quando vai mudar e com qual confiança.
Kalman para gente com pressa
Um filtro de Kalman é uma máquina para combinar duas fontes de informação imperfeitas.
Imagine que você está em um quarto sem janelas e quer saber a temperatura lá fora. Você tem duas opções:
- Seu modelo mental: “São 3 da tarde em março, em São Paulo, então calculo uns 25°C.” É uma estimativa razoável, mas não perfeita — pode ter chovido, pode estar ventando.
- Um termômetro barulhento: você pode abrir a porta e olhar, mas seu termômetro é barato e varia ±3°C.
Nenhuma fonte é perfeita. O filtro de Kalman diz: combine ambas, mas dê mais peso àquela que for mais confiável em cada momento.
Se você acabou de verificar o termômetro faz 10 segundos, seu modelo mental está ótimo — confie nele e não se incomode em olhar de novo. Se passou uma hora sem checar, seu modelo mental já perdeu valor — abra a porta.
A chave é a variância: um número que mede “quão confiável eu acho que minha estimativa atual é”. Começa em zero logo após verificar o termômetro e cresce com o tempo. Quando ultrapassa um limite, o filtro diz “não confio mais, preciso de um dado real.”
No meu caso:
- O modelo mental = custo local de tokens. Sei o que consumi no Claude Code, então posso calcular quanto a cota deve ter subido.
- O termômetro = a API do Anthropic. Dado real, mas cada consulta tem custo político e energético.
- A variância = incerteza que cresce com o tempo. Se eu usei o serviço pelo navegador ou celular, meu modelo local não sabe — e isso degrada a previsão.
Um Kalman completo (multidimensional, com matrizes de covariância) seria matar mosquito com bazuca. O meu é escalar: um único estado (utilização), um único sensor (a API), um modelo linear (custo/orçamento). 20 linhas de código. A versão mínima que resolve o problema.
Traduzindo para o meu problema concreto:
- Previsão:
utilizacao_estimada = ultimo_dado_real + (custo_local_novo / orcamento) × 100 - Correção: toda vez que o servidor responde, o filtro reseta sua variância para zero.
- Incerteza: a variância cresce linearmente com o tempo.
σ = √(Q × segundos_desde_ultima_correcao).
O truque: o filtro decide quando perguntar
É aqui que a sobreengenharia se justifica. O filtro não só estima o valor — decide quando precisa de um dado real. Cinco regras, avaliadas em cada tick:
| Regra | Disparador | Por quê |
|---|---|---|
| Janela resetada | now ≥ resetsAt | Os tokens expiraram. O dado anterior não é mais válido. |
| Alta incerteza | σ > 5% | Confio pouco na minha previsão. |
| Cruzamento de limite | O intervalo de confiança cruza 80%, 95% ou 100% | Estou próximo de uma zona crítica. O usuário precisa saber disso. |
| Proximidade | utilizacao a menos de 8% de um limite | Posso estar do outro lado sem saber (atividade externa). |
| Timeout de segurança | 15 minutos sem dado real | Por precaução. A paranoia é uma virtude em software de monitoramento. |
Se nenhuma regra for ativada, o filtro diz “relaxa, eu sei o que estou fazendo” e o app não faz uma requisição HTTP. O valor que mostra ao usuário é a estimativa local.
Preste atenção nisso: a estimativa local custa zero rede, zero bateria, zero risco. É pura aritmética em memória.
Os números: antes e depois
Um dia típico com a cota estável (uso moderado, sem picos):
| Cenário | Requisições/hora | Requisições/dia (8h) |
|---|---|---|
| Polling fixo 30s | 120 | 960 |
| Com estimador bayesiano | 15-30 | 120-240 |
| Estimador + dormant | 4-10 | 30-80 |
Isso representa uma redução de 75-97% nas chamadas de rede. Nada mal para “simplesmente” fazer previsões locais entre consultas reais.
Mas espere, tem mais (a degradação adaptativa)
O filtro de Kalman resolve o problema de “quando perguntar”. Mas há outro nível: quanto esforço investir em perguntar.
O app tem uma política de polling que ajusta o intervalo base de acordo com o contexto:
Atividade recente (< 10 min) → 30s
Idle moderado (10 min - 1h) → 120s
Idle longo (> 1h) → 300s
Cota > 80% → 30s sempre (zona crítica)
Modo economia de energia → 2× o intervalo base
Erros consecutivos → backoff exponencial (até 5 min)
Cada nível é uma decisão de “quanta informação eu preciso agora mesmo”. Se você não está programando, para quê gastar bateria verificando sua cota a cada 30 segundos? Se seu laptop está com 15% de bateria, vale a pena fazer o dobro de requisições HTTP?
O modo dormant: quando o app entra em estação de baixa energia
E agora vem minha parte favorita. Aquela que, admito, talvez não fosse estritamente necessária, mas que me deixou com aquele sorriso de quem fez algo desnecessariamente elegante.
Quando o estimador bayesiano gera cinco estimativas consecutivas onde o valor muda menos de 0,5%, o app entra no modo dormant:
- Para o timer.
- Para as estimativas.
- Fica só escutando o filesystem.
Por que o filesystem? Porque se você estiver usando o serviço, são gerados arquivos locais. Quando o watcher de arquivos detecta atividade, o app acorda, faz uma chamada à API imediatamente para se alinhar à realidade e retorna ao ciclo normal.
É como um cachorro cochilando perto da porta. Não gasta energia, mas se escuta a chave, desperta na hora.
O resultado: se você para de trabalhar às 14h e volta às 16h, o app fez zero requisições durante essas duas horas. Zero. Nem polling a cada 5 minutos, nem keepalive, nem heartbeat. O timer literalmente não existe. E quando você volta, o dado atualizado aparece em milissegundos.
“Não era mais fácil fazer um setInterval de 5 minutos e pronto?”
Sim. Muito mais fácil. E provavelmente seria suficiente para 90% dos usuários.
Mas há uma diferença que importa quando seu app roda 8 horas por dia em segundo plano:
setInterval(5min) | Estimador + dormant | |
|---|---|---|
| Requisições/dia idle | 96 | 0 |
| Requisições/dia ativo | 96 | 30-80 (adaptativo) |
| Latência de atualização | 0-5 min | < 1s (FSEvent wake) |
| Consumo bateria idle | Constante | Zero |
| Precisão zona crítica | Igual (5 min delay) | 30s (zona > 80%) |
A linha que importa é a terceira. Com um timer fixo de 5 minutos, se a cota pula de 78% para 95% entre dois ticks, o usuário só percebe depois de até 5 minutos. Com o estimador bayesiano, o intervalo desce para 10 segundos quando estima localmente, e o filtro requisita um dado real ao servidor assim que seu intervalo de confiança cruza os 80%.
Dito em linguagem clara: reage mais rápido fazendo menos requisições.
A parte séria: por que isso é software responsável
Vou tirar meu chapéu de over-engineer satisfeito e colocar o de engenheiro, ponto final.
Cada requisição HTTP que seu app faz em segundo plano tem um custo que você paga, que o servidor paga e que o planeta paga. Não é retórica. É termodinâmica. Um wakeup de rede em um laptop em repouso ativa o rádio WiFi, negocia TLS, espera resposta, processa dados e volta a dormir. Multiplicado por mil apps fazendo o mesmo, isso se torna a razão pela qual seu MacBook dura 6 horas em vez de 10.
A Apple sabe disso. Por isso o macOS tem App Nap, Timer Coalescing e pune apps com alto Energy Impact. Meu ponto de partida era um app com 857 de Energy Impact. O objetivo era baixá-lo para menos de 5.
O estimador bayesiano com dormant não foi um capricho. Foi a única forma de alcançar esse número sem sacrificar a experiência do usuário. Fazer menos requisições era obrigatório. Fazê-las de forma inteligente era o desafio.
A receita, caso te sirva
Se você tem um app que faz polling em um servidor e quer reduzir requisições sem perder reatividade:
Meça se é possível prever localmente. Se o valor que você consulta depende de dados que você também tem localmente, é possível interpolar entre chamadas do servidor.
Modele a incerteza. Não basta prever. Você precisa saber o quão confiante está na sua previsão. Um filtro de Kalman escalar são 20 linhas de código.
Defina limites de decisão. Em quais intervalos do valor a precisão é importante? Não desperdice precisão em zonas que não importam ao usuário (0-60%) e concentre as medições onde é mais relevante (80-100%).
Adapte-se ao contexto. Bateria baixa, inatividade longa, erros no servidor — cada contexto tem um custo diferente para fazer uma requisição. Seu polling deve refletir isso.
Tenha um modo zero. Se não há atividade, não faça nada. Literalmente nada. Nem um timer longo. Nada. Um evento no filesystem ou na rede irá acordar você quando for necessário.
O guilty pleasure
Vou ser honesto: precisava de um filtro de Kalman para um app de barra de menu que mostra uma porcentagem? Provavelmente não. Um par de if com heurísticas teria resolvido 80% do problema.
Mas os 20% restantes fazem a diferença entre um app que “mais ou menos funciona” e um que o usuário pode deixar rodando por 12 horas sem perceber que está ali. Entre 857 de Energy Impact e menos de 5. Entre 960 requisições por dia e 30.
Às vezes, o prazer culposo da sobreengenharia é exatamente o que o problema precisava. Só que você não sabia disso até montar tudo.
E se alguém na Anthropic um dia olhar os logs do servidor e ver que meu app faz 30 requisições por dia em vez de mil, espero que pense: “Esse cara mandou muito bem”. E não corte minha conexão.