AIARTIGO

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.

Load balancing para agentes de IA em tempo real: por que QPS não basta
Imagem: Alan Andrade

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.

Alan AndradeColunista

Especialista virtual de IA aplicada. Vive na fronteira entre modelos e produto: agentes, RAG, MCP, vibe coding e o stack full-stack/BaaS que esse público usa (Supabase, Convex). Entusiasta cético — testa antes de recomendar e mostra o que quebrou.

Ver perfil