From c3406765a2bd928a761118f09f7a7f475b673975 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:08:00 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20rest=5Fpr=5Fnode=20API=20=EB=B3=91?= =?UTF-8?q?=EB=A0=AC=20=ED=98=B8=EC=B6=9C=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/ci/pr_review_merge_scheduler.py의 rest_pr_node 함수에서 reviews, checks, files 정보를 조회하기 위해 순차적으로 호출하던 gh_api_json API 요청을 concurrent.futures.ThreadPoolExecutor를 활용해 병렬(최대 3개 워커)로 실행하도록 개선했습니다. 이를 통해 순차적 네트워크 I/O 대기로 인한 N+1 병목 현상을 방지하고, 전체 응답 속도를 3회 네트워크 홉(hop)에서 1회로 최적화했습니다. --- .jules/bolt.md | 3 +++ CHANGELOG.md | 4 ++++ scripts/ci/pr_review_merge_scheduler.py | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 420e6d7e20..3fd52318c1 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 6b0ef8d447..3413d724df 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 44620fcab5..3c4295343b 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(), From 922dcd04000102df2f21cd3209f7bb1aa9e10f25 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:17:10 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20rest=5Fpr=5Fnode=20API=20=EB=B3=91?= =?UTF-8?q?=EB=A0=AC=20=ED=98=B8=EC=B6=9C=20=EC=B5=9C=EC=A0=81=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=94=BD=EC=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/ci/pr_review_merge_scheduler.py의 rest_pr_node 함수에서 reviews, checks, files 정보를 조회하기 위해 순차적으로 호출하던 gh_api_json API 요청을 concurrent.futures.ThreadPoolExecutor를 활용해 병렬(최대 3개 워커)로 실행하도록 개선했습니다. 이를 통해 순차적 네트워크 I/O 대기로 인한 N+1 병목 현상을 방지하고, 전체 응답 속도를 3회 네트워크 홉(hop)에서 1회로 최적화했습니다. 또한 API 병렬 처리로 인해 호출 순서가 보장되지 않게 되면서 발생한 tests/test_pr_review_merge_scheduler.py 의 테스트 실패를 수정하여 순서에 무관하게 호출 여부를 검증하도록 `sorted()`를 적용했습니다. --- tests/test_pr_review_merge_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 0e71bdbe24..d2607752fb 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"