diff --git a/.jules/bolt.md b/.jules/bolt.md index 420e6d7e2..3fd52318c 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -47,3 +47,6 @@ ## 2026-08-09 - [대용량 로그 스캔 시 정규표현식 실행 전 O(N) 서브스트링 검증 선행] **Learning:** `classify_testthat_failure`에서 테스트 실패 내역이 없는 2MB 로그 파일을 대상으로 정규표현식을 실행하면 약 20ms가 소요되지만, 단순 문자열 검색은 약 1ms만 소요됩니다. 문자열 존재 여부가 정규표현식 매칭의 전제 조건일 때, 콜드 패스(Cold Path)에서 순서 최적화는 매우 큰 성능 차이를 만듭니다. **Action:** 대용량 텍스트 입력(CI 로그 등)에서 복잡한 정규표현식을 파싱하기 전에 항상 빠른 O(N) 문자열 존재 여부 확인을 먼저 수행하십시오. +## 2026-08-23 - Concurrent Github API calls in CI Scripts +**Learning:** Performing multiple independent `gh_api_json` REST API calls (like fetching reviews, check runs, and files for a pull request) sequentially inside loop iterations (e.g., `rest_pr_node` inside `fetch_open_prs`) causes severe I/O wait latency that stacks linearly. +**Action:** Always fetch independent network payloads concurrently using `concurrent.futures.ThreadPoolExecutor` when performing API queries, avoiding sequential network operations in synchronous contexts. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0ef8d44..3413d724d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -156,3 +156,7 @@ Semantic Versioning where the repository publishes a release. - Added an organization-owned reusable exact-artifact SBOM attestation boundary that validates inert six-file wheel/sdist evidence, binds CycloneDX 1.7 predicates to exact SHA-256 subjects, signs through least-privilege GitHub artifact attestations, and exports online and offline verification bundles. - Hardened exact-artifact SBOM verification with strict finite RFC 8259 JSON, integer CycloneDX document versions, deterministic UUIDv5 subject identities, exact filename properties and single SHA-256 root bindings, environment-only shell input transfer, pinned Ubuntu 24.04 quality runners, and checksum-sealed beginner-readable offline evidence. The decision record now cites Bray (2017) so NaN and Infinity cannot be treated as sealed SBOM numbers. - Recorded the org control-plane architecture, including exact-artifact SBOM attestation, so agents reconstruct the signing trust boundary from the repo instead of private memory. + +## 2026-08-23 +### 변경 사항 +- **성능 개선**: `scripts/ci/pr_review_merge_scheduler.py`의 `rest_pr_node` 함수에서 `reviews`, `checks`, `files`를 순차적으로 조회하던 것을 `concurrent.futures.ThreadPoolExecutor`를 사용하여 병렬로 가져오도록 수정했습니다. 이로 인해 I/O 대기 시간이 크게 단축됩니다. diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index 44620fcab..3c4295343 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -797,9 +797,18 @@ def rest_pr_node(repo: str, pr: dict[str, Any]) -> dict[str, Any]: head = pr.get("head") or {} base = pr.get("base") or {} head_repo = head.get("repo") or {} - reviews = gh_api_json(f"repos/{repo}/pulls/{number}/reviews?per_page=100") - checks = gh_api_json(f"repos/{repo}/commits/{head.get('sha')}/check-runs?per_page=100") - files = gh_api_json(f"repos/{repo}/pulls/{number}/files?per_page=20") + + # ⚡ Bolt: Fetch independent PR details concurrently to prevent N+1 API bottlenecks. + # This reduces total I/O wait latency from ~3 network hops to 1 hop. + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + future_reviews = executor.submit(gh_api_json, f"repos/{repo}/pulls/{number}/reviews?per_page=100") + future_checks = executor.submit(gh_api_json, f"repos/{repo}/commits/{head.get('sha')}/check-runs?per_page=100") + future_files = executor.submit(gh_api_json, f"repos/{repo}/pulls/{number}/files?per_page=20") + + reviews = future_reviews.result() + checks = future_checks.result() + files = future_files.result() + rest_merge_state = REST_MERGEABLE_STATE_MAP.get( str(pr.get("mergeable_state") or "").lower(), str(pr.get("mergeable_state") or "").upper(), diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 0e71bdbe2..d2607752f 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -590,11 +590,11 @@ def fake_api(path): }, ) - assert calls == [ + assert sorted(calls) == sorted([ "repos/owner/repo/pulls/42/reviews?per_page=100", "repos/owner/repo/commits/abc123/check-runs?per_page=100", "repos/owner/repo/pulls/42/files?per_page=20", - ] + ]) assert node["number"] == 42 assert node["mergeStateStatus"] == "CLEAN" assert node["restMergeableState"] == "CLEAN"