← Back to DevBytes

Designing a Rate Limiter with Zero-Downtime Deployment

Designing a Rate Limiter with Zero-Downtime Deployment

Rate limiting is a critical mechanism for controlling the amount of traffic a service receives within a given time window. It protects backend resources from abuse, ensures fair usage among clients, and prevents cascading failures. When deploying changes to a rate limiter—whether updating algorithms, adjusting thresholds, or migrating state stores—zero-downtime deployment becomes essential to avoid service disruptions. This tutorial covers the fundamentals of rate limiter design, the importance of zero-downtime deployments, practical implementation strategies, and best practices.

What Is a Rate Limiter?

A rate limiter restricts the number of requests a client (IP, API key, user) can make over a specified period. Common algorithms include:

Rate limiters can be implemented in-memory (single instance), or using a distributed store (Redis, Memcached) for multi-instance deployments.

Why Does Zero-Downtime Deployment Matter for Rate Limiters?

Rate limiters often carry state—counts, tokens, or timestamps per client. Changing the rate limiter logic or its backing store without careful planning can:

Zero-downtime deployment ensures that rate limiting remains consistent and available throughout the update, preventing both false positives (blocking legitimate traffic) and false negatives (allowing abuse).

How to Design a Rate Limiter with Zero-Downtime Deployment

We’ll focus on a distributed sliding window counter backed by Redis, and deploy it using a blue-green strategy. The key design principles are:

  1. Stateless application layer – The rate limiter logic is pure computation; all state lives in a durable, external store.
  2. Backward-compatible state format – New code must be able to read and write state that the old code understands (or handle migrations gracefully).
  3. Gradual rollouts – Use canary or blue-green deployments to validate the new version before full cutover.
  4. Graceful shutdown and connection draining – Ensure in-flight requests finish with the old version before it is removed.

Practical Implementation Example

We’ll implement a sliding window counter rate limiter in Go using Redis. The deployment will use a blue-green pattern with a load balancer that can switch traffic instantly.

Rate Limiter Code (rate_limiter.go):

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "time"
)

type SlidingWindowLimiter struct {
    client  *redis.Client
    window  time.Duration
    maxReqs int64
}

func NewSlidingWindowLimiter(client *redis.Client, window time.Duration, maxReqs int64) *SlidingWindowLimiter {
    return &SlidingWindowLimiter{
        client:  client,
        window:  window,
        maxReqs: maxReqs,
    }
}

func (l *SlidingWindowLimiter) Allow(ctx context.Context, key string) (bool, error) {
    now := time.Now().UnixMilli()
    windowStart := now - l.window.Milliseconds()

    pipe := l.client.TxPipeline()
    // Remove entries outside window
    pipe.ZRemRangeByScore(ctx, key, "0", fmt.Sprintf("%d", windowStart))
    // Count current entries
    countCmd := pipe.ZCard(ctx, key)
    // Add current request
    pipe.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: now})
    // Set TTL on key to auto-clean
    pipe.Expire(ctx, key, l.window)
    _, err := pipe.Exec(ctx)
    if err != nil {
        return false, err
    }

    currentCount, err := countCmd.Result()
    if err != nil {
        return false, err
    }
    return currentCount < l.maxReqs, nil
}

Deployment Script (blue-green deploy):

# Assume two environments: blue (live) and green (staging)
# Load balancer routes to blue by default

# Step 1: Deploy new version to green
kubectl set image deployment/rate-limiter-green rate-limiter=myrepo/rate-limiter:v2

# Step 2: Wait for green to be ready
kubectl rollout status deployment/rate-limiter-green

# Step 3: Run smoke tests against green (internal endpoint)
curl -f http://green-internal/health

# Step 4: Switch load balancer to green
kubectl patch service rate-limiter-svc -p '{"spec":{"selector":{"version":"green"}}}'

# Step 5: Monitor for errors; if issues, revert:
# kubectl patch service rate-limiter-svc -p '{"spec":{"selector":{"version":"blue"}}}'

# Step 6: After validation, decommission blue (optional)
kubectl scale deployment/rate-limiter-blue --replicas=0

State Migration Consideration:

If the new version changes the Redis key naming or score format, implement a dual-read strategy: new code writes in the new format but still reads old keys. After all clients have upgraded, switch to writing only new format and drop old key support. For the sliding window example above, the key and member format (timestamp as member) is stable, so no migration is needed.

Best Practices

Conclusion

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Designing a rate limiter with zero-downtime deployment requires a combination of stateless application logic, a resilient distributed state store, and a robust deployment strategy like blue-green or canary releases. By keeping the rate limiter logic independent of the instance lifecycle and ensuring backward compatibility of state, you can update your rate limiting rules or algorithms without dropping a single request. The practical example using Redis and a sliding window counter illustrates how to achieve consistency across deploys, while the best practices outlined help you avoid common pitfalls. With careful planning, your rate limiter can evolve seamlessly, maintaining both performance and protection for your services.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles