[Fix] Block proxy startup when use_redis_transaction_buffer is enabled without Redis cache

When `use_redis_transaction_buffer: true` is set in general_settings but no
Redis cache is configured in litellm_settings, the proxy starts successfully
but silently drops all spend tracking data. This adds a startup validation
that raises a clear error, preventing the proxy from running in a broken state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang
2026-03-06 21:24:49 -08:00
co-authored by Claude Opus 4.6
parent b314e8d20a
commit 3a15e1cc2e
2 changed files with 91 additions and 0 deletions
+40
View File
@@ -871,6 +871,12 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915
redis_usage_cache=redis_usage_cache,
)
## Validate use_redis_transaction_buffer requires Redis cache ##
ProxyStartupEvent._validate_redis_transaction_buffer_config(
general_settings=general_settings,
redis_usage_cache=redis_usage_cache,
)
## SEMANTIC TOOL FILTER ##
# Read litellm_settings from config for semantic filter initialization
try:
@@ -5618,6 +5624,40 @@ class ProxyStartupEvent:
llm_router=llm_router, redis_usage_cache=redis_usage_cache
)
@staticmethod
def _validate_redis_transaction_buffer_config(
general_settings: dict,
redis_usage_cache: Optional[RedisCache],
):
"""
Validates that when use_redis_transaction_buffer is enabled,
a Redis cache is properly configured in litellm_settings.
Without Redis, spend updates are silently dropped because:
- In-memory queues are drained but never pushed to Redis
- The pod lock manager cannot acquire locks for DB commits
- No fallback to direct DB writes occurs
"""
from litellm.proxy.db.db_transaction_queue.redis_update_buffer import (
RedisUpdateBuffer,
)
if (
RedisUpdateBuffer._should_commit_spend_updates_to_redis()
and redis_usage_cache is None
):
raise ValueError(
"`use_redis_transaction_buffer` is enabled in general_settings, "
"but no Redis cache is configured. Spend tracking will silently "
"fail without Redis. Please add a Redis cache configuration in "
"litellm_settings:\n\n"
"litellm_settings:\n"
" cache: true\n"
" cache_params:\n"
" type: redis\n"
" url: os.environ/REDIS_URL\n"
)
@classmethod
async def _initialize_semantic_tool_filter(
cls,
@@ -10,6 +10,7 @@ sys.path.insert(
) # Adds the parent directory to the system path
from litellm.proxy.db.db_transaction_queue.redis_update_buffer import RedisUpdateBuffer
from litellm.proxy.proxy_server import ProxyStartupEvent
from litellm.types.caching import RedisPipelineRpushOperation
@@ -192,3 +193,53 @@ async def test_get_all_transactions_from_redis_buffer_pipeline_no_redis():
buffer = RedisUpdateBuffer(redis_cache=None)
result = await buffer.get_all_transactions_from_redis_buffer_pipeline()
assert result == (None, None, None, None, None, None, None)
def test_validate_redis_transaction_buffer_raises_without_redis():
"""
When use_redis_transaction_buffer=true but no Redis cache is configured,
the proxy should refuse to start with a clear error message.
"""
general_settings = {"use_redis_transaction_buffer": True}
with patch(
"litellm.proxy.proxy_server.general_settings", general_settings
):
with pytest.raises(ValueError, match="use_redis_transaction_buffer"):
ProxyStartupEvent._validate_redis_transaction_buffer_config(
general_settings=general_settings,
redis_usage_cache=None,
)
def test_validate_redis_transaction_buffer_passes_with_redis():
"""
When use_redis_transaction_buffer=true and Redis cache is configured,
validation should pass without error.
"""
general_settings = {"use_redis_transaction_buffer": True}
mock_redis_cache = MagicMock()
with patch(
"litellm.proxy.proxy_server.general_settings", general_settings
):
# Should not raise
ProxyStartupEvent._validate_redis_transaction_buffer_config(
general_settings=general_settings,
redis_usage_cache=mock_redis_cache,
)
def test_validate_redis_transaction_buffer_passes_when_disabled():
"""
When use_redis_transaction_buffer is not set or false,
validation should pass regardless of Redis configuration.
"""
with patch(
"litellm.proxy.proxy_server.general_settings", {}
):
# Should not raise even without Redis
ProxyStartupEvent._validate_redis_transaction_buffer_config(
general_settings={},
redis_usage_cache=None,
)