Skip to content

fix: disable rate limiting when Redis backend is not configured#1979

Open
NJX-njx wants to merge 1 commit into
vllm-project:mainfrom
NJX-njx:fix/1874-noop-ratelimiter
Open

fix: disable rate limiting when Redis backend is not configured#1979
NJX-njx wants to merge 1 commit into
vllm-project:mainfrom
NJX-njx:fix/1874-noop-ratelimiter

Conversation

@NJX-njx
Copy link
Copy Markdown
Contributor

@NJX-njx NJX-njx commented Mar 4, 2026

Summary

Fixes #1874

When Redis is not configured (REDIS_HOST is empty or Redis connection fails), the gateway plugin now gracefully falls back to a NoOpRateLimiter instead of crashing with a fatal error or rejecting all requests.

Changes

  • pkg/plugins/gateway/ratelimiter/noop.go: Add NoOpRateLimiter implementation that always allows requests through, effectively disabling rate limiting
  • pkg/plugins/gateway/ratelimiter/noop_test.go: Add unit tests for NoOpRateLimiter
  • pkg/utils/redis.go: Add TryGetRedisClient() that returns nil instead of calling klog.Fatalf when Redis is unavailable
  • pkg/plugins/gateway/gateway.go: Modify NewServer to use NoOpRateLimiter when redisClient is nil
  • cmd/plugins/main.go: Use TryGetRedisClient() and handle nil redis client in defer

Behavior

Scenario Before After
Redis configured and running Rate limiting works Rate limiting works (no change)
Redis not configured (REDIS_HOST empty) Fatal crash on startup Starts normally, rate limiting disabled
Redis configured but unreachable Fatal crash on startup Starts normally, rate limiting disabled with warning

When Redis is not configured (REDIS_HOST is empty or connection fails),
the gateway plugin now gracefully falls back to a NoOpRateLimiter instead
of crashing with a fatal error or rejecting all requests.

Changes:
- Add NoOpRateLimiter implementation that always allows requests
- Add TryGetRedisClient() that returns nil instead of fatally crashing
- Modify NewServer to use NoOpRateLimiter when redisClient is nil
- Update gateway plugin main to use TryGetRedisClient
- Add unit tests for NoOpRateLimiter

Fixes vllm-project#1874
Copilot AI review requested due to automatic review settings March 4, 2026 10:26
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where the gateway plugin would crash fatally if the Redis backend for rate limiting was not configured or became unreachable. By introducing a NoOpRateLimiter and a more robust Redis client initialization, the system now gracefully falls back to disabling rate limiting in such scenarios, ensuring the application remains operational and stable without requiring Redis.

Highlights

  • Graceful Redis Handling: Introduced TryGetRedisClient() in pkg/utils/redis.go to gracefully handle Redis connection failures or unconfigured REDIS_HOST by returning nil instead of causing a fatal error.
  • No-Operation Rate Limiter: Implemented a NoOpRateLimiter that always allows requests, effectively disabling rate limiting when the Redis backend is unavailable.
  • Conditional Rate Limiter Initialization: Modified the gateway.go server initialization to conditionally use the NoOpRateLimiter if the Redis client is not successfully initialized.
  • Main Application Update: Updated cmd/plugins/main.go to utilize TryGetRedisClient() and ensure proper closure of the Redis client only when it's successfully initialized.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • cmd/plugins/main.go
    • Replaced GetRedisClient() with utils.TryGetRedisClient() to prevent fatal crashes on Redis connection issues.
    • Added a nil check before attempting to close the Redis client in the defer function to avoid panics if Redis was not initialized.
  • pkg/plugins/gateway/gateway.go
    • Modified the NewServer function to check if redisClient is nil.
    • Conditionally initializes ratelimiter.RateLimiter to use NewNoOpRateLimiter() if redisClient is nil, otherwise uses NewRedisAccountRateLimiter().
  • pkg/plugins/gateway/ratelimiter/noop.go
    • Added a new file defining the noOpRateLimiter struct and its constructor NewNoOpRateLimiter.
    • Implemented Get, GetLimit, and Incr methods for noOpRateLimiter to always return 0 and nil error, effectively allowing all requests.
    • Added klog.Info message when NoOpRateLimiter is created to indicate that rate limiting is disabled.
  • pkg/plugins/gateway/ratelimiter/noop_test.go
    • Added a new file containing unit tests for NoOpRateLimiter's Get, GetLimit, and Incr methods.
    • Included a test to ensure noOpRateLimiter correctly implements the RateLimiter interface.
  • pkg/utils/redis.go
    • Introduced TryGetRedisClient() function as an alternative to GetRedisClient().
    • The new function checks for the REDIS_HOST environment variable and returns nil if it's empty, logging an informational message.
    • It attempts to connect to Redis and returns nil with a warning log if the connection fails, instead of calling klog.Fatalf.
Activity
  • No human activity recorded for this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19077b0e12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/plugins/main.go
}

redisClient := utils.GetRedisClient()
redisClient := utils.TryGetRedisClient()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard user lookup when Redis is optional

Switching startup to TryGetRedisClient() allows redisClient to be nil, but request handling still assumes Redis is always present: HandleRequestHeaders calls utils.GetUser(..., s.redisClient) whenever a user header is set, and GetUser dereferences the client directly. In deployments without Redis (the new supported mode), any request carrying user will panic the gateway process instead of gracefully continuing with rate limiting disabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a graceful fallback mechanism for the rate limiting feature. When a Redis backend is not configured or unavailable, the system now uses a NoOpRateLimiter which effectively disables rate limiting, preventing the application from crashing. The changes include a new TryGetRedisClient function that doesn't cause a fatal error on connection failure, a NoOpRateLimiter implementation, and updates to the gateway and main application logic to use these new components. The changes are logical and well-tested. I have one suggestion to improve maintainability by reducing code duplication in the Redis utility functions.

Comment thread pkg/utils/redis.go
Comment on lines +45 to +70
// TryGetRedisClient attempts to connect to Redis and returns the client if successful.
// Returns nil if Redis is not configured or connection fails, instead of fatally crashing.
func TryGetRedisClient() *redis.Client {
redisHost := LoadEnv("REDIS_HOST", "")
if redisHost == "" {
klog.Info("REDIS_HOST is not set, Redis client will not be initialized")
return nil
}

redisPort := LoadEnv("REDIS_PORT", "6379")
redisPassword := LoadEnv("REDIS_PASSWORD", "")
client := redis.NewClient(&redis.Options{
Addr: redisHost + ":" + redisPort,
Password: redisPassword,
DB: 0,
})

pong, err := client.Ping(context.Background()).Result()
if err != nil {
klog.Warningf("Failed to connect to Redis at %s:%s: %v. Rate limiting will be disabled.", redisHost, redisPort, err)
client.Close()
return nil
}
klog.Infof("Connected to Redis: %s", pong)
return client
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and reduce code duplication, you could extract the Redis client creation and connection logic into a private helper function. This new function could then be used by both TryGetRedisClient and the existing GetRedisClient function. This would centralize the connection logic and make future changes easier.

// _newRedisClient creates and pings a new Redis client.
// It returns the client, the PONG response, and an error if connection fails.
func _newRedisClient(host, port, password string) (*redis.Client, string, error) {
	client := redis.NewClient(&redis.Options{
		Addr:     host + ":" + port,
		Password: password,
		DB:       0,
	})

	pong, err := client.Ping(context.Background()).Result()
	if err != nil {
		// It's important to close the client on failure to avoid resource leaks.
		client.Close()
		return nil, "", err
	}
	return client, pong, nil
}

// TryGetRedisClient attempts to connect to Redis and returns the client if successful.
// Returns nil if Redis is not configured or connection fails, instead of fatally crashing.
func TryGetRedisClient() *redis.Client {
	redisHost := LoadEnv("REDIS_HOST", "")
	if redisHost == "" {
		klog.Info("REDIS_HOST is not set, Redis client will not be initialized")
		return nil
	}

	redisPort := LoadEnv("REDIS_PORT", "6379")
	redisPassword := LoadEnv("REDIS_PASSWORD", "")

	client, pong, err := _newRedisClient(redisHost, redisPort, redisPassword)
	if err != nil {
		klog.Warningf("Failed to connect to Redis at %s:%s: %v. Rate limiting will be disabled.", redisHost, redisPort, err)
		return nil
	}

	klog.Infof("Connected to Redis: %s", pong)
	return client
}

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes issue #1874 by making the gateway plugin resilient to missing/unavailable Redis: instead of crashing or inadvertently enforcing limits, it falls back to a no-op rate limiter when Redis isn’t configured or can’t be reached.

Changes:

  • Add a NoOpRateLimiter implementation (and unit tests) that effectively disables rate limiting.
  • Introduce utils.TryGetRedisClient() which returns nil (and logs) when Redis is not configured/unreachable.
  • Update gateway plugin startup to use the no-op limiter when redisClient is nil, and safely handle nil in the deferred Redis close.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/utils/redis.go Adds TryGetRedisClient() to avoid fatal startup failures when Redis is absent/unreachable.
pkg/plugins/gateway/ratelimiter/noop.go Introduces a no-op RateLimiter implementation used as a fallback.
pkg/plugins/gateway/ratelimiter/noop_test.go Adds unit tests validating NoOpRateLimiter behavior and interface conformance.
pkg/plugins/gateway/gateway.go Selects Redis-backed vs no-op rate limiter based on whether redisClient is nil.
cmd/plugins/main.go Switches to TryGetRedisClient() and guards defer-close for nil Redis client.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/utils/redis.go

pong, err := client.Ping(context.Background()).Result()
if err != nil {
klog.Warningf("Failed to connect to Redis at %s:%s: %v. Rate limiting will be disabled.", redisHost, redisPort, err)
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TryGetRedisClient the warning log hard-codes "Rate limiting will be disabled", but this helper is in pkg/utils and the returned Redis client is also used for other features (e.g., cache/tracing). Consider making the message Redis-generic (e.g., "continuing without Redis") and letting callers log the feature-specific impact (rate limiting, tracing, etc.).

Suggested change
klog.Warningf("Failed to connect to Redis at %s:%s: %v. Rate limiting will be disabled.", redisHost, redisPort, err)
klog.Warningf("Failed to connect to Redis at %s:%s: %v. Continuing without Redis.", redisHost, redisPort, err)

Copilot uses AI. Check for mistakes.
// NewNoOpRateLimiter creates a rate limiter that performs no rate limiting.
// This is used as a fallback when Redis is not available or not configured.
func NewNoOpRateLimiter() RateLimiter {
klog.Info("Rate limiting is disabled: no Redis backend configured")
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The log message in NewNoOpRateLimiter says "no Redis backend configured", but this fallback is also used when Redis is configured but unreachable (TryGetRedisClient ping failure). Consider rewording to something accurate for both cases (e.g., "Redis unavailable"), or moving the log to the selection point so the reason (not configured vs connection failed) can be reflected correctly.

Suggested change
klog.Info("Rate limiting is disabled: no Redis backend configured")
klog.Info("Rate limiting is disabled: Redis backend unavailable or not configured")

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +100
// Use NoOpRateLimiter when Redis is not configured, effectively disabling rate limiting
var r ratelimiter.RateLimiter
if redisClient != nil {
r = ratelimiter.NewRedisAccountRateLimiter("aibrix", redisClient, 1*time.Minute)
} else {
r = ratelimiter.NewNoOpRateLimiter()
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior introduces an important runtime branch (Redis-backed limiter vs NoOp). There are already unit tests for other Server behaviors in this package, but no test that NewServer selects a NoOpRateLimiter when redisClient is nil. Adding a small unit test would help prevent regressions (e.g., nil Redis leading to panics or accidental enforcement).

Copilot uses AI. Check for mistakes.
@varungup90
Copy link
Copy Markdown
Collaborator

If redis backend is not configured then rate limiting is not applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ratelimiter is enforced even when Redis backend is not configured

3 participants