Files
junhong_cmp_fiber/docs/rate-limiting.md
huang 1f71741836 完成 Phase 10 质量保证,项目达到生产部署标准
主要变更:
-  完成所有文档任务(T092-T095a)
  * 创建中文 README.md 和项目文档
  * 添加限流器使用指南
  * 更新快速入门文档
  * 添加详细的中文代码注释

-  完成代码质量任务(T096-T103)
  * 通过 gofmt、go vet、golangci-lint 检查
  * 修复 17 个 errcheck 问题
  * 验证无硬编码 Redis key
  * 确保命名规范符合 Go 标准

-  完成测试任务(T104-T108)
  * 58 个测试全部通过
  * 总体覆盖率 75.1%(超过 70% 目标)
  * 核心模块覆盖率 90%+

-  完成安全审计任务(T109-T113)
  * 修复日志中令牌泄露问题
  * 验证 Fail-closed 策略正确实现
  * 审查 Redis 连接安全
  * 完成依赖项漏洞扫描

-  完成性能验证任务(T114-T117)
  * 令牌验证性能:17.5 μs/op(~58,954 ops/s)
  * 响应序列化性能:1.1 μs/op(>1,000,000 ops/s)
  * 配置访问性能:0.58 ns/op(接近 CPU 缓存速度)

-  完成质量关卡任务(T118-T126)
  * 所有测试通过
  * 代码格式和静态检查通过
  * 无 TODO/FIXME 遗留
  * 中间件集成验证
  * 优雅关闭机制验证

新增文件:
- README.md(中文项目文档)
- docs/rate-limiting.md(限流器指南)
- docs/security-audit-report.md(安全审计报告)
- docs/performance-benchmark-report.md(性能基准报告)
- docs/quality-gate-report.md(质量关卡报告)
- docs/PROJECT-COMPLETION-SUMMARY.md(项目完成总结)
- 基准测试文件(config, response, validator)

安全修复:
- 移除 pkg/validator/token.go 中的敏感日志记录

质量评分:9.6/10(优秀)
项目状态: 已完成,待部署
2025-11-11 16:53:05 +08:00

26 KiB

Rate Limiting Guide

Comprehensive guide for configuring and using the rate limiting middleware in Junhong CMP Fiber.

Table of Contents


Overview

The rate limiting middleware protects your API from abuse by limiting the number of requests a client can make within a specified time window. It operates at the IP address level, ensuring each client has independent rate limits.

Key Features

  • IP-based rate limiting: Each client IP has independent counters
  • Configurable limits: Customize max requests and time windows
  • Multiple storage backends: In-memory or Redis-based storage
  • Fail-safe operation: Continues with in-memory storage if Redis fails
  • Hot-reloadable: Change limits without restarting server
  • Unified error responses: Returns 429 with standardized error format

How It Works

Client Request → Check IP Address → Check Request Count → Allow/Reject
                       ↓                    ↓
                  192.168.1.1      Counter: 45 / Max: 100
                                          ↓
                                    Allow (increment to 46)

Rate Limit Algorithm

The middleware uses a sliding window approach:

  1. Extract client IP from request
  2. Check counter for IP in storage (key: rate_limit:{ip})
  3. If counter < max: increment counter and allow request
  4. If counter >= max: reject with 429 status
  5. Counter automatically resets after expiration duration

Configuration

Basic Configuration Structure

Rate limiting is configured in configs/config.yaml:

middleware:
  # Enable/disable rate limiting
  enable_rate_limiter: false  # Default: disabled
  
  # Rate limiter settings
  rate_limiter:
    max: 100              # Maximum requests per window
    expiration: "1m"      # Time window duration
    storage: "memory"     # Storage backend: "memory" or "redis"

Configuration Parameters

enable_rate_limiter (boolean)

Controls whether rate limiting is active.

  • Default: false
  • Values: true (enabled), false (disabled)
  • Hot-reloadable: Yes

Example:

middleware:
  enable_rate_limiter: true  # Enable rate limiting

max (integer)

Maximum number of requests allowed per time window.

  • Default: 100
  • Range: 1 - unlimited (practical max: ~1,000,000)
  • Hot-reloadable: Yes

Examples:

# Strict limit for public APIs
rate_limiter:
  max: 60  # 60 requests per minute

# Relaxed limit for internal APIs
rate_limiter:
  max: 5000  # 5000 requests per minute

expiration (duration string)

Time window for rate limiting. After this duration, the counter resets.

  • Default: "1m" (1 minute)
  • Supported formats:
    • "30s" - 30 seconds
    • "1m" - 1 minute
    • "5m" - 5 minutes
    • "1h" - 1 hour
    • "24h" - 24 hours
  • Hot-reloadable: Yes

Examples:

# Short window for burst protection
rate_limiter:
  expiration: "30s"  # Limit resets every 30 seconds

# Standard API rate limit
rate_limiter:
  expiration: "1m"   # Limit resets every minute

# Long window for daily quotas
rate_limiter:
  expiration: "24h"  # Limit resets daily

storage (string)

Storage backend for rate limit counters.

  • Default: "memory"
  • Values: "memory", "redis"
  • Hot-reloadable: Yes (but existing counters are lost when switching)

Comparison:

Feature "memory" "redis"
Speed Very fast (in-process) Fast (network call)
Persistence Lost on restart Persists across restarts
Multi-server Independent counters Shared counters
Dependencies None Requires Redis connection
Best for Single server, dev/test Multi-server, production

Examples:

# Memory storage (single server)
rate_limiter:
  storage: "memory"

# Redis storage (distributed)
rate_limiter:
  storage: "redis"

Environment-Specific Configurations

Development (configs/config.dev.yaml)

middleware:
  enable_auth: false          # Optional: disable auth for easier testing
  enable_rate_limiter: false  # Disabled by default
  
  rate_limiter:
    max: 1000                 # High limit (avoid disruption during dev)
    expiration: "1m"
    storage: "memory"         # No Redis dependency

Use case: Local development with frequent requests, no rate limiting interference

Staging (configs/config.staging.yaml)

middleware:
  enable_auth: true
  enable_rate_limiter: true   # Enabled to test production behavior
  
  rate_limiter:
    max: 1000                 # Medium limit (test realistic load)
    expiration: "1m"
    storage: "memory"         # Can use "redis" to test distributed limits

Use case: Pre-production testing with realistic rate limits

Production (configs/config.prod.yaml)

middleware:
  enable_auth: true
  enable_rate_limiter: true   # Always enabled in production
  
  rate_limiter:
    max: 5000                 # Strict limit (prevent abuse)
    expiration: "1m"
    storage: "redis"          # Distributed rate limiting

Use case: Production deployment with strict limits and distributed storage


Storage Options

Memory Storage

How it works: Stores rate limit counters in-process memory using Fiber's built-in storage.

Pros:

  • Very fast (no network latency)
  • 🔧 No external dependencies
  • 💰 Free (no Redis costs)

Cons:

  • 🔄 Counters reset on server restart
  • 🖥️ Each server instance has independent counters (can't enforce global limits in multi-server setup)
  • 📉 Memory usage grows with unique IPs

When to use:

  • Single-server deployments
  • Development/testing environments
  • When rate limit precision is not critical
  • When Redis is unavailable or not desired

Configuration:

rate_limiter:
  storage: "memory"

Example scenario: Single API server with 1000 req/min limit

Server 1:
  IP 192.168.1.1 → 950 requests → Allowed ✓
  IP 192.168.1.2 → 1050 requests → 50 rejected (429) ✗

Redis Storage

How it works: Stores rate limit counters in Redis with automatic expiration.

Pros:

  • 🌐 Distributed rate limiting (shared across all servers)
  • 💾 Counters persist across server restarts
  • 🎯 Precise global rate limit enforcement
  • 📊 Centralized monitoring (inspect Redis keys)

Cons:

  • 🐌 Slightly slower (network call to Redis)
  • 💸 Requires Redis server (infrastructure cost)
  • 🔌 Dependency on Redis availability

When to use:

  • Multi-server/load-balanced deployments
  • Production environments requiring strict limits
  • When you need consistent limits across all servers
  • When rate limit precision is critical

Configuration:

rate_limiter:
  storage: "redis"

# Ensure Redis connection is configured
redis:
  address: "redis-prod:6379"
  password: "${REDIS_PASSWORD}"
  db: 0

Example scenario: 3 API servers behind load balancer with 1000 req/min limit

Load Balancer distributes requests across servers:
  
  IP 192.168.1.1 makes 1500 requests:
    → 500 requests to Server 1 ✓
    → 500 requests to Server 2 ✓
    → 500 requests to Server 3 ✗ (global limit of 1000 reached)
    
All servers share the same Redis counter:
  Redis: rate_limit:192.168.1.1 = 1000 (limit reached)

Redis Key Structure

When using Redis storage, the middleware creates keys with the following pattern:

Key pattern: rate_limit:{ip_address}
TTL: Matches expiration config

Examples:

# List all rate limit keys
redis-cli KEYS "rate_limit:*"

# Output:
# 1) "rate_limit:192.168.1.1"
# 2) "rate_limit:192.168.1.2"
# 3) "rate_limit:10.0.0.5"

# Check counter for specific IP
redis-cli GET "rate_limit:192.168.1.1"
# Output: "45" (45 requests made in current window)

# Check TTL (time until reset)
redis-cli TTL "rate_limit:192.168.1.1"
# Output: "42" (42 seconds until counter resets)

Switching Storage Backends

You can switch between storage backends by changing the configuration. Note: Existing counters are lost when switching.

Switching from memory to Redis:

# Before: memory storage
rate_limiter:
  storage: "memory"

# After: Redis storage (all memory counters are discarded)
rate_limiter:
  storage: "redis"

Behavior: After config reload (within 5 seconds), new requests use Redis storage. Old memory counters are garbage collected.


Code Examples

Basic Setup (cmd/api/main.go)

package main

import (
    "github.com/break/junhong_cmp_fiber/internal/middleware"
    "github.com/break/junhong_cmp_fiber/pkg/config"
    "github.com/gofiber/fiber/v2"
)

func main() {
    // Load configuration
    if err := config.LoadConfig(); err != nil {
        panic(err)
    }
    
    app := fiber.New()
    
    // Optional: Register rate limiter middleware
    if config.GetConfig().Middleware.EnableRateLimiter {
        var storage fiber.Storage = nil
        
        // Use Redis storage if configured
        if config.GetConfig().Middleware.RateLimiter.Storage == "redis" {
            storage = redisStorage // Assume redisStorage is initialized
        }
        
        app.Use(middleware.RateLimiter(
            config.GetConfig().Middleware.RateLimiter.Max,
            config.GetConfig().Middleware.RateLimiter.Expiration,
            storage,
        ))
    }
    
    // Register routes
    app.Get("/api/v1/users", listUsersHandler)
    
    app.Listen(":3000")
}

Custom Rate Limiter (Different Limits for Different Routes)

// Apply different limits to different route groups

// Public API - strict limit (100 req/min)
publicAPI := app.Group("/api/v1/public")
publicAPI.Use(middleware.RateLimiter(100, 1*time.Minute, nil))
publicAPI.Get("/data", publicDataHandler)

// Internal API - relaxed limit (5000 req/min)
internalAPI := app.Group("/api/v1/internal")
internalAPI.Use(middleware.RateLimiter(5000, 1*time.Minute, nil))
internalAPI.Get("/metrics", internalMetricsHandler)

// Admin API - very relaxed limit (10000 req/min)
adminAPI := app.Group("/api/v1/admin")
adminAPI.Use(middleware.RateLimiter(10000, 1*time.Minute, nil))
adminAPI.Post("/users", createUserHandler)

Bypassing Rate Limiter for Specific Routes

// Apply rate limiter globally
app.Use(middleware.RateLimiter(100, 1*time.Minute, nil))

// But register health check BEFORE rate limiter
app.Get("/health", healthHandler)  // Not rate limited

// Alternative: Register after but add skip logic in middleware
// (requires custom middleware modification)

Testing Rate Limiter in Code

package main

import (
    "testing"
    "github.com/gofiber/fiber/v2"
    "github.com/break/junhong_cmp_fiber/internal/middleware"
)

func TestRateLimiter(t *testing.T) {
    app := fiber.New()
    
    // Apply rate limiter: 5 requests per minute
    app.Use(middleware.RateLimiter(5, 1*time.Minute, nil))
    
    app.Get("/test", func(c *fiber.Ctx) error {
        return c.SendString("success")
    })
    
    // Make 6 requests
    for i := 1; i <= 6; i++ {
        req := httptest.NewRequest("GET", "/test", nil)
        resp, _ := app.Test(req)
        
        if i <= 5 {
            // First 5 should succeed
            assert.Equal(t, 200, resp.StatusCode)
        } else {
            // 6th should be rate limited
            assert.Equal(t, 429, resp.StatusCode)
        }
    }
}

Testing

Enable Rate Limiter for Testing

Edit configs/config.yaml:

middleware:
  enable_rate_limiter: true  # Enable
  rate_limiter:
    max: 5                   # Low limit for easy testing
    expiration: "1m"
    storage: "memory"

Restart server or wait 5 seconds for config reload.

Test 1: Basic Rate Limiting

Make requests until limit is reached:

# Send 10 requests rapidly
for i in {1..10}; do
  curl -w "\nRequest $i: %{http_code}\n" \
       -H "token: test-token-abc123" \
       http://localhost:3000/api/v1/users
  sleep 0.1
done

Expected output:

Request 1: 200  ✓
Request 2: 200  ✓
Request 3: 200  ✓
Request 4: 200  ✓
Request 5: 200  ✓
Request 6: 429  ✗ Rate limited
Request 7: 429  ✗ Rate limited
Request 8: 429  ✗ Rate limited
Request 9: 429  ✗ Rate limited
Request 10: 429 ✗ Rate limited

Rate limit response (429):

{
  "code": 1003,
  "data": null,
  "msg": "请求过于频繁",
  "timestamp": "2025-11-10T15:35:00Z"
}

Test 2: Window Reset

Verify counter resets after expiration:

# Make 5 requests (hit limit)
for i in {1..5}; do curl -s http://localhost:3000/api/v1/users; done

# 6th request should fail
curl -i http://localhost:3000/api/v1/users
# Returns 429

# Wait for window to expire (1 minute)
sleep 60

# Try again - should succeed
curl -i http://localhost:3000/api/v1/users
# Returns 200 ✓

Test 3: Per-IP Rate Limiting

Verify different IPs have independent limits:

# IP 1: Make 5 requests (your local IP)
for i in {1..5}; do
  curl -s http://localhost:3000/api/v1/users > /dev/null
done

# IP 1: 6th request should fail
curl -i http://localhost:3000/api/v1/users
# Returns 429 ✗

# Simulate IP 2 (requires proxy or test infrastructure)
curl -H "X-Forwarded-For: 192.168.1.100" \
     -i http://localhost:3000/api/v1/users
# Returns 200 ✓ (separate counter for different IP)

Test 4: Redis Storage

Test Redis-based rate limiting:

# Edit configs/config.yaml
rate_limiter:
  storage: "redis"  # Switch to Redis

Wait 5 seconds for config reload.

# Make requests
curl http://localhost:3000/api/v1/users

# Check Redis for rate limit key
redis-cli GET "rate_limit:127.0.0.1"
# Output: "1" (one request made)

# Make 4 more requests
for i in {2..5}; do curl -s http://localhost:3000/api/v1/users > /dev/null; done

# Check counter again
redis-cli GET "rate_limit:127.0.0.1"
# Output: "5" (limit reached)

# Check TTL (seconds until reset)
redis-cli TTL "rate_limit:127.0.0.1"
# Output: "45" (45 seconds remaining)

Test 5: Concurrent Requests

Test rate limiting under concurrent load:

# Install Apache Bench (if not already installed)
# macOS: brew install httpd
# Linux: sudo apt-get install apache2-utils

# Send 100 requests with 10 concurrent connections
ab -n 100 -c 10 \
   -H "token: test-token-abc123" \
   http://localhost:3000/api/v1/users

# Check results
# With limit of 5 req/min: expect ~5 successful, ~95 rate limited

Integration Test Example

See tests/integration/ratelimit_test.go:

func TestRateLimiter_LimitExceeded(t *testing.T) {
    app := setupRateLimiterTestApp(t, 5, 1*time.Minute)
    
    // Make 5 requests (under limit)
    for i := 0; i < 5; i++ {
        req := httptest.NewRequest("GET", "/api/v1/test", nil)
        resp, _ := app.Test(req)
        assert.Equal(t, 200, resp.StatusCode)
    }
    
    // 6th request (over limit)
    req := httptest.NewRequest("GET", "/api/v1/test", nil)
    resp, _ := app.Test(req)
    assert.Equal(t, 429, resp.StatusCode)
    
    // Verify error response
    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    assert.Equal(t, float64(1003), result["code"])
    assert.Contains(t, result["msg"], "请求过于频繁")
}

Common Usage Patterns

Pattern 1: Tiered Rate Limits by User Type

Apply different rate limits based on user tier (free, premium, enterprise):

// Middleware to extract user tier
func tierBasedRateLimiter() fiber.Handler {
    return func(c *fiber.Ctx) error {
        userID := c.Locals(constants.ContextKeyUserID).(string)
        tier := getUserTier(userID)  // Fetch from DB or cache
        
        var max int
        switch tier {
        case "free":
            max = 100  // 100 req/min
        case "premium":
            max = 1000  // 1000 req/min
        case "enterprise":
            max = 10000  // 10000 req/min
        default:
            max = 10  // Very restrictive for unknown
        }
        
        limiter := middleware.RateLimiter(max, 1*time.Minute, nil)
        return limiter(c)
    }
}

// Apply to routes
app.Use(tierBasedRateLimiter())

Pattern 2: Different Limits for Different Endpoints

Apply strict limits to expensive operations, relaxed limits to cheap ones:

// Expensive endpoint: 10 requests/min
app.Post("/api/v1/reports/generate",
    middleware.RateLimiter(10, 1*time.Minute, nil),
    generateReportHandler)

// Cheap endpoint: 1000 requests/min
app.Get("/api/v1/users/:id",
    middleware.RateLimiter(1000, 1*time.Minute, nil),
    getUserHandler)

// Very cheap endpoint: no limit
app.Get("/health", healthHandler)

Pattern 3: Burst Protection with Short Windows

Prevent rapid bursts while allowing sustained traffic:

// Allow 10 requests per 10 seconds (burst protection)
app.Use(middleware.RateLimiter(10, 10*time.Second, nil))

// This allows:
// - 10 req in 1 second → OK
// - 60 req in 1 minute (evenly spaced) → OK
// - 100 req in 1 minute (bursty) → Some rejected

Pattern 4: Daily Quotas

Implement daily request quotas for APIs:

// Allow 10,000 requests per day
app.Use(middleware.RateLimiter(10000, 24*time.Hour, redisStorage))

// Requires Redis storage to persist across server restarts

Pattern 5: Graceful Degradation

Disable rate limiting for critical internal services:

// Check if request is from internal network
func skipRateLimitForInternal(c *fiber.Ctx) error {
    ip := c.IP()
    if isInternalIP(ip) {
        return c.Next()  // Skip rate limiting
    }
    
    // Apply rate limiting for external IPs
    limiter := middleware.RateLimiter(100, 1*time.Minute, nil)
    return limiter(c)
}

app.Use(skipRateLimitForInternal)

Pattern 6: Combined with Authentication

Apply rate limiting only after authentication:

// Authentication first
app.Use(middleware.KeyAuth(tokenValidator, logger))

// Then rate limiting (per authenticated user)
app.Use(middleware.RateLimiter(100, 1*time.Minute, nil))

// Anonymous endpoints (no auth, stricter rate limit)
app.Get("/public/data",
    middleware.RateLimiter(10, 1*time.Minute, nil),
    publicDataHandler)

Monitoring

Check Access Logs

Rate-limited requests are logged to logs/access.log:

# Filter for 429 status codes
grep '"status":429' logs/access.log | jq .

Example log entry:

{
  "timestamp": "2025-11-10T15:35:00Z",
  "level": "info",
  "method": "GET",
  "path": "/api/v1/users",
  "status": 429,
  "duration_ms": 0.345,
  "request_id": "550e8400-e29b-41d4-a716-446655440006",
  "ip": "127.0.0.1",
  "user_agent": "curl/7.88.1",
  "user_id": "user-789"
}

Count Rate-Limited Requests

# Count 429 responses in last hour
grep '"status":429' logs/access.log | \
  grep "$(date -u +%Y-%m-%dT%H)" | \
  wc -l

# Count by IP address
grep '"status":429' logs/access.log | \
  jq -r '.ip' | \
  sort | uniq -c | sort -rn

Monitor Redis Keys (Redis Storage Only)

# Count active rate limit keys
redis-cli KEYS "rate_limit:*" | wc -l

# List IPs currently tracked
redis-cli KEYS "rate_limit:*"

# Get counter for specific IP
redis-cli GET "rate_limit:192.168.1.1"

# Monitor in real-time
redis-cli --scan --pattern "rate_limit:*" | \
  while read key; do
    echo "$key: $(redis-cli GET $key)"
  done

Metrics and Alerting

Key metrics to track:

  1. Rate limit hit rate: (429 responses / total responses) * 100%

    # Calculate hit rate
    total=$(grep -c '"status"' logs/access.log)
    rate_limited=$(grep -c '"status":429' logs/access.log)
    echo "Rate limit hit rate: $(bc <<< "scale=2; $rate_limited * 100 / $total")%"
    
  2. Top rate-limited IPs: Identify potential abusers

    grep '"status":429' logs/access.log | jq -r '.ip' | \
      sort | uniq -c | sort -rn | head -10
    
  3. Rate limit effectiveness: Time series of 429 responses

    # Group by hour
    grep '"status":429' logs/access.log | \
      jq -r '.timestamp' | cut -d'T' -f1-2 | uniq -c
    

Alerting thresholds:

  • Alert if rate limit hit rate > 10% (too many legitimate requests being blocked)
  • Alert if single IP has > 100 rate-limited requests (potential abuse)
  • Alert if Redis storage fails (degrades to memory storage)

Troubleshooting

Problem: Rate limiter not working

Symptoms: All requests succeed, no 429 responses even after exceeding limit

Diagnosis:

# Check if rate limiter is enabled
grep "enable_rate_limiter" configs/config.yaml

Solutions:

  1. Ensure enable_rate_limiter: true in config
  2. Restart server or wait 5 seconds for config reload
  3. Check logs for "Configuration reloaded" message

Problem: Too many false positives (legitimate requests blocked)

Symptoms: Users frequently hit rate limits during normal usage

Diagnosis:

# Check current limit
grep -A3 "rate_limiter:" configs/config.yaml

Solutions:

  1. Increase max value (e.g., from 100 to 500)
  2. Increase expiration window (e.g., from "1m" to "5m")
  3. Implement tiered limits by user type
  4. Exclude internal IPs from rate limiting

Problem: Rate limits not shared across servers

Symptoms: In multi-server setup, each server enforces independent limits

Diagnosis:

# Check storage backend
grep "storage:" configs/config.yaml

Solution:

  • Change storage: "memory" to storage: "redis"
  • Ensure Redis is properly configured and accessible from all servers

Problem: Rate limits reset unexpectedly

Symptoms: Counters reset before expiration window

Possible causes:

  1. Server restart (with memory storage)

    • Solution: Use Redis storage for persistence
  2. Config reload when switching storage

    • Solution: Avoid switching between memory/Redis frequently
  3. Redis connection issues (with Redis storage)

    • Check logs for Redis errors
    • Verify Redis is running: redis-cli ping

Problem: Rate limiter slowing down responses

Symptoms: Increased response latency after enabling rate limiting

Diagnosis:

# Compare response times with rate limiter on/off
grep '"duration_ms"' logs/access.log | jq '.duration_ms' | \
  awk '{sum+=$1; count++} END {print "Average:", sum/count, "ms"}'

Solutions:

  1. If using Redis: Optimize Redis connection (increase pool size, reduce network latency)
  2. Switch to memory storage if precision is not critical
  3. Cache frequently accessed rate limit counters

Problem: Redis storage not working

Symptoms: Rate limiter falls back to memory storage, logs show Redis errors

Diagnosis:

# Check Redis connection
redis-cli -h your-redis-host -p 6379 ping

# Check application logs for Redis errors
grep -i "redis" logs/app.log | tail -20

Solutions:

  1. Verify Redis is running and accessible
  2. Check Redis credentials in config
  3. Ensure Redis connection pool is properly configured
  4. Check network connectivity to Redis server

Problem: Cannot see rate limit keys in Redis

Symptoms: redis-cli KEYS "rate_limit:*" returns empty

Possible causes:

  1. Rate limiter is disabled: Check enable_rate_limiter: true
  2. Using memory storage: Check storage: "redis"
  3. No requests have been made yet
  4. Wrong Redis database: Check redis.db in config

Diagnosis:

# Verify storage setting
grep "storage:" configs/config.yaml

# Make a test request
curl http://localhost:3000/api/v1/users

# Check Redis again
redis-cli KEYS "rate_limit:*"

Best Practices

1. Start Conservative, Then Relax

Begin with stricter limits and gradually increase based on monitoring:

# Initial deployment
rate_limiter:
  max: 100  # Conservative

# After monitoring (if no issues)
rate_limiter:
  max: 500  # Relaxed

2. Use Redis for Production

Always use Redis storage in production multi-server environments:

# Production config
rate_limiter:
  storage: "redis"

3. Monitor and Alert

Set up monitoring and alerts for:

  • High rate limit hit rate (> 10%)
  • Suspicious IPs with many rejections
  • Redis connection failures

4. Document Rate Limits

Inform API consumers about rate limits:

  • Include in API documentation
  • Return rate limit info in response headers (custom implementation)
  • Provide clear error messages

5. Combine with Authentication

Apply rate limiting after authentication for better control:

// Good: Authenticate first, then rate limit
app.Use(authMiddleware)
app.Use(rateLimitMiddleware)

6. Test Before Deploying

Always test rate limits in staging before production:

# Load test with rate limiting enabled
ab -n 1000 -c 50 http://staging-api/endpoint

7. Plan for Failures

Ensure rate limiter fails gracefully if Redis is unavailable (already implemented):

  • Falls back to memory storage
  • Logs errors but continues serving requests

Summary

Configuration Single Server Multi-Server Development Production
enable_rate_limiter Optional Recommended false true
max 100-1000 1000-5000 1000+ 100-5000
expiration "1m" "1m" "1m" "1m"
storage "memory" "redis" "memory" "redis"

Key Takeaways:

  • Rate limiting protects your API from abuse
  • IP-based limiting ensures fair usage
  • Redis storage enables distributed rate limiting
  • Configuration is hot-reloadable (no restart needed)
  • Monitor 429 responses to tune limits
  • Always test in staging before production

For more information, see: