fix(proxy): sort spend updates to prevent DB deadlocks - #27221
fix(proxy): sort spend updates to prevent DB deadlocks#27221pnookala-godaddy wants to merge 1 commit into
Conversation
Greptile SummaryThis PR fixes potential PostgreSQL deadlocks in multi-pod LiteLLM deployments by wrapping each spend-bucket iteration with
Confidence Score: 5/5Safe to merge — the change adds only The fix is a small, targeted addition that does not touch any control flow, data transformation, or schema. Each modified path now sorts the same keys the same way on every pod, which is the correct mechanism to prevent row-lock ordering inversions. Tests exercise all 7 spend buckets with proper mocks and verify the iteration order directly. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/proxy/db/db_spend_update_writer.py | Adds sorted() to 5 per-table transaction loops and the shared _update_entity_spend_in_db helper; change is minimal, correctly applied, and does not alter any other logic. |
| litellm/proxy/utils.py | Adds sorted() to the end_user spend loop in ProxyUpdateSpend.update_end_user_spend; one-line change, no side-effects. |
| tests/test_litellm/proxy/db/test_db_spend_update_writer.py | Adds a parametrized async test covering all 7 spend buckets using only mocks; verifies sorted iteration order end-to-end for each path. |
Reviews (1): Last reviewed commit: "fix(proxy): sort spend updates to preven..." | Re-trigger Greptile
VANDRANKI
left a comment
There was a problem hiding this comment.
LGTM. The analysis is correct — without consistent iteration order, two pods can acquire the same row locks in opposite sequence and deadlock. sorted() on the dict items is the canonical Python fix; it adds negligible overhead on typical batch sizes.
Prisma batch_() issuing statements sequentially within the transaction is the key invariant that makes iteration order == lock acquisition order — worth keeping that comment in the code.
Test coverage is excellent: all 7 spend buckets are parametrized and the test directly asserts that where args arrive at the batcher in sorted order. Clean PR.
Iterate user/key/team/team_member/org/end_user/tag spend dicts in sorted order inside each Prisma transaction so concurrent pods acquire row locks in the same order, avoiding PostgreSQL deadlocks under load.
54d342d to
209bd0b
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
🤖 litellm-agent: Merged into staging branch |
PR Review: BerriAI/litellm#27221 — Fix Postgres Deadlocks via Sorted Transaction Key Ordering🟢 Merge Confidence: 5/5 — READY TO SHIPWhat This PR DoesThis PR fixes a Postgres deadlock bug in the LiteLLM proxy's spend tracking system. Under high concurrency (multiple proxy pods writing spend updates simultaneously), different pods could acquire row locks in different orders across the six transaction dictionaries (user, key, team, team-member, org, end-user), creating a circular wait → deadlock condition. The fix is elegantly simple: sort the keys before iterating each transaction dictionary before batching DB writes. This enforces a consistent lock-acquisition order across all pods, eliminating the circular wait. Key Changes
Strengths ✅
Risk Signals
|
Pre-Submission checklist
tests/test_litellm/(parametrized across all 7 spend buckets)Type
🐛 Bug Fix
Changes
Problem
When multiple proxy pods flush batched spend updates concurrently, each pod wraps per-entity updates in a single Prisma transaction. Without consistent iteration order, two pods can lock overlapping rows in different orders and trigger PostgreSQL deadlocks.
Fix
Iterate entity IDs in sorted order inside each transaction so all pods acquire row locks in the same order. Applied to user, key, team, team_member, org, end_user, and tag spend paths. Prisma's
batch_()issues statements sequentially within the transaction, so iteration order equals lock acquisition order.Files changed
litellm/proxy/db/db_spend_update_writer.pylitellm/proxy/utils.pytests/test_litellm/proxy/db/test_db_spend_update_writer.py