Estávamos limitando a taxa dos nossos próprios serviços
A API do Mythex permite 300 requisições por minuto a partir de um endereço de rede. Esse limite existe para nos proteger de uma pessoa ou script martelando a API. Em produção, ele passou a recusar os nossos próprios serviços, a parte da nossa borda que serve os apps publicados e o serviço que roda o agente de IA, porque cada um deles acessa a API a partir de poucos endereços. As requisições que carregam a nossa credencial interna agora estão isentas, e todos os outros continuam limitados exatamente como antes.
O sintoma
A API começou a responder algumas chamadas internas nossas com 429 Too Many Requests. Três tipos de chamada foram atingidos, e cada um importa para quem usa o Mythex:
- Consultar qual app vive em um domínio personalizado. Quando um visitante abre um app publicado no próprio domínio, a nossa borda pergunta à API onde fica aquele site. Um 429 aí é um visitante que não consegue acessar um site.
- Salvar um turno do agente em execução. Enquanto o agente trabalha, o progresso é salvo conforme avança, para que uma resposta sobreviva a uma atualização da página ou a uma conexão caída. Um 429 aí é progresso que não foi salvo.
- Verificar o acesso a uma pré-visualização. A pré-visualização de um app que você está construindo tem uma barreira de acesso, e essa barreira consulta a API. Um 429 aí é uma pré-visualização que não abre.
Nenhuma dessas chamadas era de pessoas usando a API. Todas eram de nós mesmos.
Por que aconteceu
Um limite de taxa por endereço faz uma suposição: um endereço é, aproximadamente, um chamador. Para pessoas, isso é quase verdade. Para a nossa própria infraestrutura, está muito errado.
- O serviço de borda que serve os apps publicados e as pré-visualizações acessa a API a partir de alguns poucos endereços de saída compartilhados. Cada visitante de cada app publicado, e cada verificação de pré-visualização, chega parecendo ter vindo de um desses poucos.
- O orquestrador, o serviço que roda os turnos do agente, acessa a API a partir de um único endereço de contêiner. Cada turno em execução, de cada usuário, compartilha esse endereço.
Assim, o orçamento pensado para uma pessoa estava sendo dividido entre todo o tráfego de um serviço inteiro. Esses serviços o consumiram juntos, e o limitador fez o que foi configurado para fazer: recusou.
A correção
Nossos próprios serviços já provam quem são. Cada chamada interna carrega um token interno, e toda rota da seção interna da API verifica esse token antes de fazer qualquer coisa.
O limitador de taxa agora usa a mesma verificação. Uma requisição cujo token corresponde ao interno entra em uma lista de permitidos e não é contada no orçamento por endereço. Todo o resto é contado exatamente como antes: 300 requisições por minuto por endereço.
Dois detalhes impediram que isso virasse um buraco:
- Um token errado não recebe passe livre. A lista de permitidos só corresponde ao token interno real. Uma requisição com um token inventado é contada e limitada como qualquer outra, e as rotas internas continuam rejeitando-a com
401 Unauthorized. - O limite em si não mudou. Não aumentamos o número para abrir espaço. Aumentá-lo teria escondido o problema até o tráfego crescer de novo, e enfraqueceria a proteção no caso para o qual o limite existe.
Movemos as configurações do limitador para uma pequena função e escrevemos testes contra ela. Um envia 20 requisições com o token interno a um limitador configurado para permitir 5, e espera que todas as 20 tenham sucesso. Outro envia requisições comuns a partir de um endereço e espera que as cinco primeiras tenham sucesso e as seguintes sejam recusadas. Um terceiro envia requisições com um token forjado e espera que sejam recusadas da mesma forma.
O que tiramos disso
- Limites carregam suposições. "Um endereço, um chamador" está embutido em um limite por endereço. Isso deixa de ser verdade assim que os seus próprios serviços ficam atrás de endereços compartilhados, e muitos ficam: balanceadores de carga, redes de borda, contêineres, redes de escritório.
- Distinga o seu próprio tráfego pela identidade, não pelo endereço. Endereços são por onde o tráfego chega, não quem o enviou. Nós já tínhamos uma credencial que prova que a chamada é nossa; o limitador simplesmente não olhava para ela.
- Não resolva aumentando o número. Um limite maior teria feito os 429 sumirem por um tempo, protegido menos e falhado de novo depois.
- Observe quem está sendo limitado. Um 429 que cai no seu próprio serviço é um relatório de bug sobre os seus limites, não um sinal de que o limitador está funcionando.
O que isso significa se você constrói no Mythex
Nada para mudar do seu lado. Apps publicados em domínios personalizados, turnos do agente e pré-visualizações não dependem mais de quão ocupado o resto do Mythex está. O tráfego do próprio app é assunto do seu app: o limite descrito aqui está na nossa API, não nos apps que você publica.
Se você mantém um backend próprio, a mesma armadilha vale para qualquer limite de taxa que você adicionar. Nossos guias sobre o que é uma API e a lista de verificação de segurança para apps criados com IA são um bom ponto de partida. E para outro caso de a nossa própria infraestrutura atrapalhar a si mesma, veja como chegamos a deploys com zero erros 502.