Ferramentas de desenvolvimento

Estratégias de Rate Limiting para APIs em Produção: Token Bucket, Leaky Bucket e Além

Rate limiting é essencial para proteger APIs de abuso, garantir alocação justa de recursos e gerenciar capacidade. Compreender diferentes algoritmos e seus trade-offs evita degradação de serviço e interrupções inesperadas.

13/03/20268 min de leituraDev tools
Estratégias de Rate Limiting para APIs em Produção: Token Bucket, Leaky Bucket e Além

Resumo executivo

Rate limiting é essencial para proteger APIs de abuso, garantir alocação justa de recursos e gerenciar capacidade. Compreender diferentes algoritmos e seus trade-offs evita degradação de serviço e interrupções inesperadas.

Ultima atualizacao: 13/03/2026

Introdução: Por que rate limiting importa

Toda API em produção enfrenta a tensão fundamental entre capacidade e demanda. Sem rate limiting, tráfego abusivo pode sobrecarregar sistemas, usuários legítimos experimentam degradação e custos de infraestrutura crescem incontrolavelmente. Rate limiting fornece as guardas que mantêm sistemas estáveis e justos.

Rate limiting é mais do que prevenir abuso—é sobre gerenciamento de capacidade. Ele garante comportamento previsível do sistema sob carga, habilita alocação justa de recursos e fornece a base para degradação graciosa quando limites são excedidos.

Escolher a estratégia correta de rate limiting depende dos seus requisitos: tolerância a burst, estrita, precisão e complexidade operacional. Compreender trade-offs evita implementar um algoritmo que funciona em desenvolvimento mas falha sob carga de produção.

Algoritmos de rate limiting

Algoritmo de token bucket

O algoritmo de token bucket permite bursts enquanto impõe limites de taxa a longo prazo. Tokens acumulam em um bucket a uma taxa fixa. Cada request consome um ou mais tokens. Se o bucket está vazio, requests são rejeitados.

typescriptclass TokenBucket {
  private tokens: number;
  private lastRefillTimestamp: number;
  private capacity: number;
  private refillRate: number; // tokens por segundo

  constructor(capacity: number, refillRate: number) {
    this.capacity = capacity;
    this.refillRate = refillRate;
    this.tokens = capacity;
    this.lastRefillTimestamp = Date.now();
  }

  consume(tokens: number = 1): boolean {
    this.refill();

    if (this.tokens >= tokens) {
      this.tokens -= tokens;
      return true;
    }

    return false;
  }

  // Calcula tempo de espera antes do próximo request
  getWaitTime(tokens: number = 1): number {
    this.refill();

    if (this.tokens >= tokens) {
      return 0;
    }

    const needed = tokens - this.tokens;
    return (needed / this.refillRate) * 1000; // milissegundos
  }

  private refill(): void {
    const now = Date.now();
    const timeSinceLastRefill = (now - this.lastRefillTimestamp) / 1000; // segundos

    const tokensToAdd = timeSinceLastRefill * this.refillRate;
    this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);

    this.lastRefillTimestamp = now;
  }
}

// Uso: Permite bursts de 10 requests, recarrega a 1 request/segundo
const rateLimiter = new TokenBucket(10, 1);

async function handleRequest(request: Request): Promise<Response> {
  if (!rateLimiter.consume()) {
    const waitTime = rateLimiter.getWaitTime();
    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'Retry-After': Math.ceil(waitTime / 1000).toString(),
        'X-RateLimit-Limit': '10',
        'X-RateLimit-Remaining': Math.floor(rateLimiter.tokens).toString(),
      }
    });
  }

  return await processRequest(request);
}

Características:

  • Permite bursts até capacidade do bucket
  • Limite de taxa a longo prazo forçado pela taxa de recarga
  • Eficiente em memória (contador único)
  • Simples de implementar e entender

Melhor para:

  • APIs que toleram tráfego burst
  • Aplicações voltadas ao usuário onde burstiness é esperada
  • Cenários requerendo implementação simples

Desvantagens:

  • Bursts podem ser esgotados rapidamente por clientes agressivos
  • Sem proteção contra ataques distribuídos através de múltiplos IPs

Algoritmo de leaky bucket

O leaky bucket suaviza tráfego processando requests a uma taxa constante, independente da taxa de entrada. Requests que excedem capacidade são enfileirados ou rejeitados.

typescriptclass LeakyBucket {
  private queue: Array<{request: Request, resolve: Function}> = [];
  private lastLeakTimestamp: number;
  private capacity: number;
  private leakRate: number; // requests por segundo

  constructor(capacity: number, leakRate: number) {
    this.capacity = capacity;
    this.leakRate = leakRate;
    this.lastLeakTimestamp = Date.now();
  }

  async process(request: Request): Promise<Response> {
    this.leak();

    if (this.queue.length >= this.capacity) {
      return new Response('Too Many Requests', {
        status: 429,
        headers: {
          'X-RateLimit-Limit': this.capacity.toString(),
          'X-RateLimit-QueueSize': this.queue.length.toString(),
        }
      });
    }

    return new Promise((resolve) => {
      this.queue.push({ request, resolve });
      this.processQueue();
    });
  }

  private leak(): void {
    const now = Date.now();
    const timeSinceLastLeak = (now - this.lastLeakTimestamp) / 1000; // segundos

    const requestsToProcess = Math.floor(timeSinceLastLeak * this.leakRate);

    for (let i = 0; i < requestsToProcess && this.queue.length > 0; i++) {
      const { request, resolve } = this.queue.shift()!;
      resolve(this.executeRequest(request));
    }

    this.lastLeakTimestamp = now;
  }

  private processQueue(): void {
    this.leak();
  }

  private async executeRequest(request: Request): Promise<Response> {
    // Executa o request atual
    return await processRequest(request);
  }
}

// Uso: Processa até 100 requests a 10 requests/segundo
const rateLimiter = new LeakyBucket(100, 10);

Características:

  • Suaviza tráfego para taxa constante
  • Sem tolerância a burst
  • Requisitos de memória crescem com tamanho da fila
  • Implementação mais complexa que token bucket

Melhor para:

  • APIs requerendo throughput previsível
  • Processamento de jobs em background
  • Cenários onde tráfego burst deve ser suavizado

Desvantagens:

  • Enfileiramento adiciona latência
  • Pegada de memória maior
  • Mais complexo de implementar corretamente

Contador de janela fixa

O algoritmo de janela fixa rastreia contagens de request dentro de janelas de tempo fixas. Quando o contador excede o limite, requests são rejeitados até a próxima janela começar.

typescriptclass FixedWindowCounter {
  private counters: Map<string, {count: number, windowStart: number}> = new Map();
  private windowSize: number; // milissegundos
  private limit: number;

  constructor(windowSize: number, limit: number) {
    this.windowSize = windowSize;
    this.limit = limit;
  }

  allow(key: string): boolean {
    const now = Date.now();
    const windowStart = Math.floor(now / this.windowSize) * this.windowSize;
    const counter = this.counters.get(key);

    // Reseta contador se janela expirou
    if (!counter || counter.windowStart !== windowStart) {
      this.counters.set(key, { count: 1, windowStart });
      return true;
    }

    // Verifica limite
    if (counter.count >= this.limit) {
      return false;
    }

    // Incrementa contador
    counter.count++;
    return true;
  }

  // Obtém requests restantes na janela atual
  getRemaining(key: string): number {
    const now = Date.now();
    const windowStart = Math.floor(now / this.windowSize) * this.windowSize;
    const counter = this.counters.get(key);

    if (!counter || counter.windowStart !== windowStart) {
      return this.limit;
    }

    return Math.max(0, this.limit - counter.count);
  }

  // Obtém tempo até próxima janela
  getResetTime(key: string): number {
    const now = Date.now();
    const windowStart = Math.floor(now / this.windowSize) * this.windowSize;
    const windowEnd = windowStart + this.windowSize;

    return windowEnd - now;
  }
}

// Uso: Permite 100 requests por minuto
const rateLimiter = new FixedWindowCounter(60000, 100);

async function handleRequest(request: Request): Promise<Response> {
  const clientKey = getClientKey(request);

  if (!rateLimiter.allow(clientKey)) {
    const resetTime = rateLimiter.getResetTime(clientKey);
    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'Retry-After': Math.ceil(resetTime / 1000).toString(),
        'X-RateLimit-Limit': '100',
        'X-RateLimit-Remaining': rateLimiter.getRemaining(clientKey).toString(),
        'X-RateLimit-Reset': new Date(Date.now() + resetTime).toUTCString(),
      }
    });
  }

  return await processRequest(request);
}

function getClientKey(request: Request): string {
  // Extrai identificador de cliente (IP, ID de usuário, chave de API, etc.)
  return request.headers.get('X-Forwarded-For') || request.headers.get('X-Real-IP') || 'unknown';
}

Características:

  • Implementação simples
  • Sem tolerância a burst dentro da janela
  • Problemas de borda (spikes nas bordas da janela)
  • Fácil de entender e configurar

Melhor para:

  • Requisitos simples de rate limiting
  • Cenários onde spikes de borda são aceitáveis
  • Implementações rápidas com complexidade mínima

Desvantagens:

  • Spike nas bordas da janela (burst duplo)
  • Sem suavização de tráfego
  • Menos preciso que janela deslizante

Log de janela deslizante

O algoritmo de janela deslizante rastreia timestamps individuais de requests dentro de uma janela de tempo deslizante, fornecendo limitação mais precisa sem spikes de borda.

typescriptclass SlidingWindowLog {
  private logs: Map<string, number[]> = new Map();
  private windowSize: number; // milissegundos
  private limit: number;

  constructor(windowSize: number, limit: number) {
    this.windowSize = windowSize;
    this.limit = limit;
  }

  allow(key: string): boolean {
    const now = Date.now();
    const windowStart = now - this.windowSize;

    // Obtém ou inicializa log para chave
    const timestamps = this.logs.get(key) || [];
    this.logs.set(key, timestamps);

    // Remove timestamps fora da janela
    const validTimestamps = timestamps.filter(t => t > windowStart);

    // Verifica limite
    if (validTimestamps.length >= this.limit) {
      this.logs.set(key, validTimestamps); // Atualiza com timestamps filtrados
      return false;
    }

    // Adiciona timestamp do request atual
    validTimestamps.push(now);
    this.logs.set(key, validTimestamps);

    return true;
  }

  // Obtém requests restantes na janela
  getRemaining(key: string): number {
    const now = Date.now();
    const windowStart = now - this.windowSize;
    const timestamps = this.logs.get(key) || [];

    const validTimestamps = timestamps.filter(t => t > windowStart);
    this.logs.set(key, validTimestamps);

    return Math.max(0, this.limit - validTimestamps.length);
  }

  // Obtém timestamp mais antigo na janela (para cálculo de retry-after)
  getOldestTimestamp(key: string): number | null {
    const now = Date.now();
    const windowStart = now - this.windowSize;
    const timestamps = this.logs.get(key) || [];

    const validTimestamps = timestamps.filter(t => t > windowStart);
    if (validTimestamps.length === 0) {
      return null;
    }

    return validTimestamps[0];
  }
}

// Uso: Permite 100 requests por minuto com janela precisa
const rateLimiter = new SlidingWindowLog(60000, 100);

async function handleRequest(request: Request): Promise<Response> {
  const clientKey = getClientKey(request);

  if (!rateLimiter.allow(clientKey)) {
    const oldestTimestamp = rateLimiter.getOldestTimestamp(clientKey);
    const waitTime = oldestTimestamp
      ? oldestTimestamp + rateLimiter['windowSize'] - Date.now()
      : rateLimiter['windowSize'];

    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'Retry-After': Math.ceil(waitTime / 1000).toString(),
        'X-RateLimit-Limit': '100',
        'X-RateLimit-Remaining': rateLimiter.getRemaining(clientKey).toString(),
      }
    });
  }

  return await processRequest(request);
}

Características:

  • Limitação precisa sem spikes de borda
  • Eficiente em memória (armazena apenas timestamps)
  • Distribuição suave de tráfego
  • Implementação mais complexa que janela fixa

Melhor para:

  • APIs requerendo limitação de taxa precisa
  • Cenários onde spikes de borda são inaceitáveis
  • Sistemas de produção com requisitos estritos de capacidade

Desvantagens:

  • Maior uso de memória que abordagens baseadas em contador
  • Mais complexo de implementar
  • Requer cleanup de timestamps antigos

Rate limiting distribuído

Rate limiting baseado em Redis

Para sistemas distribuídos, o estado de rate limiting deve ser compartilhado através de instâncias. Redis fornece um data store rápido e compartilhado para rate limiting distribuído.

typescriptclass RedisRateLimiter {
  private redis: RedisClient;
  private windowSize: number;
  private limit: number;

  constructor(redis: RedisClient, windowSize: number, limit: number) {
    this.redis = redis;
    this.windowSize = windowSize;
    this.limit = limit;
  }

  async allow(key: string): Promise<{allowed: boolean, remaining: number, resetTime: number}> {
    const now = Date.now();
    const windowStart = Math.floor(now / this.windowSize) * this.windowSize;
    const redisKey = `ratelimit:${key}:${windowStart}`;

    // Obtém contador atual
    const currentCount = await this.redis.get(redisKey);
    const count = parseInt(currentCount || '0', 10);

    // Verifica limite
    if (count >= this.limit) {
      const windowEnd = windowStart + this.windowSize;
      return {
        allowed: false,
        remaining: 0,
        resetTime: windowEnd
      };
    }

    // Incrementa contador
    const newCount = count + 1;
    await this.redis.incr(redisKey);

    // Define expiração
    await this.redis.expireat(redisKey, windowStart + this.windowSize / 1000);

    return {
      allowed: true,
      remaining: this.limit - newCount,
      resetTime: windowStart + this.windowSize
    };
  }
}

// Uso com Express.js
const express = require('express');
const Redis = require('ioredis');
const app = express();

const redis = new Redis();
const rateLimiter = new RedisRateLimiter(redis, 60000, 100);

app.use(async (req, res, next) => {
  const clientKey = getClientKey(req);
  const result = await rateLimiter.allow(clientKey);

  res.set('X-RateLimit-Limit', '100');
  res.set('X-RateLimit-Remaining', result.remaining.toString());
  res.set('X-RateLimit-Reset', new Date(result.resetTime).toUTCString());

  if (!result.allowed) {
    const retryAfter = Math.ceil((result.resetTime - Date.now()) / 1000);
    res.set('Retry-After', retryAfter.toString());
    return res.status(429).send('Too Many Requests');
  }

  next();
});

Script Redis Lua para operações atômicas

Para lógica de rate limiting mais complexa, use scripts Redis Lua para garantir atomicidade:

lua-- token_bucket.lua
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local tokens_to_consume = tonumber(ARGV[3])
local now = tonumber(ARGV[4])

-- Obter estado atual
local state = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(state[1]) or capacity
local last_refill = tonumber(state[2]) or now

-- Recarregar tokens
local time_passed = (now - last_refill) / 1000
local tokens_to_add = time_passed * refill_rate
tokens = math.min(capacity, tokens + tokens_to_add)

-- Verificar se há tokens suficientes
if tokens < tokens_to_consume then
  -- Tokens insuficientes, retorna rejeição
  redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
  redis.call('EXPIRE', key, 3600) -- TTL de 1 hora
  return {0, tokens}
end

-- Consumir tokens
tokens = tokens - tokens_to_consume
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, 3600) -- TTL de 1 hora

return {1, tokens}
typescript// Uso TypeScript
async function consumeTokens(
  redis: RedisClient,
  key: string,
  capacity: number,
  refillRate: number,
  tokensToConsume: number
): Promise<{allowed: boolean, remainingTokens: number}> {
  const result = await redis.eval(
    script,
    1, // número de chaves
    key, // chave
    capacity.toString(), // ARGV[1]
    refillRate.toString(), // ARGV[2]
    tokensToConsume.toString(), // ARGV[3]
    Date.now().toString() // ARGV[4]
  );

  return {
    allowed: result[0] === 1,
    remainingTokens: result[1]
  };
}

Estratégias de rate limiting

Múltiplos tiers

Implemente diferentes limites de taxa para diferentes tiers de usuário:

typescriptclass TieredRateLimiter {
  private limiters: Map<string, RateLimiter> = new Map();
  private userTierCache: Map<string, string> = new Map();
  private tierLimits: Map<string, {windowSize: number, limit: number}> = new Map();

  constructor() {
    this.tierLimits.set('free', { windowSize: 60000, limit: 100 });
    this.tierLimits.set('pro', { windowSize: 60000, limit: 1000 });
    this.tierLimits.set('enterprise', { windowSize: 60000, limit: 10000 });

    // Inicializa limiters para cada tier
    this.tierLimits.forEach((config, tier) => {
      this.limiters.set(tier, new FixedWindowCounter(config.windowSize, config.limit));
    });
  }

  async allow(userId: string): Promise<{allowed: boolean, tier: string}> {
    const tier = await this.getUserTier(userId);
    const limiter = this.limiters.get(tier)!;

    return {
      allowed: limiter.allow(userId),
      tier
    };
  }

  private async getUserTier(userId: string): Promise<string> {
    // Verifica cache primeiro
    if (this.userTierCache.has(userId)) {
      return this.userTierCache.get(userId)!;
    }

    // Busca do banco de dados
    const user = await fetchUserFromDatabase(userId);
    const tier = user.subscriptionTier || 'free';

    // Cache por 5 minutos
    this.userTierCache.set(userId, tier);
    setTimeout(() => this.userTierCache.delete(userId), 300000);

    return tier;
  }
}

Rate limiting por endpoint

Diferentes endpoints podem ter diferentes limites de taxa:

typescriptclass EndpointRateLimiter {
  private limiters: Map<string, Map<string, RateLimiter>> = new Map();

  registerEndpoint(path: string, method: string, windowSize: number, limit: number): void {
    if (!this.limiters.has(method)) {
      this.limiters.set(method, new Map());
    }

    this.limiters.get(method)!.set(path, new FixedWindowCounter(windowSize, limit));
  }

  async allow(method: string, path: string, clientId: string): Promise<boolean> {
    const methodLimiters = this.limiters.get(method);
    if (!methodLimiters) {
      return true; // Nenhum limite de taxa configurado
    }

    const limiter = methodLimiters.get(path);
    if (!limiter) {
      return true; // Nenhum limite de taxa configurado
    }

    return limiter.allow(`${method}:${path}:${clientId}`);
  }
}

// Uso
const rateLimiter = new EndpointRateLimiter();

// Limites diferentes para diferentes endpoints
rateLimiter.registerEndpoint('/api/v1/users', 'GET', 60000, 100);
rateLimiter.registerEndpoint('/api/v1/users', 'POST', 60000, 10);
rateLimiter.registerEndpoint('/api/v1/search', 'GET', 60000, 1000);

// Middleware
app.use(async (req, res, next) => {
  const clientId = getClientKey(req);
  const allowed = await rateLimiter.allow(req.method, req.path, clientId);

  if (!allowed) {
    return res.status(429).send('Too Many Requests');
  }

  next();
});

Integração com circuit breaker

Combine rate limiting com circuit breakers para resiliência:

typescriptclass CircuitBreakerRateLimiter {
  private rateLimiter: RateLimiter;
  private circuitBreaker: CircuitBreaker;

  async execute(request: Request): Promise<Response> {
    // Verifica rate limit primeiro
    const clientId = getClientKey(request);
    if (!this.rateLimiter.allow(clientId)) {
      return new Response('Too Many Requests', { status: 429 });
    }

    // Verifica circuit breaker
    if (this.circuitBreaker.isOpen()) {
      return new Response('Service Unavailable', { status: 503 });
    }

    try {
      // Executa request
      const response = await this.executeRequest(request);
      this.circuitBreaker.recordSuccess();
      return response;
    } catch (error) {
      this.circuitBreaker.recordFailure();
      throw error;
    }
  }
}

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: 'closed' | 'open' | 'half-open' = 'closed';

  constructor(
    private failureThreshold: number = 5,
    private timeout: number = 60000 // 1 minuto
  ) {}

  isOpen(): boolean {
    if (this.state === 'open') {
      // Verifica se deve transicionar para half-open
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'half-open';
        return false;
      }
      return true;
    }
    return false;
  }

  recordSuccess(): void {
    this.failureCount = 0;
    this.state = 'closed';
  }

  recordFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();

    if (this.failureCount >= this.failureThreshold) {
      this.state = 'open';
    }
  }
}

Headers de resposta e experiência do cliente

Respostas de rate limiting devem fornecer orientação clara para clientes:

typescriptasync function handleRateLimitedRequest(
  request: Request,
  rateLimiter: RateLimiter,
  clientId: string
): Promise<Response> {
  if (!rateLimiter.allow(clientId)) {
    const remaining = rateLimiter.getRemaining(clientId);
    const resetTime = rateLimiter.getResetTime(clientId);

    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'Content-Type': 'application/json',
        'Retry-After': Math.ceil(resetTime / 1000).toString(),
        'X-RateLimit-Limit': rateLimiter['limit'].toString(),
        'X-RateLimit-Remaining': remaining.toString(),
        'X-RateLimit-Reset': new Date(Date.now() + resetTime).toUTCString(),
      }
    });
  }

  return await processRequest(request);
}

// Inclui info de rate limit em respostas bem-sucedidas
function addRateLimitHeaders(
  response: Response,
  rateLimiter: RateLimiter,
  clientId: string
): Response {
  const headers = new Headers(response.headers);

  headers.set('X-RateLimit-Limit', rateLimiter['limit'].toString());
  headers.set('X-RateLimit-Remaining', rateLimiter.getRemaining(clientId).toString());

  return new Response(response.body, {
    status: response.status,
    headers
  });
}

Monitoramento e observabilidade

Rastreie métricas de rate limiting para ajustar limites e detectar abuso:

typescriptclass RateLimitingMetrics {
  private requestCounts: Map<string, number> = new Map();
  private rejectionCounts: Map<string, number> = new Map();
  private requestTimes: Array<{key: string, timestamp: number, duration: number}> = [];

  recordRequest(key: string, allowed: boolean, duration: number): void {
    // Conta requests
    const requestCount = this.requestCounts.get(key) || 0;
    this.requestCounts.set(key, requestCount + 1);

    // Conta rejeições
    if (!allowed) {
      const rejectionCount = this.rejectionCounts.get(key) || 0;
      this.rejectionCounts.set(key, rejectionCount + 1);
    }

    // Registra timing
    this.requestTimes.push({ key, timestamp: Date.now(), duration });

    // Cleanup de registros antigos (mantém últimos 1000)
    if (this.requestTimes.length > 1000) {
      this.requestTimes.shift();
    }
  }

  getMetrics(key: string): RateLimitMetrics {
    const requestCount = this.requestCounts.get(key) || 0;
    const rejectionCount = this.rejectionCounts.get(key) || 0;

    const keyRequests = this.requestTimes.filter(r => r.key === key);
    const avgDuration = keyRequests.length > 0
      ? keyRequests.reduce((sum, r) => sum + r.duration, 0) / keyRequests.length
      : 0;

    return {
      key,
      requestCount,
      rejectionCount,
      rejectionRate: requestCount > 0 ? rejectionCount / requestCount : 0,
      averageRequestDuration: avgDuration,
    };
  }

  getTopOffenders(limit: number = 10): RateLimitMetrics[] {
    const allKeys = Array.from(new Set(this.requestTimes.map(r => r.key)));

    return allKeys
      .map(key => this.getMetrics(key))
      .sort((a, b) => b.rejectionCount - a.rejectionCount)
      .slice(0, limit);
  }
}

interface RateLimitMetrics {
  key: string;
  requestCount: number;
  rejectionCount: number;
  rejectionRate: number;
  averageRequestDuration: number;
}

Framework de decisão

Escolha o algoritmo correto

AlgoritmoTolerância a BurstPrecisãoComplexidadeMemóriaMelhor Para
Token BucketAltaMédiaBaixaBaixaAPIs tolerando bursts
Leaky BucketNenhumaAltaMédiaAltaThroughput previsível
Janela FixaBaixaBaixaMuito BaixaMuito BaixaImplementações simples
Janela DeslizanteBaixaAltaAltaMédiaLimitação precisa

Avalie requisitos

Perguntas a fazer:

  1. Sua API precisa lidar com tráfego burst?
  • Sim → Token bucket ou leaky bucket
  • Não → Janela fixa ou deslizante
  1. Quão precisa precisa ser sua limitação de taxa?
  • Muito precisa → Janela deslizante
  • Precisão moderada → Token bucket
  • Precisão básica → Janela fixa
  1. Qual é sua tolerância de complexidade operacional?
  • Baixa tolerância → Janela fixa
  • Tolerância moderada → Token bucket
  • Alta tolerância → Janela deslizante ou leaky bucket
  1. Você precisa de rate limiting distribuído?
  • Sim → Redis ou banco de dados compartilhado
  • Não → Implementação em memória

Conclusão

Rate limiting é essencial para proteger APIs em produção e garantir alocação justa de recursos. O algoritmo correto depende dos seus requisitos específicos: tolerância a burst, necessidades de precisão e complexidade operacional.

Comece com uma implementação simples (janela fixa ou token bucket) e evolua conforme seus requisitos se tornam mais claros. Monitore métricas de rate limiting continuamente para ajustar limites e detectar padrões de abuso. O objetivo não é bloquear usuários legítimos—é criar comportamento previsível do sistema que protege tanto a infraestrutura quanto a experiência do usuário.

Pergunta prática de fechamento: Qual é o padrão de abuso mais comum em sua API atual, e um algoritmo de rate limiting diferente abordaria melhor?


Construindo uma API em produção e precisa de orientação especializada sobre rate limiting e gerenciamento de capacidade? Fale com especialistas em API da Imperialis sobre implementar estratégias de rate limiting que protejam sua infraestrutura enquanto fornecem excelente experiência do usuário.

Fontes

Leituras relacionadas