# 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.

### Coverage Scope

Rate limiting is applied to the following business API route groups:
- ✅ `/api/admin/*` - Admin management APIs
- ✅ `/api/h5/*` - H5 client APIs
- ✅ `/api/c/v1/*` - Personal customer APIs

The following routes are **explicitly excluded** from rate limiting:
- ❌ `/api/callback/*` - Third-party callback routes (payment, webhooks)
- ❌ `/health` - Health check endpoint
- ❌ `/ready` - Readiness check endpoint

### 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
- **Selective coverage**: Applied only to business API routes

### 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_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_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_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: Apply rate limiter to business API route groups
    if config.GetConfig().Middleware.EnableRateLimiter {
        rateLimitMiddleware := createRateLimiter(cfg, appLogger)
        
        // Admin API group
        adminGroup := app.Group("/api/admin")
        adminGroup.Use(rateLimitMiddleware)
        
        // H5 API group
        h5Group := app.Group("/api/h5")
        h5Group.Use(rateLimitMiddleware)
        
        // Personal customer API group
        personalGroup := app.Group("/api/c/v1")
        personalGroup.Use(rateLimitMiddleware)
    }
    
    // Health check (excluded from rate limiting)
    app.Get("/health", healthHandler)
    
    // Callback routes (excluded from rate limiting)
    callbackGroup := app.Group("/api/callback")
    callbackGroup.Post("/payment", paymentCallbackHandler)
    
    app.Listen(":3000")
}

func createRateLimiter(cfg *config.Config, logger *zap.Logger) fiber.Handler {
    var storage fiber.Storage = nil
    
    if cfg.Middleware.RateLimiter.Storage == "redis" {
        storage = middleware.NewRedisStorage(/* ... */)
    }
    
    return middleware.RateLimiter(
        cfg.Middleware.RateLimiter.Max,
        cfg.Middleware.RateLimiter.Expiration,
        storage,
    )
}
```

### 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 to specific route groups only
rateLimitMiddleware := middleware.RateLimiter(100, 1*time.Minute, nil)

// Business API routes (rate limited)
adminGroup := app.Group("/api/admin")
adminGroup.Use(rateLimitMiddleware)

// Health check (excluded from rate limiting)
app.Get("/health", healthHandler)  // Not rate limited

// Callback routes (excluded from rate limiting)
callbackGroup := app.Group("/api/callback")
callbackGroup.Post("/payment", paymentCallbackHandler)  // Not rate limited
```

### 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)
