Rate Limiting & DDoS — Controlling Request Volume
Rate limiting caps per-client request rate. DDoS protection absorbs volumetric attacks at the CDN/network edge before they reach your origin.
When to use
- All public APIs; especially login, signup, payment endpoints
- Token bucket for API rate limits; CDN-level DDoS protection for volumetric attacks
Tradeoffs
- Too aggressive blocks legitimate traffic (especially behind NAT — many users same IP)
- Shared counters need distributed state (Redis atomic operations)
- Go
- Python
// Token bucket via Redis atomic operations
func Allow(ctx context.Context, rdb *redis.Client, key string, burst, rate int) (bool, error) {
now := time.Now().Unix()
pipe := rdb.Pipeline()
// refill tokens proportional to elapsed time
pipe.SetNX(ctx, key+":ts", now, 24*time.Hour)
pipe.SetNX(ctx, key+":tokens", burst, 24*time.Hour)
pipe.Exec(ctx)
tokens, err := rdb.DecrBy(ctx, key+":tokens", 1).Result()
if err != nil {
return false, err
}
if tokens < 0 {
rdb.IncrBy(ctx, key+":tokens", 1) // restore
return false, nil // 429
}
return true, nil
}
import redis, time
def allow(rdb: redis.Redis, key: str, burst: int, rate: int) -> bool:
now = int(time.time())
token_key = f"{key}:tokens"
pipe = rdb.pipeline()
pipe.setnx(token_key, burst)
pipe.expire(token_key, 86400)
pipe.execute()
remaining = rdb.decr(token_key)
if remaining < 0:
rdb.incr(token_key) # restore
return False # 429 Too Many Requests
return True
def handle(request, rdb: redis.Redis) -> int:
key = f"rl:user:{request.user_id}:endpoint:{request.path}"
if not allow(rdb, key, burst=20, rate=10):
return 429
return 200
Gotcha: Rate limit by authenticated user ID AND by IP AND by endpoint. A single user hammering one endpoint should not trigger a global block for all users on that IP.