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

1050 lines
26 KiB
Markdown

# Rate Limiting Guide
Comprehensive guide for configuring and using the rate limiting middleware in Junhong CMP Fiber.
## Table of Contents
- [Overview](#overview)
- [Configuration](#configuration)
- [Storage Options](#storage-options)
- [Code Examples](#code-examples)
- [Testing](#testing)
- [Common Usage Patterns](#common-usage-patterns)
- [Monitoring](#monitoring)
- [Troubleshooting](#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:
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`:
```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**:
```yaml
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**:
```yaml
# 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**:
```yaml
# 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**:
```yaml
# Memory storage (single server)
rate_limiter:
storage: "memory"
# Redis storage (distributed)
rate_limiter:
storage: "redis"
```
### Environment-Specific Configurations
#### Development (`configs/config.dev.yaml`)
```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`)
```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`)
```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**:
```yaml
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**:
```yaml
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**:
```bash
# 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**:
```yaml
# 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)
```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)
```go
// 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
```go
// 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
```go
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`:
```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**:
```bash
# 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)**:
```json
{
"code": 1003,
"data": null,
"msg": "请求过于频繁",
"timestamp": "2025-11-10T15:35:00Z"
}
```
### Test 2: Window Reset
**Verify counter resets after expiration**:
```bash
# 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**:
```bash
# 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**:
```yaml
# Edit configs/config.yaml
rate_limiter:
storage: "redis" # Switch to Redis
```
Wait 5 seconds for config reload.
```bash
# 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**:
```bash
# 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`:
```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):
```go
// 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:
```go
// 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:
```go
// 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:
```go
// 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:
```go
// 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:
```go
// 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`:
```bash
# Filter for 429 status codes
grep '"status":429' logs/access.log | jq .
```
**Example log entry**:
```json
{
"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
```bash
# 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)
```bash
# 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%`
```bash
# 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
```bash
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
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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**:
```bash
# 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:
```yaml
# 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:
```yaml
# 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:
```go
// Good: Authenticate first, then rate limit
app.Use(authMiddleware)
app.Use(rateLimitMiddleware)
```
### 6. Test Before Deploying
Always test rate limits in staging before production:
```bash
# 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:
- [Quick Start Guide](../specs/001-fiber-middleware-integration/quickstart.md)
- [README](../README.md)
- [Implementation Plan](../specs/001-fiber-middleware-integration/plan.md)