主要变更: - ✅ 完成所有文档任务(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(优秀) 项目状态:✅ 已完成,待部署
26 KiB
Rate Limiting Guide
Comprehensive guide for configuring and using the rate limiting middleware in Junhong CMP Fiber.
Table of Contents
- Overview
- Configuration
- Storage Options
- Code Examples
- Testing
- Common Usage Patterns
- Monitoring
- Troubleshooting
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:
- Extract client IP from request
- Check counter for IP in storage (key:
rate_limit:{ip}) - If counter < max: increment counter and allow request
- If counter >= max: reject with 429 status
- Counter automatically resets after
expirationduration
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:
-
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")%" -
Top rate-limited IPs: Identify potential abusers
grep '"status":429' logs/access.log | jq -r '.ip' | \ sort | uniq -c | sort -rn | head -10 -
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:
- Ensure
enable_rate_limiter: truein config - Restart server or wait 5 seconds for config reload
- 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:
- Increase
maxvalue (e.g., from 100 to 500) - Increase
expirationwindow (e.g., from "1m" to "5m") - Implement tiered limits by user type
- 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"tostorage: "redis" - Ensure Redis is properly configured and accessible from all servers
Problem: Rate limits reset unexpectedly
Symptoms: Counters reset before expiration window
Possible causes:
-
Server restart (with memory storage)
- Solution: Use Redis storage for persistence
-
Config reload when switching storage
- Solution: Avoid switching between memory/Redis frequently
-
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:
- If using Redis: Optimize Redis connection (increase pool size, reduce network latency)
- Switch to memory storage if precision is not critical
- 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:
- Verify Redis is running and accessible
- Check Redis credentials in config
- Ensure Redis connection pool is properly configured
- Check network connectivity to Redis server
Problem: Cannot see rate limit keys in Redis
Symptoms: redis-cli KEYS "rate_limit:*" returns empty
Possible causes:
- Rate limiter is disabled: Check
enable_rate_limiter: true - Using memory storage: Check
storage: "redis" - No requests have been made yet
- Wrong Redis database: Check
redis.dbin 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: