Rate Limiting

Rate Limiting ist eine grundlegende Technik zur Verkehrssteuerung, die die Anzahl der Anfragen begrenzt, die ein Client innerhalb eines bestimmten Zeitfensters an eine API oder einen Webdienst stellen kann, und so die Infrastruktur vor Überlastung, Denial-of-Service-Angriffen (DoS/DDoS), automatisiertem Missbrauch, böswilligem Scraping und unsachgemäßer Nutzung begrenzter Rechenressourcen schützt. In einem Szenario, in dem moderne APIs Millionen von Anfragen pro Sekunde aus mobilen Anwendungen, Partnerintegrationen, legitimen Bots und potenziell böswilligen Angreifern bedienen, kann das Fehlen eines angemessenen Rate Limitings zu explodierenden Betriebskosten, einer starken Beeinträchtigung der Performance für legitime Nutzer, Anfälligkeiten für Credential-Stuffing- und Brute-Force-Angriffe und sogar zur vollständigen Nichtverfügbarkeit des Dienstes führen. Die effektive Implementierung von Rate Limiting erfordert ein tiefes Verständnis verschiedener Algorithmen (Token Bucket, Leaky Bucket, Fixed Window, Sliding Window), Überlegungen zur verteilten Architektur, bei der mehrere Server Ratenzähler teilen müssen, Strategien zur Client-Identifizierung (IP, API Key, JWT, Session), differenzierte Richtlinien je nach Nutzer-Tier (free, premium, enterprise) und Mechanismen zur klaren Kommunikation über standardisierte HTTP-Header, die Clients über Limits, den aktuellen Verbrauch und die Reset-Zeit informieren. Dieser Artikel untersucht Algorithmen, Implementierungsmuster, Tools und Best Practices für den Aufbau robuster Rate-Limiting-Systeme.

Rate-Limiting-Algorithmen

1. Token Bucket (Am Häufigsten)

      # Konzept: Bucket mit maximaler Token-Kapazität
      # Tokens werden mit konstanter Rate hinzugefügt
      # Jeder Request verbraucht 1 Token
      # Wenn der Bucket leer ist, wird der Request abgelehnt
      Kapazität: 100 Tokens
      Nachfüllrate: 10 Tokens/Sekunde
      Vorteile:
      - Ermöglicht kontrollierte Bursts (voller Bucket)
      - Einfach zu implementieren
      - Flexibel für verschiedene Verkehrsmuster
      Nachteile:
      - Kann Bursts zulassen, die das System überlasten
      - Erfordert Tracking des letzten Nachfüllens
      Implementierung:
      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

      # Konzept: Queue mit konstantem Abfluss
      # Requests gelangen in den Bucket (Queue)
      # Mit konstanter Rate verarbeitet (Leak)
      # Wenn die Queue voll ist, werden Requests abgelehnt
      Vorteile:
      - Glättet Bursts (Traffic Shaping)
      - Konstante und vorhersehbare Output Rate
      - Schützt das Backend vor Spikes
      Nachteile:
      - Kann Latenz hinzufügen (Queueing)
      - Implementierungskomplexität
      Verwendung:
      - Traffic Shaping
      - Network Gateways
      - Wenn eine konstante Output Rate kritisch ist
      

3. Fixed Window Counter

      # Konzept: Zähler pro festem Zeitfenster
      # Beispiel: 100 Requests pro Minute
      # Reset zu Beginn jeder Minute (XX:00, XX:01, XX:02...)
      Vorteile:
      - Extrem einfach
      - Memory efficient
      - Leicht zu verstehen
      Nachteile:
      - Edge Case: 200 Requests in 1 Sekunde
      (100 am Ende von Minute 1, 100 am Anfang von Minute 2)
      - Erlaubt Bursts an den Fenstergrenzen
      Redis-Implementierung:
      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  # Wenn > 100, reject
      

4. Sliding Window Log

      # Konzept: Log der Request-Timestamps
      # Entfernt Requests außerhalb des Fensters
      # Zählt Requests innerhalb des gleitenden Fensters
      Vorteile:
      - Perfekte Genauigkeit
      - Keine Edge Cases von Fixed Window
      - Verteilt die Rate gleichmäßig
      Nachteile:
      - Memory intensive (speichert alle Timestamps)
      - Performance verschlechtert sich bei High Traffic
      Redis-Implementierung (Sorted Set):
      ZADD user:123 <timestamp> <request-id>
      ZREMRANGEBYSCORE user:123 0 <timestamp-60s>  # Entfernt alte
      ZCARD user:123  # Count requests
      Wenn > 100, reject
      

5. Sliding Window Counter (Hybrid)

      # Konzept: Kombiniert Fixed Window mit Sliding
      # Verwendet Zähler vorheriger Fenster mit Gewichtung
      # Schätzt die Rate in einem gleitenden Fenster
      Beispiel: Limit 100/Minute
      Aktuelles Fenster (14:30): 70 Requests
      Vorheriges Fenster (14:29): 90 Requests
      Verstrichen im aktuellen Fenster: 40s (66.7%)
      Schätzung: 90 * (1 - 0.667) + 70 = 30 + 70 = 100
      Vorteile:
      - Genauigkeit nahe an Sliding Log
      - Memory efficient (nur 2 Zähler)
      - Glättet Bursts
      Nachteile:
      - Schätzung (nicht exakt)
      - Komplexer als Fixed Window
      

Distributed Rate Limiting

      # Problem: Mehrere Server müssen State teilen
      # Lösungen:
      1. Zentralisiertes Redis (Am Häufigsten)
      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
      - Nutzer immer zum selben Server routen
      - Lokaler Counter auf dem Server
      - Problem: Funktioniert nicht gut mit Auto-Scaling
      4. Gossip Protocol
      - Server teilen State via Gossip
      - Eventual Consistency
      - Komplexer, eingesetzt bei High Scale
      

Standard-HTTP-Header

      # Standards (RFCs)
      X-RateLimit-Limit: 100
      X-RateLimit-Remaining: 45
      X-RateLimit-Reset: 1640000000  # Unix timestamp
      # Wenn das Limit überschritten wird
      HTTP/1.1 429 Too Many Requests
      Retry-After: 60  # Sekunden bis zum Retry
      X-RateLimit-Limit: 100
      X-RateLimit-Remaining: 0
      X-RateLimit-Reset: 1640000060
      {
      "error": "Rate limit exceeded",
      "retryAfter": 60,
      "limit": 100
      }
      # GitHub-Stil (informativer)
      X-RateLimit-Limit: 5000
      X-RateLimit-Remaining: 4999
      X-RateLimit-Reset: 1372700873
      X-RateLimit-Used: 1
      X-RateLimit-Resource: core
      

Implementierung mit 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);
      });
      

Tools und Dienste

  • Redis: Verteilte Zähler, automatisches Expire
  • Kong: API Gateway mit Rate-Limiting-Plugin
  • Nginx rate limiting: limit_req_zone, limit_conn_zone
  • Cloudflare: Rate Limiting auf CDN-Ebene
  • AWS API Gateway: Integriertes Throttling
  • express-rate-limit: Express-Middleware
  • Tyk: Open-Source-API-Gateway

Best Practices

  • Wählen Sie den richtigen Algorithmus: Token Bucket für allgemeine APIs, Sliding Window für Genauigkeit
  • Differenzierte Limits: Auth-Endpoints restriktiver, Read-Only permissiver
  • Klare Kommunikation: Informative Header, hilfreiche Fehlermeldungen
  • Whitelist: IPs vertrauenswürdiger Partner, Health Checks
  • Monitoring: Alarmieren, wenn Nutzer häufig Limits erreichen
  • Graceful Degradation: Nach Möglichkeit Cached Data zurückgeben
  • Distributed State: Redis für Multi-Server-Deployments verwenden
  • Cost-based Limiting: Teure Operationen verbrauchen mehr Tokens

Empfehlungen

Implementieren Sie für moderne APIs einen Token Bucket mit Redis für Distributed Rate Limiting. Verwenden Sie einen Sliding Window Counter, wenn Sie Genauigkeit ohne Speicher- Overhead benötigen. Konfigurieren Sie differenzierte Limits: 5 Req/min für Login, 100 Req/min für Lesezugriffe, 10 Req/min für Schreiboperationen. Geben Sie immer informative Header zurück und implementieren Sie eine Retry-Logik mit exponentiellem Backoff auf den Clients.