perf: autocomplete fires two API calls per keystroke; cache user team lookup #99

Closed
opened 2026-03-20 13:18:29 +00:00 by cal · 0 comments
Owner

Problem

utils/autocomplete.py player_autocomplete (lines 38–41) makes two API calls on every keystroke:

  1. get_user_major_league_team(interaction) — looks up the user's team
  2. player_service.search_players(query, limit=50) — searches for players

Discord fires autocomplete on every character typed. The user's team membership changes extremely rarely but is fetched fresh every keystroke.

Additionally, limit=50 fetches twice as many results as Discord can display (max 25 choices).

Fix

  1. Cache get_user_major_league_team result per interaction.user.id with a short TTL (60 seconds):
_user_team_cache: Dict[int, Tuple[Optional[Team], float]] = {}

async def _get_cached_user_team(interaction) -> Optional[Team]:
    user_id = interaction.user.id
    if user_id in _user_team_cache:
        team, cached_at = _user_team_cache[user_id]
        if time.time() - cached_at < 60:
            return team
    team = await get_user_major_league_team(interaction)
    _user_team_cache[user_id] = (team, time.time())
    return team
  1. Reduce limit=50 to limit=25 to match Discord's display limit.

Impact

MEDIUM — Autocomplete is the most latency-sensitive path in Discord. Slow autocomplete shows a visible spinner to users on every keystroke.

## Problem `utils/autocomplete.py` `player_autocomplete` (lines 38–41) makes **two API calls on every keystroke**: 1. `get_user_major_league_team(interaction)` — looks up the user's team 2. `player_service.search_players(query, limit=50)` — searches for players Discord fires autocomplete on every character typed. The user's team membership changes extremely rarely but is fetched fresh every keystroke. Additionally, `limit=50` fetches twice as many results as Discord can display (max 25 choices). ## Fix 1. Cache `get_user_major_league_team` result per `interaction.user.id` with a short TTL (60 seconds): ```python _user_team_cache: Dict[int, Tuple[Optional[Team], float]] = {} async def _get_cached_user_team(interaction) -> Optional[Team]: user_id = interaction.user.id if user_id in _user_team_cache: team, cached_at = _user_team_cache[user_id] if time.time() - cached_at < 60: return team team = await get_user_major_league_team(interaction) _user_team_cache[user_id] = (team, time.time()) return team ``` 2. Reduce `limit=50` to `limit=25` to match Discord's display limit. ## Impact **MEDIUM** — Autocomplete is the most latency-sensitive path in Discord. Slow autocomplete shows a visible spinner to users on every keystroke.
cal added the
ai-working
label 2026-03-20 13:18:43 +00:00
cal removed the
ai-working
label 2026-03-20 13:21:34 +00:00
Claude added the
ai-working
label 2026-03-20 13:31:13 +00:00
Claude added
ai-pr-opened
and removed
ai-working
labels 2026-03-20 13:34:58 +00:00
cal closed this issue 2026-03-20 15:27:35 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/major-domo-v2#99
No description provided.