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 = limiter

View on GitHub (pinned to 668db92c2c)

Solutions

  1. Scale out the service or raise the limiter's budget so real traffic fits (larger bbr/ulimit settings or more replicas)
  2. Pick the limiter strategy that matches the workload: bbr adapts to load, fixed qps does not
  3. Make clients treat errors.Is(err, ratelimit.ErrLimitExceed) as retryable with exponential backoff and jitter
  4. 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

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

Related errors


AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16). Data as JSON: /api/errors/cf4e8f05c94c932b. Report an issue: GitHub.