Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow requests to proceed on cache server connection errors #6894

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions admin/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,6 @@ func (s *Server) checkRateLimit(ctx context.Context) (context.Context, error) {
return ctx, fmt.Errorf("server context does not have a method")
}

// If the connection to the cache server is lost, skip rate limiting.
if err := s.limiter.Ping(ctx); err != nil {
s.logger.Warn("skipping rate limiting due to cache connection error", zap.Error(err))
return ctx, nil
}

// Don't rate limit superusers. This is useful for scripting.
if auth.GetClaims(ctx).Superuser(ctx) {
return ctx, nil
Expand Down
70 changes: 61 additions & 9 deletions runtime/pkg/ratelimit/ratelimiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package ratelimit

import (
"context"
"errors"
"fmt"
"io"
"math"
"net"
"strings"

"github.com/go-redis/redis_rate/v10"
"github.com/redis/go-redis/v9"
Expand Down Expand Up @@ -42,6 +46,13 @@ func (l *Redis) Limit(ctx context.Context, limitKey string, limit redis_rate.Lim

rateResult, err := l.Allow(ctx, limitKey, limit)
if err != nil {
// If the error is a server connection error, we should not return an error.
// This is because the server may be temporarily unavailable, and we should not block the request.
// The client should retry the request.
if isServerConnError(err) {
return nil
}

return err
}

Expand Down Expand Up @@ -72,15 +83,14 @@ func (n Noop) Ping(ctx context.Context) error {
return nil
}

var Default = redis_rate.PerMinute(180)

var Sensitive = redis_rate.PerMinute(30)

var Public = redis_rate.PerMinute(750)

var Unlimited = redis_rate.PerSecond(math.MaxInt)

var Zero = redis_rate.Limit{}
// Common rate limit configurations
var (
Default = redis_rate.PerMinute(180)
Sensitive = redis_rate.PerMinute(30)
Public = redis_rate.PerMinute(750)
Unlimited = redis_rate.PerSecond(math.MaxInt)
Zero = redis_rate.Limit{}
)

type QuotaExceededError struct {
message string
Expand All @@ -101,3 +111,45 @@ func AuthLimitKey(methodName, authID string) string {
func AnonLimitKey(methodName, peer string) string {
return fmt.Sprintf("anon:%s:%s", methodName, peer)
}

// Common Redis error messages
const (
errMaxClients = "ERR max number of clients reached"
errLoading = "LOADING "
errReadOnly = "READONLY "
errMasterDown = "MASTERDOWN "
errClusterDown = "CLUSTERDOWN "
errTryAgain = "TRYAGAIN "
)

func isServerConnError(err error) bool {
if err == nil {
return false
}

// Check specific I/O errors
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return true
}

// Check for network-specific errors
var netErr net.Error
if errors.As(err, &netErr) {
return true
}

// Check specific Redis error strings
s := err.Error()
if s == errMaxClients {
return true
}
if strings.HasPrefix(s, errLoading) ||
strings.HasPrefix(s, errReadOnly) ||
strings.HasPrefix(s, errMasterDown) ||
strings.HasPrefix(s, errClusterDown) ||
strings.HasPrefix(s, errTryAgain) {
return true
}

return false
}
6 changes: 0 additions & 6 deletions runtime/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,6 @@ func mapGRPCError(err error) error {
}

func (s *Server) checkRateLimit(ctx context.Context) (context.Context, error) {
// If the connection to the cache server is lost, skip rate limiting.
if err := s.limiter.Ping(ctx); err != nil {
s.logger.Warn("skipping rate limiting due to cache connection error", zap.Error(err))
return ctx, nil
}

// Any request type might be limited separately as it is part of Metadata
// Any request type might be excluded from this limit check and limited later,
// e.g. in the corresponding request handler by calling s.limiter.Limit(ctx, "limitKey", redis_rate.PerMinute(100))
Expand Down
Loading