Rate Limiting e Load Shedding

TL;DR

Os dois modos de dizer não na entrada, e a diferença entre eles é o critério. Rate limiting recusa por cota: você tem N por minuto, é um contrato conhecido de antemão, e a recusa é justa e previsível. Load shedding recusa por pressão: o sistema está no limite agora, e descarta o que puder para continuar servindo o resto. O primeiro protege contra abuso e vizinho barulhento; o segundo é o que impede o colapso total quando a carga legítima excede a capacidade. Servir 80% bem é melhor que servir 100% mal — e essa frase é o padrão inteiro.

O recorte desta nota

Aqui os dois padrões como decisão e o que sacrificam. Algoritmos e escala em System Design 3-04; limite como contrato de API (headers, 429, negociação com o cliente) em Comunicação 3-04; quotas no gateway gerenciado em Cloud 14-03.

Servir 80% bem é melhor que servir 100% mal

O tráfego dobra numa promoção. Sua capacidade não dobrou.

Sem defesa na entrada, todas as requisições são aceitas. As filas internas crescem, o tempo de resposta sobe de 200 ms para 12 segundos, e algo perverso acontece: os clientes começam a desistir e a tentar de novo, o que acrescenta ainda mais carga. Pior, o servidor continua processando requisições cujos clientes já foram embora — trabalho puro e desperdiçado, executado com recursos que faltam para quem ainda espera.

O resultado é o pior de todos os mundos: ninguém é atendido, e o sistema gasta 100% da sua capacidade produzindo respostas que ninguém recebe. Ele não caiu — ficou inútil, que é operacionalmente pior, porque nem sequer aciona os alarmes de indisponibilidade.

A alternativa é aceitar menos e servir bem. Rejeitar 20% em 5 milissegundos, com uma resposta clara, e atender os 80% restantes em 200 ms. É uma escolha desconfortável — alguém é sacrificado explicitamente, por decisão sua — e é quase sempre melhor que a degradação uniforme.

Cota × pressão: os dois critérios


graph TD
    R["Requisição chega"] --> RL{"<b>Rate limit</b><br/>este cliente já passou<br/>da cota dele?"}
    RL -->|"sim"| E1["429 + Retry-After<br/><i>previsível, é o contrato</i>"]
    RL -->|"não"| LS{"<b>Load shedding</b><br/>o sistema está<br/>saturado agora?"}
    LS -->|"sim, e isto é<br/>baixa prioridade"| E2["503 · descarte<br/><i>situacional, é emergência</i>"]
    LS -->|"não"| OK["processa"]

    style E1 fill:#F5A623,color:#000
    style E2 fill:#D0021B,color:#fff
    style OK fill:#4A90D9,color:#fff

Rate limiting é contratual. O limite existe antes do problema, é publicado, e o cliente pode se planejar. Serve para proteger contra abuso, para isolar o vizinho barulhento e para monetizar (planos por volume). A recusa é justa por construção: quem passou da cota sabia qual era.

Load shedding é situacional. Não há cota — há um sistema saturado neste instante. O critério não é “quem é você”, é “o que é mais dispensável agora”. E isso exige uma noção de prioridade: rejeitar a listagem de produtos antes do checkout; rejeitar um relatório antes de um pagamento; nunca rejeitar o health check.

A distinção prática mais útil: um cliente bem comportado nunca deveria ser rejeitado por rate limiting, mas pode ser rejeitado por load shedding — e é por isso que os dois códigos de resposta e as duas mensagens devem ser diferentes.

O que se sacrifica

Clientes legítimos na cauda. Alguém que faria uma requisição perfeitamente válida recebe uma recusa. No rate limiting, é quem estourou a cota — previsível, mas ainda assim pode ser um caso de uso real e legítimo que a cota não previu. No load shedding, é quem teve o azar de chegar durante a saturação, ou de estar na classe de menor prioridade.

A assimetria de quem é sacrificado é uma decisão de valor, não técnica. Se você prioriza por plano, clientes pequenos caem primeiro. Se prioriza por tipo de operação, funcionalidades secundárias caem. Se não prioriza, cai quem chegar — o que é “justo” no sentido de aleatório e ruim no sentido de sacrificar um checkout para servir uma busca. Essa escolha deveria ser explícita e conhecida pelo negócio, e raramente é.

Sacrifica também o benefício da dúvida. Um limite mal calibrado bloqueia uso legítimo — e o cliente afetado quase nunca entende por quê, especialmente se a resposta não disser quando ele pode voltar.

Armadilhas comuns

Rejeitar sem dizer quando voltar

O que acontece: a resposta é um 429 seco. O cliente, sem informação, retenta imediatamente — e agora você tem mais carga vinda exatamente de quem você acabou de recusar. Por quê: implementou-se a recusa, que é a parte que protege o servidor, sem a parte que orienta o cliente. Como evitar: 429 com Retry-After, e headers informando limite, restante e janela. Uma recusa que orienta o cliente reduz a carga; uma que não orienta a aumenta. O contrato completo está em Comunicação 3-04.

Limitar pela chave errada

O que acontece: o limite é por IP, e um cliente corporativo inteiro atrás de um NAT compartilha uma cota — usuários legítimos se bloqueiam mutuamente. Ou o serviço está atrás de proxy e todas as requisições parecem vir do mesmo IP, o que ou bloqueia todo mundo ou não limita ninguém. Por quê: o IP é a chave mais fácil de obter e a que menos corresponde a “quem é o cliente”. Como evitar: limite por identidade (chave de API, conta, tenant) sempre que houver. Onde só houver IP, use o cabeçalho de encaminhamento correto e valide que ele é confiável na sua topologia.

Shedding que derruba o que não podia cair

O que acontece: o descarte é uniforme e atinge o health check — a plataforma conclui que a instância está morta e a reinicia, reduzindo a capacidade durante a sobrecarga. Ou atinge o webhook de confirmação de pagamento, e o dinheiro fica em limbo. Por quê: o shedding foi implementado como percentual sobre o total, sem noção de prioridade. Como evitar: classes de prioridade explícitas, com uma lista curta do que nunca é descartado — health check, autenticação, e as operações críticas de negócio. Shedding sem prioridade é sorteio, e o sorteio vai eventualmente tirar o número errado.

Como explicar em inglês

“These are the two ways of saying no at the door, and the difference is the criterion. Rate limiting refuses by quota — you get N per minute, it’s a published contract, and the rejection is predictable and fair. Load shedding refuses by pressure — the system is saturated right now, so it drops whatever it can to keep serving the rest. The reasoning behind shedding is that serving eighty percent well beats serving a hundred percent badly: without it, queues grow, latency goes to twelve seconds, clients give up and retry, and you end up spending your entire capacity producing responses nobody receives. The part that needs a real decision is priority — dropping uniformly will eventually drop your health checks, which gets your instances restarted in the middle of an overload. And a 429 without Retry-After actively increases load, because the client just tries again immediately.”

PTEN
limitação de taxarate limiting
estrangulamentothrottling
descarte de cargaload shedding
cotaquota
balde de fichastoken bucket
pressão de retornobackpressure
classe de prioridadepriority class

O que vem a seguir

Recusar protege o sistema, mas não melhora o atendimento de quem passou. O próximo padrão ataca o outro lado: não fazer o trabalho de novo — o que reduz carga na origem e, de quebra, permite continuar servindo mesmo quando ela está fora.

Veja também

Fontes

  • Michael NygardRelease It! (2ª ed., 2018) — shed load e o handshake de capacidade entre serviços.
  • Google SRE BookHandling Overload — load shedding, criticidade de requisições e o custo de trabalho desperdiçado.
  • MicrosoftThrottling pattern — a ficha do catálogo Azure.
  • IETFRFC 6585 — o status 429 Too Many Requests.