From d44bbedf0e56eb5bac582ddeea20ec1fd862c2f2 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 15 Feb 2026 06:07:17 +0000 Subject: [PATCH] perf: bound concurrency for approval check reconciliation --- backend/.env.example | 2 ++ backend/app/core/config.py | 2 ++ .../github/mission_control_approval_check.py | 33 +++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 4a88998d..bbde4b65 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -25,6 +25,8 @@ GH_TOKEN= # Periodic reconciliation safety net (rq-scheduler) GITHUB_APPROVAL_CHECK_SCHEDULE_ID=mission-control-approval-check-reconcile GITHUB_APPROVAL_CHECK_SCHEDULE_INTERVAL_SECONDS=900 +GITHUB_APPROVAL_CHECK_RECONCILE_CONCURRENCY=3 +GITHUB_APPROVAL_CHECK_RECONCILE_MAX_PR_URLS=500 # Webhook queue / worker WEBHOOK_REDIS_URL=redis://localhost:6379/0 diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 7d5b0fc1..4a8c1a83 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -60,6 +60,8 @@ class Settings(BaseSettings): # Periodic reconciliation safety net for mission-control/approval checks. github_approval_check_schedule_id: str = "mission-control-approval-check-reconcile" github_approval_check_schedule_interval_seconds: int = 900 + github_approval_check_reconcile_concurrency: int = Field(default=3, ge=1, le=10) + github_approval_check_reconcile_max_pr_urls: int = Field(default=500, ge=1) # Database lifecycle db_auto_migrate: bool = False diff --git a/backend/app/services/github/mission_control_approval_check.py b/backend/app/services/github/mission_control_approval_check.py index 755ff7c8..ec744841 100644 --- a/backend/app/services/github/mission_control_approval_check.py +++ b/backend/app/services/github/mission_control_approval_check.py @@ -20,6 +20,8 @@ A periodic reconciliation job should call the sync functions as a safety net. from __future__ import annotations +import asyncio + from dataclasses import dataclass from typing import Literal, cast from uuid import UUID @@ -368,10 +370,35 @@ async def reconcile_github_approval_checks_for_board( if isinstance(value, str) and value.strip(): pr_urls.add(value.strip()) - for pr_url in sorted(pr_urls): - await sync_github_approval_check_for_pr_url(session, board_id=board_id, pr_url=pr_url) + pr_url_list = sorted(pr_urls) + max_urls = settings.github_approval_check_reconcile_max_pr_urls + if len(pr_url_list) > max_urls: + logger.warning( + "github.approval_check.reconcile.truncated_pr_urls", + extra={"board_id": str(board_id), "count": len(pr_url_list), "max": max_urls}, + ) + pr_url_list = pr_url_list[:max_urls] - return len(pr_urls) + sem = asyncio.Semaphore(settings.github_approval_check_reconcile_concurrency) + + async def _run(url: str) -> None: + async with sem: + await sync_github_approval_check_for_pr_url( + session, + board_id=board_id, + pr_url=url, + ) + + # Process concurrently but bounded to avoid overwhelming GitHub. + results = await asyncio.gather(*[_run(url) for url in pr_url_list], return_exceptions=True) + for result in results: + if isinstance(result, Exception): + logger.exception( + "github.approval_check.reconcile.pr_failed", + extra={"board_id": str(board_id), "error": str(result)}, + ) + + return len(pr_url_list) async def reconcile_mission_control_approval_checks_for_all_boards() -> int: