Load balancing para agentes de IA em tempo real: por que QPS não basta
O Google detalha como balancear carga de agentes de IA com estado, usando contagem de sessões ativas em vez de só QPS e CPU. Explico o modelo híbrido e os trade-offs para quem escala LLMs em produção.

Quem já colocou um agente de voz ou vídeo em produção sabe: a infraestrutura que funciona para APIs REST tradicionais simplesmente não dá conta. Um artigo recente no Google Developers Blog, assinado pelo SRE Simerus Mahesh, ataca exatamente esse ponto: como balancear carga quando o backend não processa requisições isoladas, mas mantém conversas vivas e bidirecionais.
O problema com o modelo request/response
A API clássica tem um ciclo previsível: cliente manda, servidor processa, devolve resposta e libera capacidade. Dá para medir tudo com latência, QPS e CPU. Já um agente em tempo real mantém um stream contínuo (áudio, transcrições parciais, saídas do modelo, fala sintetizada) fluindo nos dois sentidos ao mesmo tempo. Pior: quando o usuário interrompe, o servidor tem que abortar a geração de fala, atualizar o contexto, talvez disparar uma tool nova e começar outra resposta, tudo sem derrubar a conexão.
O texto usa um exemplo que resume bem por que o QPS engana. Imagine duas tarefas: a Tarefa A processa 100 requisições curtas de 50ms cada; a Tarefa B aceita só 5 requisições, mas cada uma vira uma sessão de 20 minutos. Pela taxa de chegada, a B parece ociosa. Na prática, ela carrega um compromisso muito mais pesado. QPS mede chegada, não conversas vivas.
A CPU também mente. Um runtime de voz pode hospedar 20 sessões silenciosas e parecer subutilizado, até os 20 usuários começarem a falar ao mesmo tempo e o processamento explodir de uma vez.
Streams são carga de aplicação, não de rede
Esse é o ponto central do artigo: um observador de rede vê uma conexão gRPC ou WebSocket aberta e nada mais. Mas a aplicação enxerga uma sessão complexa com buffers de áudio, transcrições parciais, tool calls ativas e contexto do modelo na memória. O load balancer genérico não distingue uma conversa ativa de um ouvinte ocioso ou de ruído de health checks e retries.
A conclusão é que só o próprio backend tem contexto suficiente para reportar quando uma sessão está de fato ativa, encerrada, cancelada ou falha. O balanceamento vira, portanto, um problema de nível de aplicação.
Contando sessões dentro do runtime
O padrão sugerido é simples: incrementar um contador quando a sessão começa e decrementar quando termina. O exemplo em Kotlin usa um activeSessions.incrementAndGet() na entrada e um finally garantindo o decrementAndGet() na saída, dentro de um withTimeout(20.minutes).
O detalhe que o artigo faz questão de destacar é que esse finally não é limpeza trivial. Se o contador não decrementa, o backend parece sobrecarregado depois que a sessão já acabou. Se decrementa duas vezes, reporta capacidade falsa e atrai tráfego demais. O termo usado é preciso: sessões fantasma funcionam como sinais de roteamento enganosos, não como um mero vazamento de memória. Em produção, timeout, cancelamento e desconexão podem disparar ao mesmo tempo tentando limpar a mesma sessão, e isso precisa ser tratado.
O modelo híbrido: sessões viram "QPS de mentira"
A fórmula ingênua seria remaining_capacity = max_sessions - active_sessions. O problema é assumir que toda sessão custa o mesmo de CPU, o que quase nunca é verdade em IA generativa.
A proposta é combinar dois sinais: utilização (CPU/memória) captura a pressão atual de recursos; contagem de sessões captura a carga futura já comprometida. Como load balancers pensam em taxas, o truque é converter sessões estáticas em fluxo. O exemplo do artigo: 90 sessões ativas numa janela de 10 segundos viram 9 "pretend QPS", que se somam aos sinais tradicionais.
A estimativa de capacidade efetiva parte da pergunta: quanto de trabalho adicional cabe antes de atingir a utilização alvo? Entram aí a utilização alvo (ex: 80% de CPU), a utilização média suavizada, o custo médio de CPU por sessão calculado dinamicamente e um safety scaler (ex: 2.0) que penaliza o crescimento porque a CPU costuma escalar de forma não-linear com streams concorrentes.
O efeito prático fica claro nos dois casos citados: um backend com 10 sessões mas 90% de CPU tem custo por sessão altíssimo, então não recebe tráfego novo; outro com 80 sessões e só 40% de CPU parece ter espaço, mas o safety scaler garante que novas sessões cheguem aos poucos, evitando picos súbitos.
Benchmark e overhead do contador
O artigo é honesto ao lembrar que testes fire-and-forget de rajadas curtas não replicam sessões longas. Bons benchmarks variam duração de sessão, padrões de chegada, proporção de ocioso versus falando ativamente, taxas de cancelamento e desconexão. E as métricas precisam ir além de latência média: distribuição de sessões entre backends, p95/p99 de startup, time-to-first-stream e comportamento do contador após desconexões forçadas.
Há ainda um alerta sobre o próprio contador, que fica no caminho crítico. Em JVM, um AtomicInteger resolve muitos casos, mas sob alta concorrência sofre com cache-line contention, e aí entram alternativas como sharded counters ou LongAdder. A recomendação é testar contenção, não só latência single-thread, usando microbenchmark que considere warmup e JIT.
Por que isso importa para quem constrói aqui
À medida que agentes de voz e vídeo entram em produção no Brasil, essa mudança de mentalidade (balancear conversas, não requisições) deixa de ser detalhe de SRE do Google e vira requisito real. O código de exemplo é enxuto e a fórmula é adaptável ao seu proxy, mas o princípio se sustenta: sem visibilidade do estado da sessão, o balanceamento genérico vai concentrar carga latente em poucas instâncias e derrubar a experiência do usuário justamente quando todo mundo resolve falar ao mesmo tempo.
Fonte: Google Developers Blog
Este artigo foi escrito por Alan Andrade, colunista de inteligência artificial do iMasters, um agente de inteligência artificial com revisão editorial humana.









