Rate Limiting
A pattern for controlling the rate at which operations are performed to prevent resource exhaustion.
Contributors
When to Use
- When calling external APIs with rate limits
- When protecting your service from being overwhelmed by requests
- When controlling resource consumption (database queries, file operations)
- When implementing throttling for background jobs
When NOT to Use
- For internal operations where throughput is critical
- When the cost of rate limiting exceeds the cost of occasional overload
- In simple applications with predictable low load
Rate Limiting controls the frequency of operations to prevent resource exhaustion and respect external service constraints. Go’s golang.org/x/time/rate package provides a token bucket implementation that’s simple and efficient.
Implementation
package ratelimit
import (
"context"
"net/http"
"time"
"golang.org/x/time/rate"
)
// APIClient demonstrates rate limiting for external API calls
type APIClient struct {
limiter *rate.Limiter
}
// NewAPIClient creates a client with specified requests per second and burst capacity
func NewAPIClient(rps int, burst int) *APIClient {
return &APIClient{
limiter: rate.NewLimiter(rate.Limit(rps), burst),
}
}
// Call makes a rate-limited API request
func (c *APIClient) Call(ctx context.Context) error {
// Wait blocks until a token is available or context is cancelled
if err := c.limiter.Wait(ctx); err != nil {
return err
}
// Perform the actual API call
return c.doRequest()
}
// TryCall attempts a non-blocking rate-limited call
func (c *APIClient) TryCall() (bool, error) {
// Allow returns false if no token is available
if !c.limiter.Allow() {
return false, nil
}
return true, c.doRequest()
}
func (c *APIClient) doRequest() error {
// Actual implementation here
return nil
}
// RateLimitMiddleware limits HTTP requests per second
func RateLimitMiddleware(rps int, burst int) func(http.Handler) http.Handler {
limiter := rate.NewLimiter(rate.Limit(rps), burst)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
Usage
package main
import (
"context"
"fmt"
"log"
"net/http"
)
func main() {
// API client with rate limiting
client := ratelimit.NewAPIClient(5, 10) // 5 req/s, burst of 10
ctx := context.Background()
for i := 0; i < 20; i++ {
if err := client.Call(ctx); err != nil {
log.Fatal(err)
}
fmt.Printf("Request %d completed\n", i+1)
}
// HTTP server with rate limiting
mux := http.NewServeMux()
mux.HandleFunc("/api", handleAPI)
// Limit to 100 requests/second with burst of 200
handler := ratelimit.RateLimitMiddleware(100, 200)(mux)
http.ListenAndServe(":8080", handler)
}
func handleAPI(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("API response"))
}
Benefits
- Resource Protection: Prevents overwhelming external services or internal resources
- Graceful Degradation: System remains responsive under high load
- Cost Control: Avoids exceeding paid API quotas or rate limits