Rate limiting sem prejudicar os usuários que você quer
Token bucket, identidade em vez de IP, limites por plano, modo sombra: como barrar abuso sem devolver 429 para quem paga.
A maioria dos rate limiters que eu audito foi escrita em uma tarde, depois de um incidente, por alguém irritado com um bot. Eles funcionam. Também devolvem 429 em silêncio para os melhores clientes da empresa, e ninguém percebe porque os melhores clientes raramente reclamam. Eles só tentam de novo, ou vão embora.
Um rate limiter tem uma função: proteger o sistema de abuso e de acidentes. Não é um mecanismo de punição. Se um usuário pagante bate no limite em uso normal, o limite está errado, não o usuário.
Escolha o algoritmo para o tráfego que você realmente tem
Dois algoritmos resolvem quase tudo. O token bucket dá a cada cliente um balde que enche a uma taxa fixa, digamos 10 tokens por segundo, até uma capacidade de, digamos, 50. Cada request gasta um token. Essa capacidade é a sua margem de burst: um cliente pode disparar 50 requests de uma vez depois de ficar parado, e depois se acomoda em 10 por segundo. Usuários reais são assim, em rajadas. Um carregamento de página dispara uma dúzia de chamadas e depois nada por um minuto. O token bucket modela isso bem.
A sliding window conta os requests dos últimos N segundos e rejeita quando a contagem passa do limite. É mais rígida e mais fácil de explicar na documentação ("100 requests por minuto"), mas não tem noção natural de burst, então um usuário que recarrega um dashboard três vezes pode ser bloqueado por algo completamente razoável.
Meu padrão: token bucket para APIs voltadas ao usuário, sliding window para o que precisa ser limitado de forma estrita, como tentativas de senha ou envio de SMS. Evite janelas fixas que zeram na virada do minuto; elas permitem o dobro do limite na fronteira e os clientes aprendem a explorar isso.
Limite por identidade, não por IP
Limite por IP é o erro mais comum que vejo. Um escritório inteiro, um campus universitário, um hospital ou o NAT de uma operadora de celular podem compartilhar um único IP público. Limite por IP e você bloqueia mil pessoas legítimas porque uma delas escreveu um loop.
Limite por chave de API, id de usuário ou sessão quando tiver um. Use IP só em endpoints não autenticados, e deixe esses limites generosos, porque você não faz ideia de quem está atrás do endereço. Se precisar combinar os dois, faça do IP a rede larga e da identidade a rede fina.
Nem todo endpoint merece o mesmo limite
Um limite global por usuário é uma ferramenta grosseira. Leituras e escritas têm custos diferentes e perfis de abuso diferentes. Ler um perfil é barato e idempotente; criar um pedido mexe em estoque, pagamento e e-mail. Dê às escritas um orçamento separado e mais apertado.
Depois vá além e proteja especificamente os endpoints caros. Busca com curinga, exportação de relatórios, geração de PDF, qualquer coisa que se espalha por vários serviços. É ali que um loop acidental de um cliente derruba você, e eles precisam do próprio balde, dimensionado por custo, não por contagem de requests. Alguns times atribuem um custo por endpoint e descontam de um único orçamento; funciona bem quando os custos são honestos.
Separe os limites por plano. Free, pago e enterprise não deveriam compartilhar números, e o plano deve ser legível a partir da mesma identidade pela qual você limita. Isso transforma o rate limiting de muro defensivo em funcionalidade de produto.
Rejeite de um jeito que o cliente consiga cooperar
Devolva 429, não 403 nem 500. Inclua um header Retry-After com segundos e, de preferência, os headers RateLimit padrão, para que clientes bem comportados desacelerem antes de bater no muro. Um cliente que sabe quando tentar de novo para de martelar; um cliente que recebe um erro opaco tenta de novo imediatamente, em loop, e piora o incidente.
Nunca limite health checks, e tenha cuidado com webhooks de parceiros que você mesmo pediu para enviar tráfego.
Observe antes de aplicar
O conselho mais importante aqui: rode o limiter em modo sombra primeiro. Calcule a decisão, registre, conte, mas não rejeite. Depois de uma semana você sabe exatamente quem teria sido bloqueado e por quê. Quase sempre a lista inclui seu maior parceiro de integração, um dashboard interno e uma versão do app mobile com bug de retry. Corrija isso, ajuste os números, e só então ligue a aplicação de verdade.
Mantenha as métricas do modo sombra rodando depois disso. Quando o limite de alguém precisar mudar, você quer ter os dados antes do ticket de suporte.