3.6 KiB
3.6 KiB
R2.18.2 任务报告
结论
Sub2API 评分查询会给 PK01 带来显著的周期性 CPU 峰值,尤其是 ApiState 每 10 分钟执行的 8 小时账号评分。它不足以解释持续高 CPU,但与生产流量或人工刷新重叠时,足以把 2 vCPU 的 PK01 推近 90% 以上。应把评分、去重、TTFT 分位数、成本和 failover 关联迁到 NC01;PK01 只保留原生分页查询和最小必要数据库聚合。
实测证据
- PK01 为 2 vCPU、约 4 GiB 内存;调查结束时 load average 约 0.84/0.60/0.46,Sub2API 基线约 2% 至 4%,Redis约 0.2% 至 0.4%。
- 原生
ops diagnosis --time-range 1h约 2.4 秒,属于较轻的 dashboard 聚合。 runtime errors --all-groups --since 1h约 17.1 秒;远端 job 约 16.0 秒。查询期间 Sub2API 峰值约 13%,远端 Python 约 9%,PostgreSQL 活动连接通常 1 至 2 个。- 单组
runtime errors --group unidesk-codex-pool --since 8h约 31 秒。采样观察到:- PostgreSQL 单 backend 瞬时约 70.2% CPU;
- Sub2API 容器约 12.5% 至 22.2% CPU;
- PK01 远端 Python 聚合约 8% 至 11.3% CPU;
- Redis保持约 0.2% 至 0.4%,不是主因。
- ApiState 最近一次生产评分从 2026-07-17T09:06:02.920Z 到 09:06:58.786Z,耗时约 55.9 秒。
- ApiState 配置为 8 小时窗口、10 分钟刷新;实现先调用一次
--all-groups,再以并发 2 对全部组执行详细评分。当前有 3 个组,两个 OpenAI 组各有 13 个账号。
根因
- UniDesk
runtime errors通过runRemoteCodexPoolScript把完整 Python 脚本放到 PK01 执行。 - 详细评分在 PK01 内完成以下工作:
- 每组读取 dashboard overview、账号可用性和并发;
- 分页读取 request-errors;
- 对 7 类 system-log marker 分别分页搜索;
- 对组内每个账号逐页读取
/api/v1/admin/usage?page_size=100; - 必要时读取容器日志回退;
- 在 PK01 Python 中完成事件关联、TTFT 分位数、评分和成本聚合。
- 8 小时窗口当前约 1 万次请求,仅 usage 的理论分页下限就超过 100 页;逐账号分页会进一步增加请求次数。
- ApiState 的并发 2 会让两个 OpenAI 组的重查询同时落到 PK01。共享账号最终虽在 NC01 合并为一行,但组内 usage、错误和日志查询仍分别执行。
- Web 人工刷新当前由 API 进程直接执行,Temporal 周期刷新由 worker 执行;两者没有跨进程 single-flight,存在重叠风险。
优化方案
- 保留 Sub2API 原生 admin/ops API 和数据库为事实源,不改 Sub2API 源码或版本。
- 将受控 CLI 拆为“PK01 薄采集 + NC01 本地聚合”:
- PK01 只执行登录、原生分页读取和最小字段裁剪;
- 原始分页结果返回 NC01;
- 账号去重、错误归因、failover join、TTFT 分位数、评分、成本和表格渲染全部在 NC01 执行。
- usage 改为每组/窗口一次分页,不再按账号逐个分页;在 NC01 按
account_id分桶。 - 在 NC01 保存 8 小时滚动缓存和原生记录高水位,后续刷新只拉增量;窗口过期数据本地剔除。
- ApiState 保留 Temporal 为唯一周期刷新 owner;人工刷新也提交到同一 Temporal 路径并合并进行中的刷新,避免 API 与 worker 重叠。
- 迁移完成前的保守缓解是把详细组查询并发从 2 降为 1,并阻止人工刷新与周期刷新重叠。该措施只削峰,不解决远端聚合根因。
边界
- 本轮仅只读调查和源码分析。
- 未修改 Sub2API 源码、版本、runtime、YAML、外部哨兵或生产运行面。
- 未停止任何进程。