ResiliênciaIntermediário8 min de leitura5 seçõesanos 1980, Data aproximada

Rate limiting

Capacidade é finita, e demanda não pede licença. Limitar a taxa é escolher conscientemente quem é recusado quando a demanda passa do que se aguenta — em vez de deixar a sobrecarga escolher, degradando o serviço para todo mundo ao mesmo tempo.

Em 10 segundos

Quando a demanda passa da capacidade, alguém vai ser recusado. Rate limiting é decidir isso de propósito — por cliente, por rota, por peso — recusando cedo e barato, e dizendo quando voltar. Sem ele, a sobrecarga decide, e ela degrada todo mundo de uma vez.

Antes de ter nome

Moldar a vazão para não afogar o próximo é a mesma ideia do controle de tráfego em redes ATM — limitar o fluxo antes que ele estoure o buffer.

  • #resiliencia
  • #throttling
  • #token-bucket
  • #capacidade
  • #producao
Analogia

A catraca do estádio

O estádio tem 40 mil lugares. A catraca não existe para maltratar quem chegou por último — existe porque deixar entrar 60 mil não cria 20 mil lugares novos: cria um tumulto em que ninguém assiste ao jogo. Recusar na porta é rápido, previsível e reversível. Descobrir a lotação lá dentro não é nenhuma das três coisas.

01Conceito

Alguém sempre é recusado

A pergunta não é *se* haverá recusa quando a demanda passar da capacidade — é *quem* recusa e *quando*. Sem limite explícito, a recusa acontece lá no fundo, depois de a requisição já ter consumido conexão, CPU e uma consulta ao banco.

É a pior forma de recusar: cara para quem serve e imprevisível para quem chama.

Sob sobrecarga, um sistema sem limite não fica lento de forma uniforme — ele entra em colapso. As filas crescem, a latência sobe, os clientes começam a estourar timeout e a repetir, o que aumenta a carga, o que aumenta a latência. O nome disso é colapso congestivo, e a característica dele é que remover a carga extra não devolve o desempenho de imediato: o sistema fica preso processando trabalho que ninguém mais espera.

Rate limiting corta esse ciclo no ponto mais barato possível: na borda, antes de gastar qualquer recurso caro. Uma requisição recusada em 1ms na entrada custa aproximadamente nada; a mesma requisição recusada depois de 40ms de banco custou uma conexão, uma consulta e uma vaga no pool.

Há uma distinção que vale fixar. Rate limiting protege da *quantidade* de pedidos por unidade de tempo. Bulkhead protege da *concorrência simultânea* de um tipo de pedido. Circuit Breaker protege de insistir com uma dependência quebrada. Os três costumam ser confundidos porque todos recusam trabalho — mas medem coisas diferentes e falham em situações diferentes.

Sobre algoritmos: janela fixa é a implementação mais simples e tem um defeito conhecido na virada — com limite de 100 por minuto, um cliente manda 100 às 10:00:59 e mais 100 às 10:01:00, ou seja, 200 em dois segundos sem violar a regra. Janela deslizante corrige isso ao custo de mais memória. Token bucket costuma ser a melhor escolha padrão: permite rajada até o tamanho do balde e alisa o restante, que é exatamente o comportamento que tráfego real tem.

Como se encaixa
  1. Cliente
  2. requisiçãoIdentifica a chave
  3. cliente, IP, tenant, rotaTem token disponível?
  4. sim — consome 1 tokenAplicação
  5. não → 429 + Retry-AfterResposta
A decisão acontece antes de a requisição tocar em qualquer recurso caro. Ao recusar, o Retry-After é o que impede os clientes recusados de voltarem todos no mesmo instante.
02No mundo real

Como isso aparece na prática

O cliente com o laço infinito

Cenário

Uma integração de um cliente tem um bug: em vez de paginar, ela repete a primeira página num laço. Passa a mandar 4 mil requisições por minuto contra uma média histórica de 30, e a latência sobe para todos os outros clientes.

Como o padrão entra

Token bucket por chave de API, com capacidade de 200 e reposição de 20 por segundo. A integração com bug passa a receber 429 com Retry-After, e a plataforma volta ao normal em segundos.

Trade-off: O cliente com bug fica parcialmente quebrado até corrigir o laço — o que é exatamente o objetivo, mas exige um canal de aviso e um limite generoso o bastante para não punir uso legítimo intenso.

O endpoint de envio de e-mail

Cenário

Uma rota de 'recuperar senha' dispara e-mail a cada chamada. Um atacante a usa para bombardear a caixa de entrada de uma vítima, e o provedor de e-mail ameaça suspender a conta por reputação.

Como o padrão entra

Limite por endereço de destino e por IP de origem, bem mais apertado que o limite geral da API, com custo maior por requisição no balde.

Trade-off: Um usuário legítimo que peça recuperação três vezes seguidas é bloqueado por alguns minutos. É um atrito real, aceito porque a alternativa é perder a reputação de envio do domínio inteiro.

03Cuidado

Onde isto dá errado

  1. Recusar sem dizer quando voltar

    Um 429 sem Retry-After obriga o cliente a adivinhar — e todos adivinham parecido, o que os faz voltar juntos. A resposta de recusa precisa carregar o momento de retorno, e idealmente o limite e o quanto resta.

  2. Janela fixa e a rajada na virada

    Com limite de 100 por minuto, cabe mandar 100 no último segundo da janela e mais 100 no primeiro da seguinte: 200 em dois segundos, sem violar a regra escrita. Janela deslizante ou token bucket resolvem; janela fixa é a mais fácil de implementar e a mais fácil de furar.

  3. Contar requisição em vez de custo

    Um GET de 200 bytes e um upload de 50 MB contam igual num limitador ingênuo. Quando o custo real varia em ordens de grandeza, o limite precisa ser por peso — tokens proporcionais ao trabalho, não uma unidade por chamada.

  4. Limitar por IP num mundo com NAT e proxy

    Uma empresa inteira pode sair por um IP só, e uma botnet distribui-se por milhares. Limitar por IP pune escritório e não incomoda atacante. Onde houver identidade — chave de API, conta, sessão —, é ela que deve ser a chave.

  5. Contador local em serviço com várias réplicas

    Cada instância mantendo o próprio contador multiplica o limite pelo número de réplicas: dez instâncias com teto de 100 permitem 1000. Ou o estado é compartilhado, ou o teto por instância é dividido — e aí o autoscaling muda o limite sem ninguém perceber.

  6. Limitar o sintoma em vez de corrigir a causa

    Racionar acesso a um endpoint lento porque ele derruba o banco esconde o problema real, que é a consulta. O limite compra tempo para corrigir; quando vira solução permanente, o teto só desce com o tempo.

04Implementação

Token bucket com o relógio injetado

// Janela fixa: simples e com um defeito grave na virada.
// Com limite de 100/min, um cliente manda 100 as 10:00:59
// e mais 100 as 10:01:00 — 200 em dois segundos, dentro da regra.

// Token bucket: permite rajada controlada e alisa o resto.
interface Balde {
  tokens: number;
  /** ultimo instante em que reabastecemos. */
  atualizadoEm: number;
}

interface Config {
  capacidade: number;   // tamanho da rajada permitida
  porSegundo: number;   // taxa de reposicao
}

// 'agora' entra por parametro: funcao pura, testavel sem relogio.
function permitir(b: Balde, cfg: Config, agora: number, custo = 1): boolean {
  const decorrido = (agora - b.atualizadoEm) / 1000;
  b.tokens = Math.min(cfg.capacidade, b.tokens + decorrido * cfg.porSegundo);
  b.atualizadoEm = agora;

  if (b.tokens < custo) return false;
  b.tokens -= custo;
  return true;
}

// Ao recusar, diga QUANDO voltar. Sem isso o cliente vai adivinhar —
// e adivinhar errado significa todos voltando no mesmo instante.
function cabecalhos(b: Balde, cfg: Config) {
  return {
    "RateLimit-Limit": String(cfg.capacidade),
    "RateLimit-Remaining": String(Math.floor(b.tokens)),
    "Retry-After": String(Math.ceil((1 - b.tokens) / cfg.porSegundo)),
  };
}
05Decisão

Quando usar × quando evitar

Use quando

3
  • APIs públicas e qualquer superfície exposta a clientes que você não controla.
  • Recursos caros: e-mail, relatório, chamada a serviço pago por uso.
  • Isolamento entre tenants num serviço multiempresa.

Evite quando

3
  • Entre serviços internos de confiança, onde Bulkhead e fila servem melhor.
  • Como remendo permanente para consulta lenta.
  • Com contador local quando o serviço tem várias réplicas.

Testar o que ficou

6 armadilhas de Rate limiting viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • O 429 e o Retry-AfterO código HTTP existe só para isto: recusar por excesso de taxa e dizer quando voltar.
  • limit_req do NginxLimitação por taxa embutida no servidor web, antes de a requisição chegar na sua aplicação.
  • Cotas de APITodo provedor sério publica limite por minuto e por chave — é rate limiting virando contrato comercial.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Recusa de propósito quando passa do orçamento.
if (contador > LIMITE) return 429;

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Manter contadores por cliente e janela custa estado, ainda mais quando distribuído entre instâncias
  • Um limite mal calibrado ou recusa tráfego legítimo, ou não protege de verdade

Não vale se: o recurso aguenta qualquer pico plausível sem se degradar — limitar aí só adiciona atrito e estado sem proteger nada.

Onde isto já custou caro

Incidentes reais que este conceito explica.

Costuma ser confundido com

Onde isto aparece

Continue por aqui