Skip to content

fix: batch key queries in list_team to eliminate N+1#23152

Merged
RheagalFire merged 2 commits intoBerriAI:litellm_oss_staging_03_11_2026from
CAFxX:fix/team-list-n-plus-1-query
Mar 11, 2026
Merged

fix: batch key queries in list_team to eliminate N+1#23152
RheagalFire merged 2 commits intoBerriAI:litellm_oss_staging_03_11_2026from
CAFxX:fix/team-list-n-plus-1-query

Conversation

@CAFxX
Copy link
Contributor

@CAFxX CAFxX commented Mar 9, 2026

Summary

  • The list_team endpoint issued one find_many query per team to fetch verification token keys, causing an N+1 query problem
  • With many teams and ~163 keys per team on average, this generates significant database load
  • Replace the per-team loop with a single batched IN query, then distribute keys in-memory

The existing @@index([team_id]) on LiteLLM_VerificationToken already supports this query efficiently.

Test plan

  • Added test_list_team_batches_key_queries verifying:
    • find_many is called exactly once with an IN query (not once per team)
    • Keys are correctly distributed to their respective teams
    • Teams with no keys get an empty list

🤖 Generated with Claude Code

The list_team endpoint issued one DB query per team to fetch keys,
causing N+1 query overhead. Replace with a single batched IN query.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 11, 2026 6:20pm

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR fixes an N+1 database query problem in the /team/list endpoint by replacing a per-team find_many loop with a single batched IN query, then distributing the results in-memory.

Changes:

  • team_endpoints.py: Replaces the per-team loop that issued one DB query per team with a single batch find_many call (WHERE team_id IN (...)), then builds a _keys_by_team dict for O(1) lookup during the response loop.
  • test_team_endpoints.py: Adds test_list_team_batches_key_queries using AsyncMock/MagicMock to assert that find_many is called exactly once with the IN query (not once per team) and that keys are correctly distributed to their respective teams, including teams with no keys.

Correctness: The logic is straightforward and correct. The existing @@index([team_id]) on LiteLLM_VerificationToken efficiently supports the new batched query. The test is well-structured and verifies the main invariant (single batched call + correct key distribution).

Scope: This PR successfully eliminates the N+1 query problem for the primary use case. No backwards-incompatible changes.

Confidence Score: 5/5

  • This PR is safe to merge; it correctly eliminates N+1 queries with a well-tested batched approach and no backwards-incompatible changes.
  • The logic is straightforward and correct: the batch query uses the existing @@index([team_id]) for efficient lookup, a list comprehension builds the batch, setdefault distributes keys, and the original team loop is otherwise untouched. The test covers the main invariant (single call + correct distribution). No files require special attention; both changed files are clean and correct.
  • No files require special attention

Sequence Diagram

sequenceDiagram
    participant Client
    participant list_team
    participant DB as Database (Prisma)

    Client->>list_team: GET /team/list

    Note over list_team,DB: Before (N+1)
    list_team->>DB: get teams (find_many)
    DB-->>list_team: [team1, team2, ..., teamN]
    loop for each team
        list_team->>DB: find_many keys WHERE team_id = ?
        DB-->>list_team: keys for team
    end

    Note over list_team,DB: After (batched)
    list_team->>DB: get teams (find_many)
    DB-->>list_team: [team1, team2, ..., teamN]
    list_team->>DB: find_many keys WHERE team_id IN (id1, id2, ..., idN)
    DB-->>list_team: all keys (single query)
    list_team->>list_team: distribute keys in-memory by team_id

    list_team-->>Client: TeamListResponseObject[]
Loading

Last reviewed commit: 37a46e6

@krrishdholakia krrishdholakia changed the base branch from main to litellm_oss_staging_03_09_2026 March 9, 2026 15:59
@krrishdholakia krrishdholakia changed the base branch from litellm_oss_staging_03_09_2026 to main March 9, 2026 15:59
- Rename test to include "v1" for consistency with other test names
- Remove unnecessary DB call assertion, keep behavioral assertions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@RheagalFire RheagalFire changed the base branch from main to litellm_oss_staging_03_11_2026 March 11, 2026 18:46
@RheagalFire RheagalFire merged commit c7e3b11 into BerriAI:litellm_oss_staging_03_11_2026 Mar 11, 2026
21 of 37 checks passed
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.

2 participants