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.