Merge pull request 'perf: parallelize N+1 player/creator lookups with asyncio.gather (#89)' (#118) from ai/major-domo-v2-89 into main

Reviewed-on: #118
This commit is contained in:
cal 2026-03-31 19:45:01 +00:00
commit deb40476a4
2 changed files with 56 additions and 36 deletions

View File

@ -4,6 +4,7 @@ Custom Commands Service for Discord Bot v2.0
Modern async service layer for managing custom commands with full type safety.
"""
import asyncio
import math
from datetime import UTC, datetime, timedelta
from typing import Optional, List, Any, Tuple
@ -466,21 +467,28 @@ class CustomCommandsService(BaseService[CustomCommand]):
commands_data = await self.get_items_with_params(params)
creators = await asyncio.gather(
*[
self.get_creator_by_id(cmd_data.creator_id)
for cmd_data in commands_data
],
return_exceptions=True,
)
commands = []
for cmd_data in commands_data:
try:
creator = await self.get_creator_by_id(cmd_data.creator_id)
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
except BotException as e:
# Handle missing creator gracefully
for cmd_data, creator in zip(commands_data, creators):
if isinstance(creator, BotException):
self.logger.warning(
"Skipping popular command with missing creator",
command_id=cmd_data.id,
command_name=cmd_data.name,
creator_id=cmd_data.creator_id,
error=str(e),
error=str(creator),
)
continue
if isinstance(creator, BaseException):
raise creator
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
return commands
@ -662,21 +670,28 @@ class CustomCommandsService(BaseService[CustomCommand]):
commands_data = await self.get_items_with_params(params)
creators = await asyncio.gather(
*[
self.get_creator_by_id(cmd_data.creator_id)
for cmd_data in commands_data
],
return_exceptions=True,
)
commands = []
for cmd_data in commands_data:
try:
creator = await self.get_creator_by_id(cmd_data.creator_id)
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
except BotException as e:
# Handle missing creator gracefully
for cmd_data, creator in zip(commands_data, creators):
if isinstance(creator, BotException):
self.logger.warning(
"Skipping command with missing creator",
command_id=cmd_data.id,
command_name=cmd_data.name,
creator_id=cmd_data.creator_id,
error=str(e),
error=str(creator),
)
continue
if isinstance(creator, BaseException):
raise creator
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
return commands
@ -688,21 +703,28 @@ class CustomCommandsService(BaseService[CustomCommand]):
commands_data = await self.get_items_with_params(params)
creators = await asyncio.gather(
*[
self.get_creator_by_id(cmd_data.creator_id)
for cmd_data in commands_data
],
return_exceptions=True,
)
commands = []
for cmd_data in commands_data:
try:
creator = await self.get_creator_by_id(cmd_data.creator_id)
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
except BotException as e:
# Handle missing creator gracefully
for cmd_data, creator in zip(commands_data, creators):
if isinstance(creator, BotException):
self.logger.warning(
"Skipping command with missing creator",
command_id=cmd_data.id,
command_name=cmd_data.name,
creator_id=cmd_data.creator_id,
error=str(e),
error=str(creator),
)
continue
if isinstance(creator, BaseException):
raise creator
commands.append(CustomCommand(**cmd_data.model_dump(), creator=creator))
return commands

View File

@ -4,6 +4,7 @@ Decision Service
Manages pitching decision operations for game submission.
"""
import asyncio
from typing import List, Dict, Any, Optional, Tuple
from utils.logging import get_contextual_logger
@ -124,22 +125,19 @@ class DecisionService:
if int(decision.get("b_save", 0)) == 1:
bsv_ids.append(pitcher_id)
# Second pass: Fetch Player objects
wp = await player_service.get_player(wp_id) if wp_id else None
lp = await player_service.get_player(lp_id) if lp_id else None
sv = await player_service.get_player(sv_id) if sv_id else None
# Second pass: Fetch all Player objects in parallel
# Order: [wp_id, lp_id, sv_id, *hold_ids, *bsv_ids]; None IDs resolve immediately
ordered_ids = [wp_id, lp_id, sv_id] + hold_ids + bsv_ids
results = await asyncio.gather(
*[
player_service.get_player(pid) if pid else asyncio.sleep(0, result=None)
for pid in ordered_ids
]
)
holders = []
for hold_id in hold_ids:
holder = await player_service.get_player(hold_id)
if holder:
holders.append(holder)
blown_saves = []
for bsv_id in bsv_ids:
bsv = await player_service.get_player(bsv_id)
if bsv:
blown_saves.append(bsv)
wp, lp, sv = results[0], results[1], results[2]
holders = [p for p in results[3 : 3 + len(hold_ids)] if p]
blown_saves = [p for p in results[3 + len(hold_ids) :] if p]
return wp, lp, sv, holders, blown_saves