Lesson 20 / 25
Rate Limiting
Tickers and token buckets.
Smoothing the request rate
A bounded worker pool limits how many operations run at once; a rate limiter limits how often they start. The simplest limiter is a time.Ticker: wait for a tick before each request, giving a steady rate with no bursts. For production use, golang.org/x/time/rate implements a token bucket: rate.NewLimiter(r, b) refills at r tokens per second (use rate.Every(d) to express one token per interval) and holds at most b tokens, allowing bursts of up to b. limiter.Wait(ctx) blocks until a token is available or the context is done; Allow() returns immediately with true or false, which suits rejecting excess HTTP requests; Reserve() tells you how long to wait. One limiter is safe to share between goroutines.
A ticker limiter and a token bucket
Both throttle calls to an external API.
package main
import (
"context"
"fmt"
"time"
"golang.org/x/time/rate"
)
func callAPI(i int) { fmt.Println("call", i) }
func main() {
// 1. Ticker: one call every 200ms, no bursts.
t := time.NewTicker(200 * time.Millisecond)
defer t.Stop()
for i := range 3 {
<-t.C
callAPI(i)
}
// 2. Token bucket: 5 per second on average, bursts of up to 10.
lim := rate.NewLimiter(rate.Limit(5), 10)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
for i := range 20 {
if err := lim.Wait(ctx); err != nil { // blocks or fails on ctx
fmt.Println("stopping:", err)
return
}
callAPI(i)
}
}Limit per client, not only globally
An HTTP server often keeps one limiter per API key or client IP, stored in a mutex-protected map with eviction of idle entries, so one noisy client cannot use the whole budget.
Quick check: In rate.NewLimiter(r, b), what does b control?
- The number of goroutines that may call Wait
- The bucket size, i.e. the maximum burst of events allowed at once
- The timeout for Wait
- The number of seconds between tokens
Answer
The bucket size, i.e. the maximum burst of events allowed at once — r is the refill rate; b is the burst size.