go-kratos/kratos · error · github.com/go-kratos/kratos/v3/errors.Error
RATELIMIT
RATELIMIT
Error message
service unavailable due to rate limit exceeded
What it means
ErrLimitExceed is the error the kratos ratelimit middleware returns (HTTP/gRPC status 429, reason RATELIMIT) when the configured Limiter denies a request. The middleware calls the limiter per request and, when it reports the limit is exceeded, aborts the handler and surfaces this error. It exists so a service can shed load deliberately instead of degrading latency for every caller.
Source
Thrown at middleware/ratelimit/ratelimit.go:12
package ratelimit
import (
"context"
"github.com/go-kratos/kratos/v3/errors"
internalratelimit "github.com/go-kratos/kratos/v3/internal/ratelimit"
"github.com/go-kratos/kratos/v3/middleware"
)
// ErrLimitExceed is service unavailable due to rate limit exceeded.
var ErrLimitExceed = errors.New(429, "RATELIMIT", "service unavailable due to rate limit exceeded")
// DoneFunc records request completion.
type DoneFunc = internalratelimit.DoneFunc
// DoneInfo contains request completion metadata.
type DoneInfo = internalratelimit.DoneInfo
// Limiter is a rate limiter.
type Limiter = internalratelimit.Limiter
// Option is ratelimit option.
type Option func(*options)
// WithLimiter set Limiter implementation,
// default is bbr limiter
func WithLimiter(limiter Limiter) Option {
return func(o *options) {
o.limiter = limiterView on GitHub (pinned to 668db92c2c)
Solutions
- Scale out the service or raise the limiter's budget so real traffic fits (larger bbr/ulimit settings or more replicas)
- Pick the limiter strategy that matches the workload: bbr adapts to load, fixed qps does not
- Make clients treat errors.Is(err, ratelimit.ErrLimitExceed) as retryable with exponential backoff and jitter
- Shed load upstream (gateway/API limits) so the internal middleware rarely triggers
Example fix
// before
// server with a limiter sized below real traffic
srv.Use(ratelimit.Server(limiter)) // bursts -> 429 RATELIMIT
// after
// 1) size/scale the limiter to load
limiter = newLimiter(higherLimit)
// 2) client side: back off and retry on 429
if errors.Is(err, ratelimit.ErrLimitExceed) {
time.Sleep(backoff(attempt))
return retry(ctx)
} Defensive patterns
Strategy: retry
Try / catch
resp, err := client.Call(ctx, req)
if err != nil {
if errors.Is(err, ratelimit.ErrLimitExceed) { // 429 RATELIMIT
time.Sleep(backoff(attempt)) // exponential + jitter
continue // retry until the retry budget is spent
}
return err
} Prevention
- Load-test to size limiter thresholds before release
- Give every client a retry budget with exponential backoff and jitter for 429
- Scale horizontally instead of tightening per-instance limits
- Shed load at the gateway so internal rate limit middleware is the last line of defense
When it happens
Trigger: A handler or invocation wrapped by ratelimit.Server(limiter) / ratelimit.Client(limiter) calls the limiter's Allow(); the limiter (bbr or a custom implementation) reports the request exceeds the allowed rate and the middleware returns the 429 RATELIMIT error instead of invoking the handler.
Common situations: Traffic spikes beyond the bbr limiter's thresholds; a fixed-qps limiter sized below real traffic; per-instance limits on scaled-out deployments each enforcing their own quota; clients without backoff retrying immediately and amplifying load.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- RATELIMIT
- CIRCUITBREAKER
- 🚫 go.mod don't exists in %s
- 🚫 failed to get relative path: %v
- 🚫 failed to parse `go.mod`: %v
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/cf4e8f05c94c932b.
Report an issue: GitHub.