Rate Limiting
Rate limiting é uma técnica fundamental de controle de tráfego que limita o número de requisições que um cliente pode fazer a uma API ou serviço web dentro de uma janela de tempo específica, protegendo a infraestrutura contra sobrecarga, ataques de negação de serviço (DoS/DDoS), abuso automatizado, scraping malicioso e uso inadequado de recursos computacionais limitados. Em um cenário onde APIs modernas servem milhões de requisições por segundo vindas de aplicações móveis, integrações de parceiros, bots legítimos e potencialmente atacantes maliciosos, a ausência de rate limiting adequado pode resultar em custos operacionais explosivos, degradação severa de performance para usuários legítimos, vulnerabilidades a ataques de credential stuffing e brute force, e até mesmo indisponibilidade total do serviço. A implementação eficaz de rate limiting requer compreensão profunda de diferentes algoritmos (token bucket, leaky bucket, fixed window, sliding window), considerações sobre arquitetura distribuída onde múltiplos servidores precisam compartilhar contadores de taxa, estratégias de identificação de clientes (IP, API key, JWT, session), políticas diferenciadas por tier de usuário (free, premium, enterprise), e mecanismos de comunicação clara através de HTTP headers padronizados que informam clientes sobre limites, consumo atual e tempo de reset. Este artigo explora algoritmos, padrões de implementação, ferramentas e best practices para construir sistemas robustos de rate limiting.
Algoritmos de Rate Limiting
1. Token Bucket (Mais Comum)
# Conceito: Bucket com capacidade máxima de tokens
# Tokens são adicionados a taxa constante
# Cada request consome 1 token
# Se bucket vazio, request é rejeitado
Capacidade: 100 tokens
Taxa de recarga: 10 tokens/segundo
Vantagens:
- Permite bursts controlados (bucket cheio)
- Simples de implementar
- Flexível para diferentes padrões de tráfego
Desvantagens:
- Pode permitir bursts que sobrecarregam sistema
- Precisa tracking de último refill
Implementação:
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillRate;
this.lastRefill = Date.now();
}
consume(count = 1) {
this.refill();
if (this.tokens >= count) {
this.tokens -= count;
return true;
}
return false;
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const tokensToAdd = elapsed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
2. Leaky Bucket
# Conceito: Queue com vazamento constante
# Requests entram no bucket (queue)
# Processados a taxa constante (leak)
# Se queue cheia, requests são rejeitados
Vantagens:
- Smooths out bursts (traffic shaping)
- Output rate constante e previsível
- Protege backend de spikes
Desvantagens:
- Pode adicionar latência (queueing)
- Complexidade de implementação
Uso:
- Traffic shaping
- Network gateways
- Quando output rate constante é crítico
3. Fixed Window Counter
# Conceito: Contador por janela de tempo fixa
# Exemplo: 100 requests por minuto
# Reset no início de cada minuto (XX:00, XX:01, XX:02...)
Vantagens:
- Extremamente simples
- Memory efficient
- Fácil de entender
Desvantagens:
- Edge case: 200 requests em 1 segundo
(100 no final do minuto 1, 100 no início do minuto 2)
- Permite bursts no limite das janelas
Implementação Redis:
INCR user:123:2024-01-15:14:30
EXPIRE user:123:2024-01-15:14:30 60
GET user:123:2024-01-15:14:30 # Se > 100, reject
4. Sliding Window Log
# Conceito: Log de timestamps de requests
# Remove requests fora da janela
# Conta requests dentro da janela deslizante
Vantagens:
- Precisão perfeita
- Sem edge cases de fixed window
- Distribui rate uniformemente
Desvantagens:
- Memory intensive (armazena todos timestamps)
- Performance degrada com high traffic
Implementação Redis (Sorted Set):
ZADD user:123 <timestamp> <request-id>
ZREMRANGEBYSCORE user:123 0 <timestamp-60s> # Remove antigos
ZCARD user:123 # Count requests
Se > 100, reject
5. Sliding Window Counter (Hybrid)
# Conceito: Combina fixed window com sliding
# Usa contadores de janelas anteriores com peso
# Estima rate em sliding window
Exemplo: Limite 100/minuto
Janela atual (14:30): 70 requests
Janela anterior (14:29): 90 requests
Elapsed na janela atual: 40s (66.7%)
Estimativa: 90 * (1 - 0.667) + 70 = 30 + 70 = 100
Vantagens:
- Precisão próxima de sliding log
- Memory efficient (só 2 contadores)
- Suaviza bursts
Desvantagens:
- Estimativa (não exato)
- Mais complexo que fixed window
Distributed Rate Limiting
# Problema: Múltiplos servidores precisam compartilhar state
# Soluções:
1. Redis Centralizado (Mais Comum)
const redis = require('redis');
const client = redis.createClient();
async function checkRateLimit(userId) {
const key = \`rate:\$:\${getCurrentWindow()}\`;
const count = await client.incr(key);
if (count === 1) {
await client.expire(key, 60); // 60 seconds
}
return count <= 100; // Limit: 100/min
}
2. Redis Lua Script (Atomic)
const luaScript = \`
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('incr', key)
if current == 1 then
redis.call('expire', key, ARGV[2])
end
if current > limit then
return 0
end
return 1
\`;
3. Sticky Sessions + Local Counters
- Route user sempre para mesmo server
- Counter local no servidor
- Problema: Não funciona bem com auto-scaling
4. Gossip Protocol
- Servidores compartilham state via gossip
- Eventual consistency
- Mais complexo, usado em high scale
HTTP Headers Padrões
# Standards (RFCs)
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000 # Unix timestamp
# Quando limite excedido
HTTP/1.1 429 Too Many Requests
Retry-After: 60 # Segundos até retry
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000060
{
"error": "Rate limit exceeded",
"retryAfter": 60,
"limit": 100
}
# GitHub style (mais informativo)
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
X-RateLimit-Reset: 1372700873
X-RateLimit-Used: 1
X-RateLimit-Resource: core
Implementação com Express.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const client = redis.createClient();
// Basic rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false,
message: 'Too many requests, please try again later.'
});
app.use('/api/', limiter);
// Redis-based distributed rate limiting
const distributedLimiter = rateLimit({
store: new RedisStore({
client: client,
prefix: 'rate-limit:',
}),
windowMs: 60 * 1000,
max: 10,
standardHeaders: true,
});
// Different limits per route
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Stricter for auth endpoints
skipSuccessfulRequests: true, // Don't count successful logins
});
app.post('/api/login', authLimiter, loginHandler);
// Custom key function (rate limit by user ID instead of IP)
const userLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
keyGenerator: (req) => req.user.id, // Requires auth middleware
});
Tiered Rate Limiting
// Different limits based on user tier
function getRateLimit(user) {
const tiers = {
free: { windowMs: 3600000, max: 100 }, // 100/hour
basic: { windowMs: 3600000, max: 1000 }, // 1000/hour
premium: { windowMs: 3600000, max: 10000 }, // 10k/hour
enterprise: { windowMs: 3600000, max: 100000 } // 100k/hour
};
return tiers[user.tier] || tiers.free;
}
app.use(async (req, res, next) => {
const user = await getUserFromToken(req);
const limits = getRateLimit(user);
const limiter = rateLimit({
...limits,
keyGenerator: () => user.id,
});
limiter(req, res, next);
});
Ferramentas e Serviços
- Redis: Distributed counters, expire automático
- Kong: API Gateway com rate limiting plugin
- Nginx rate limiting: limit_req_zone, limit_conn_zone
- Cloudflare: CDN-level rate limiting
- AWS API Gateway: Built-in throttling
- express-rate-limit: Express middleware
- Tyk: Open-source API gateway
Best Practices
- Escolha algoritmo adequado: Token bucket para APIs gerais, sliding window para precisão
- Limites diferenciados: Auth endpoints mais restritivos, read-only mais permissivos
- Comunicação clara: Headers informativos, mensagens de erro úteis
- Whitelist: IPs de parceiros confiáveis, health checks
- Monitoring: Alert quando usuários atingem limites frequentemente
- Graceful degradation: Retorne cached data se possível
- Distributed state: Use Redis para multi-server deployments
- Cost-based limiting: Operações caras consomem mais tokens
Recomendações
Para APIs modernas, implemente token bucket com Redis para distributed rate limiting. Use sliding window counter quando precisar precisão sem overhead de memory. Configure limites diferenciados: 5 req/min para login, 100 req/min para leitura, 10 req/min para operações write. Sempre retorne headers informativos e implemente retry logic com exponential backoff nos clientes.
