fix(ci): auto-approve/merge改为短作业+5分钟定时扫描,防止长轮询占垮runner #721

Merged
xiaoxia merged 1 commits from fix/ci-auto-merge-short-job-mode into develop 2026-07-22 15:06:02 +08:00
Owner

问题

auto-approve(40分钟)/auto-merge(30分钟)长轮询占住runner不放,CI拥堵时12个runner被占满,整个流水线接近停摆。

根因

PR Automation的两个job(auto-approve / auto-merge)采用长轮询模式,每个PR占一个runner几十分钟等待CI完成。批量PR时runner资源被快速耗尽。

方案

1. 缩短长轮询时间(治标,立即缓解)

  • auto-approve: 40分钟 → 2分钟快速检查(减少95%占用时间)
  • auto-merge: 30分钟 → 3分钟快速检查(减少90%占用时间)
  • CI没跑完就退出,不占着runner死等

2. 新增定时扫描兜底(治本)

  • 新增 pr-auto-scan.yml:每5分钟定时扫描所有open PR
  • 新增 scripts/ci/pr_auto_scan.py:批量扫描脚本
  • CI全绿的自动审批/合并,确保不会遗漏
  • 自动识别纯前端/全栈改动,使用不同门禁标准

3. 效果预估

  • 单PR占用runner时间:30分钟 → 3分钟(减少90%)
  • runner吞吐量提升:约10倍
  • 不会再出现长轮询把runner占满的情况
## 问题 auto-approve(40分钟)/auto-merge(30分钟)长轮询占住runner不放,CI拥堵时12个runner被占满,整个流水线接近停摆。 ## 根因 PR Automation的两个job(auto-approve / auto-merge)采用长轮询模式,每个PR占一个runner几十分钟等待CI完成。批量PR时runner资源被快速耗尽。 ## 方案 ### 1. 缩短长轮询时间(治标,立即缓解) - auto-approve: 40分钟 → 2分钟快速检查(减少95%占用时间) - auto-merge: 30分钟 → 3分钟快速检查(减少90%占用时间) - CI没跑完就退出,不占着runner死等 ### 2. 新增定时扫描兜底(治本) - 新增 `pr-auto-scan.yml`:每5分钟定时扫描所有open PR - 新增 `scripts/ci/pr_auto_scan.py`:批量扫描脚本 - CI全绿的自动审批/合并,确保不会遗漏 - 自动识别纯前端/全栈改动,使用不同门禁标准 ### 3. 效果预估 - 单PR占用runner时间:30分钟 → 3分钟(减少90%) - runner吞吐量提升:约10倍 - 不会再出现长轮询把runner占满的情况
xiaoxia added 1 commit 2026-07-22 14:57:03 +08:00
fix(ci): auto-approve/merge改为短作业+5分钟定时扫描,防止长轮询占垮runner
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 52s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 19s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 56s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 57s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 44s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 27s
AI Code Review / AI Code Review (pull_request) Successful in 9m8s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m16s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m5s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
e1eebdcf66
xiaoxia merged commit 637c5a8b6f into develop 2026-07-22 15:06:02 +08:00

🚀 预览环境已部署

项目 详情
PR号 #721
预览链接 https://pr-721.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #721 | | 预览链接 | [https://pr-721.preview.xiaoxiajianji.com](https://pr-721.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。

🗑️ 预览环境已清理

PR #721 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #721 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Collaborator

代码审查结果 - PR #721

⚠️ 问题(5个需要修改)

  1. scripts/ci/pr_auto_scan.py 第40行e.read() 流读取逻辑错误

    • 说明:在 except 块中,e.read()if 条件中被调用一次,然后在 json.loads() 中又被调用一次。HTTPError 的流只能读取一次,第二次调用返回空字符串,导致 json.loads("") 抛出 JSONDecodeError,使得错误处理失效。
    • 后果:当 API 返回错误(如 401/403)时,脚本会因解析错误崩溃,而不是优雅地处理错误信息。
  2. scripts/ci/pr_auto_scan.py 第267行merge_pr 函数中存在严重的性能瓶颈导致超时

    • 说明:函数中硬编码了 time.sleep(30)。Workflow 设置的超时时间为 5 分钟,默认处理 20 个 PR。即使其他操作耗时为 0,仅合并阶段的休眠时间就需要 20 * 30s = 600s(10分钟),必然导致 Job 超时被杀死。
    • 后果:脚本无法在规定时间内完成扫描任务,导致自动化流程失败。
  3. scripts/ci/pr_auto_scan.py 第32行:禁用了 SSL 证书验证

    • 说明ctx.verify_mode = ssl.CERT_NONE 关闭了 SSL 校验。
    • 后果:存在中间人攻击风险,且在生产环境中属于严重安全隐患。即使是内网环境,也应配置正确的 CA 证书。
  4. scripts/ci/pr_auto_scan.py 第38行:未处理空响应体导致解析异常

    • 说明json.loads(resp.read().decode()) 假设响应体一定不为空。如果 API 返回 204 No Content(虽然 Gitea 较少见,但符合 HTTP 规范)或某些错误返回空体,此处会崩溃。
    • 后果:程序因非预期的空响应而异常退出。
  5. scripts/ci/pr_auto_scan.py 第40行:异常捕获范围过窄

    • 说明:仅捕获了 urllib.error.HTTPError。网络抖动、DNS 解析失败、连接超时等场景会抛出 urllib.error.URLErrorsocket.timeout,这些异常未被捕获。
    • 后果:在网络不稳定时,脚本会直接崩溃而不是重试或报错退出。

💡 建议(3个可选)

  1. scripts/ci/pr_auto_scan.py 第216行:简化 approve_pr 逻辑

    • 说明:当前逻辑先创建 PENDING 状态再提交 APPROVED。Gitea API 通常支持直接在 POST 请求中发送 {"event": "APPROVED"} 一步完成审批。两步操作增加了 API 调用次数和失败概率。建议确认 API 文档后简化为一步调用。
  2. scripts/ci/pr_auto_scan.py 第189行:优化 get_pr_files 性能

    • 说明:为了判断是否为纯前端改动,脚本获取了 PR 的所有文件列表。对于包含大量文件(如依赖更新、Lock 文件变动)的 PR,这会非常耗时且消耗配额。
    • 建议:考虑限制获取的文件数量(如前 100 个),或者通过 Commit Message 的标签、分支名约定等更轻量的方式来判断是否需要全量 CI 检查。
  3. .gitea/workflows/pr-auto-scan.yml 第21行:Checkout 逻辑过于脆弱

    • 说明:使用 curl 下载脚本并通过 python3 --help 判断是否成功,失败时才回退到 checkout。这种“猜测式”逻辑在 curl 返回非 200 但有部分内容时可能产生误判。
    • 建议:直接使用标准的 actions/checkout,或者明确检查 curl 的 HTTP 状态码(--fail$?)。

格式检查通过 | 逻辑审查需修改 | ⚠️ 建议关注性能


🤖 由 AI 代码审查机器人自动生成 | 2026-07-22 15:34:38 | 模型:

## 代码审查结果 - PR #721 ### ⚠️ 问题(5个需要修改) 1. **scripts/ci/pr_auto_scan.py 第40行**:`e.read()` 流读取逻辑错误 - **说明**:在 `except` 块中,`e.read()` 在 `if` 条件中被调用一次,然后在 `json.loads()` 中又被调用一次。HTTPError 的流只能读取一次,第二次调用返回空字符串,导致 `json.loads("")` 抛出 `JSONDecodeError`,使得错误处理失效。 - **后果**:当 API 返回错误(如 401/403)时,脚本会因解析错误崩溃,而不是优雅地处理错误信息。 2. **scripts/ci/pr_auto_scan.py 第267行**:`merge_pr` 函数中存在严重的性能瓶颈导致超时 - **说明**:函数中硬编码了 `time.sleep(30)`。Workflow 设置的超时时间为 5 分钟,默认处理 20 个 PR。即使其他操作耗时为 0,仅合并阶段的休眠时间就需要 `20 * 30s = 600s`(10分钟),必然导致 Job 超时被杀死。 - **后果**:脚本无法在规定时间内完成扫描任务,导致自动化流程失败。 3. **scripts/ci/pr_auto_scan.py 第32行**:禁用了 SSL 证书验证 - **说明**:`ctx.verify_mode = ssl.CERT_NONE` 关闭了 SSL 校验。 - **后果**:存在中间人攻击风险,且在生产环境中属于严重安全隐患。即使是内网环境,也应配置正确的 CA 证书。 4. **scripts/ci/pr_auto_scan.py 第38行**:未处理空响应体导致解析异常 - **说明**:`json.loads(resp.read().decode())` 假设响应体一定不为空。如果 API 返回 204 No Content(虽然 Gitea 较少见,但符合 HTTP 规范)或某些错误返回空体,此处会崩溃。 - **后果**:程序因非预期的空响应而异常退出。 5. **scripts/ci/pr_auto_scan.py 第40行**:异常捕获范围过窄 - **说明**:仅捕获了 `urllib.error.HTTPError`。网络抖动、DNS 解析失败、连接超时等场景会抛出 `urllib.error.URLError` 或 `socket.timeout`,这些异常未被捕获。 - **后果**:在网络不稳定时,脚本会直接崩溃而不是重试或报错退出。 ### 💡 建议(3个可选) 1. **scripts/ci/pr_auto_scan.py 第216行**:简化 `approve_pr` 逻辑 - **说明**:当前逻辑先创建 PENDING 状态再提交 APPROVED。Gitea API 通常支持直接在 POST 请求中发送 `{"event": "APPROVED"}` 一步完成审批。两步操作增加了 API 调用次数和失败概率。建议确认 API 文档后简化为一步调用。 2. **scripts/ci/pr_auto_scan.py 第189行**:优化 `get_pr_files` 性能 - **说明**:为了判断是否为纯前端改动,脚本获取了 PR 的所有文件列表。对于包含大量文件(如依赖更新、Lock 文件变动)的 PR,这会非常耗时且消耗配额。 - **建议**:考虑限制获取的文件数量(如前 100 个),或者通过 Commit Message 的标签、分支名约定等更轻量的方式来判断是否需要全量 CI 检查。 3. **.gitea/workflows/pr-auto-scan.yml 第21行**:Checkout 逻辑过于脆弱 - **说明**:使用 `curl` 下载脚本并通过 `python3 --help` 判断是否成功,失败时才回退到 checkout。这种“猜测式”逻辑在 `curl` 返回非 200 但有部分内容时可能产生误判。 - **建议**:直接使用标准的 `actions/checkout`,或者明确检查 `curl` 的 HTTP 状态码(`--fail` 或 `$?`)。 --- ✅ 格式检查通过 | ❌ 逻辑审查需修改 | ⚠️ 建议关注性能 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-07-22 15:34:38 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
Sign in to join this conversation.