Rate Limiting
Control the rate of operations using tickers and the golang.org/x/time/rate token bucket.
Rate limiting controls how frequently an operation can proceed. Go's standard approach uses a time.Ticker as a simple token source, or the golang.org/x/time/rate package for full token-bucket semantics with burst support.
A basic rate limiter uses a ticker channel. Each tick grants permission for one request. The sender blocks until the next tick fires, naturally enforcing the rate.
package main
import (
"fmt"
"time"
)
func main() {
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
// allow 1 request per 200ms
limiter := time.NewTicker(200 * time.Millisecond)
defer limiter.Stop()
for req := range requests {
<-limiter.C
fmt.Println("request", req, time.Now().Format("15:04:05.000"))
}
}For burst allowances, use golang.org/x/time/rate. A Limiter with rate r and burst b permits up to b events at once and then allows r events per second:
import "golang.org/x/time/rate"
limiter := rate.NewLimiter(rate.Every(100*time.Millisecond), 3) // 10/s, burst of 3
for i := 0; i < 10; i++ {
if err := limiter.Wait(ctx); err != nil {
return err
}
process(i)
}limiter.Allow() is non-blocking - useful when you want to drop rather than queue:
if !limiter.Allow() {
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
return
}In production
The time/rate package implements a token bucket that supports burst allowances. Use it at the service boundary to protect downstream dependencies. For multi-instance deployments, local rate limiting is not sufficient - you need a distributed counter (Redis INCR with TTL, or a sidecar rate limiter like Envoy).
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free