Skip to content
Zever
Home

Rate limiting

Bound request rates per key with token buckets in memory or shared over Redis.

1 min readEdit on GitHub

ratelimit bounds how fast one key (user, IP, token) can use an endpoint. Limiter.Allow returns a Decision with Allowed, RetryAfter, and Remaining; mapping a denial to 429 and the fail mode are your call.

The default is memory with rate: 10, burst: 20. rate (refill per second) and burst (bucket size) are both required, over zero, and finite.

Terminal window
RATELIMIT_ADAPTER=memory
RATELIMIT_RATE=10
RATELIMIT_BURST=20
main.go
rl, err := c.RateLimit() // c.Ratelimit() is an alias
if err != nil {
return err
}
defer rl.Close()
for i := 0; i < 3; i++ {
dec, err := rl.Allow(ctx, "book:guest-1", 1)
fmt.Println(dec.Allowed, dec.Remaining, err)
}
true 19 <nil>
true 18 <nil>
true 17 <nil>

The bookings handler calls Allow(ctx, "book:"+guest, 1), returns 429 when !dec.Allowed, and 500 on error. Charges cap at burst. Generated servers wire middleware.RateLimit (HTTP 429 + Retry-After) and a gRPC interceptor (ResourceExhausted) when RATELIMIT_ADAPTER is set; both fail open, and WithFailMode(FailClosed) opts in.

Per-instance buckets multiply the effective limit (limit x replica count); switch to redis once more than one replica serves traffic.

Terminal window
RATELIMIT_ADAPTER=redis
RATELIMIT_RATE=100
RATELIMIT_BURST=200
RATELIMIT_REDIS_ADDR=127.0.0.1:6379
RATELIMIT_REDIS_PREFIX=rl:
Terminal window
RATELIMIT_ADAPTER=redis
RATELIMIT_RATE=100
RATELIMIT_BURST=200

Use a distinct redis.prefix per environment. File durations are integer nanoseconds (idle_ttl default 10m, sweep_interval 1m); env accepts "5s". Validate rejects non-positive rate/burst, negative idle_ttl/sweep_interval, and bad Redis addr/prefix. Adapters: ratelimit/memory (default) and ratelimit/redis. Keys: reference.

  • Limiter.Close() error takes no context; the container joins errors with errors.Join, and panics become *ClosePanicError.
  • Keys can be PII (IP, user ID); InvalidKeyError reports only length.
  • Never log raw option maps; use cfg.RedactedServices().
The effective limit looks multiplied

Replicas run memory buckets; move to redis, or divide the per-instance rate by the replica count.

rate must be > 0 and finite

rate and burst are required; an empty options block fails Validate (the bookings example uses RATELIMIT_RATE=10, RATELIMIT_BURST=20 in .env).

InvalidKeyError on Allow

Keys cap at 256 bytes (MaxKeyLen) with no control bytes; hash or truncate long composite keys first.

Locks and idempotency · Queue · Observability