Repository navigation
Litellm token verification query optimization - #26202
harish-berri merged 34 commits into
Conversation
…eption which are not great for performance (specifically repeated throws). Logic remains the same and the tests are still passing
… Add attach_redis_cache method to DualCache for lazy Redis integration. Update RedisCache to handle None keys and improve logging. Enhance user_api_key_auth caching logic and introduce tests for CacheCodec functionality.
Greptile SummaryThis PR introduces The core abstractions are sound. A few cache call sites were not fully migrated to the new Confidence Score: 5/5Safe to merge — core serialization logic is correct and the behavioral change is properly gated behind a feature flag. Only P2 style/consistency findings: two cache sites not fully migrated to the model_type pattern (dead-code branch and slightly weaker logging). No correctness bugs found; the CacheCodec design, UserApiKeyCache implementation, enable_redis_auth_cache flag gating, TTL propagation, and Prometheus counter wiring are all sound. litellm/proxy/auth/auth_checks.py (get_team_member_default_budget) and litellm/proxy/proxy_server.py (_update_user_cache/_update_end_user_cache) for the consistency gap; no blocking issues.
|
| Filename | Overview |
|---|---|
| litellm/proxy/common_utils/cache_pydantic_utils.py | New CacheCodec class centralizing serialize/deserialize at the DualCache boundary; clean design with proper ValidationError handling. |
| litellm/proxy/common_utils/user_api_key_cache.py | New UserApiKeyCache wrapping DualCache with typed model_type overloads and codec integration; overload ordering is unusual but valid. |
| litellm/proxy/auth/auth_checks.py | Most cache read/write sites migrated to model_type pattern; get_team_member_default_budget is a missed site with dead-code isinstance branch. |
| litellm/proxy/proxy_server.py | Spend-tracking cache updated to CacheCodec; _update_user_cache/_update_end_user_cache use inconsistent two-step deserialization vs rest of codebase. |
| litellm/caching/dual_cache.py | Adds attach_redis_cache no-op guard method for lazy Redis wiring; clean implementation. |
| litellm/proxy/auth/auth_metrics.py | New AuthMetrics helper correctly uses _inc_labeled_counter + prometheus_label_factory to increment litellm_auth_combined_view_queries_total. |
| tests/test_litellm/proxy/test_redis_auth_cache_flag.py | Flag-behavior tests are correct; _patched_init_cache properly extracts and forwards enable_redis_auth_cache; uses plain DualCache instead of UserApiKeyCache for the patched object but sufficient for flag verification. |
| tests/test_litellm/proxy/common_utils/test_cache_codec.py | Thorough unit tests for CacheCodec; _IncompatibleModel now correctly uses foo: int (missing name) ensuring ValidationError is raised. |
| litellm/proxy/auth/user_api_key_auth.py | Type hints updated to UserApiKeyCache; team-member cache reads/writes now use model_type; team cache write guards against None team_id. |
Reviews (23): Last reviewed commit: "fix unused imports" | Re-trigger Greptile
| if cached_key is None: | ||
| return |
There was a problem hiding this comment.
Early return silently drops spend updates when key absent from cache
Before this PR, if async_get_cache returned None (cache miss), the function fell through to existing_spend = existing_spend_obj.spend. Now, cached_key is None triggers an early return, silently discarding the spend update. In a fresh-start or post-eviction scenario where the key hasn't been populated yet, all spend accrued during that window is lost. The introduction of a second return on deserialization failure (lines 1954–1960) compounds the silent-loss surface area.
There was a problem hiding this comment.
On cached_key is None, the previous code path couldn’t safely continue to existing_spend_obj.spend unless existing_spend_obj was non-None — typically that would have been an AttributeError, not a successful spend update. The early return makes that cache-miss case explicit and matches other helpers in update_cache (e.g. user spend: no cache value → return). This block only updates an existing cached key object; it isn’t the sole source of truth for spend — DB/async spend logging still records usage — so the risk is stale cached spend until the key is rehydrated, not “lost” totals. The deserialize None path is the same: skip unsafe cache writes, with a warning log. Happy to track a follow-up to DB-load-then-update-cache on miss if we want tighter cache freshness.
|
@greptile review again |
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
…e CacheCodec for serialization and deserialization. Simplify cache retrieval by removing unnecessary type checks and streamline cache storage with consistent key formatting.
Low: No security issues foundThis PR centralizes Pydantic model serialization/deserialization for the DualCache (in-memory + Redis) layer via a new The Status: 0 open Posted by Veria AI · 2026-04-23T23:45:09.599Z |
… across various components. Introduce the enable_redis_auth_cache flag to control Redis integration for user_api_key_cache, improving performance in multi-worker deployments. Update documentation and tests to reflect these changes.
Low: No security issues foundThis PR introduces a Status: 0 open Posted by Veria AI · 2026-04-24T01:33:22.474Z |
…s, ensuring proper asynchronous behavior in test_update_team_guardrails_with_org_id. This improves test reliability and aligns with recent caching improvements.
…med the cache stores a pydantic object
… for EventDrivenCacheCoordinator
This commit deletes the AuthMetrics class and its associated methods, which were responsible for tracking combined_view SQL query metrics. The PrometheusLogger integration has been updated to remove references to these metrics, streamlining the codebase. Additionally, minor whitespace adjustments were made in the cache coordinator for consistency.
This commit updates the codebase to replace instances of DualCache with UserApiKeyCache in various files, including utils, expired_ui_session_key_cleanup_manager, and team_member_permission_checks. Additionally, it enhances the UserApiKeyCache class with new methods for cache management, improving type safety and consistency across the application.
32ab390
into
litellm_internal_staging
…ion_query_opt Litellm token verification query optimization
Relevant issues
Problem
In Kubernetes or any multi-replica deployment, per-process memory cache is not shared. Requests can hit different pods, so in-memory-only
user_api_key_cachedoes not deduplicate work across nodes and can yield inconsistent views of the same key.Using Redis with DualCache shares cached key metadata across replicas and reduces redundant database lookups—if serialized values are JSON-safe on write and reads rehydrate to the types the proxy expects (e.g.
UserAPIKeyAuth).This can be amplified if each pod has multiple workers and since each worker is a subprocess , they dont share memory and the number of db calls in this path is
num_pods X num_workers_in_each_pod X cache_miss_ratio.Without a single encode/decode rule, Redis often returns a
dictafter JSON round-trip while memory may holdBaseModelinstances. Code that assumes attributes (e.g..spend) then fails withAttributeError, and spend/cache updates become fragile.Approach
CacheCodec(litellm/proxy/common_utils/cache_pydantic_utils.py) centralizes the boundary:serialize— validate +model_dump(mode="json", exclude_none=True)when writing (aligned with Redis /json.dumps).deserialize—model_validateondicthits;ValidationError→ treat as miss (None) + warning log (no bad data served).Scope note:
CacheCodecis for PydanticBaseModel+dictonly; stdlibdataclassesare not supported—convert at the call site (e.g.dataclasses.asdict) or use a Pydantic model.Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Screenshots / Proof of Fix
Type
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
Changes
Code changes (summary)
cache_pydantic_utils.pyCacheCodec.serialize/deserialize, validation-on-read behavior, dataclass disclaimer in docstringsproxy_server.py_update_key_cache:deserializebefore attribute access;serialize(..., UserAPIKeyAuth)for pipeline writesauth_checks.py(and related)CacheCodecwhen reading/writing cached key/team/project objectsNoneRedis keys and noisy errors where payloads are missing (e.g. team id / team object)tests/test_litellm/proxy/common_utils/test_cache_codec.pyTesting
CacheCodecintests/test_litellm/user_api_key_cache, multiple replicas (or workers), verify auth + spend paths, nodictvs model errors in logsget_key_object)Load Test (4 Pods)
Without Redis — combined view only (
SELECT v.* … FROM "LiteLLM_VerificationToken" AS v …)user_api_key_cachein-memory TTL: 60 seconds (UserAPIKeyCacheTTLEnum; nogeneral_settings.user_api_key_cache_ttloverride unless you set one).With Redis — combined view only (same query)
With Redis enabled, the same default 60s TTL is applied to both in-memory and Redis when
user_api_key_cache_ttlis not set (proxy_servermirrors TTL on both layers).Locust Results