# 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，存在重叠风险。

## 优化方案

1. 保留 Sub2API 原生 admin/ops API 和数据库为事实源，不改 Sub2API 源码或版本。
2. 将受控 CLI 拆为“PK01 薄采集 + NC01 本地聚合”：
   - PK01 只执行登录、原生分页读取和最小字段裁剪；
   - 原始分页结果返回 NC01；
   - 账号去重、错误归因、failover join、TTFT 分位数、评分、成本和表格渲染全部在 NC01 执行。
3. usage 改为每组/窗口一次分页，不再按账号逐个分页；在 NC01 按 `account_id` 分桶。
4. 在 NC01 保存 8 小时滚动缓存和原生记录高水位，后续刷新只拉增量；窗口过期数据本地剔除。
5. ApiState 保留 Temporal 为唯一周期刷新 owner；人工刷新也提交到同一 Temporal 路径并合并进行中的刷新，避免 API 与 worker 重叠。
6. 迁移完成前的保守缓解是把详细组查询并发从 2 降为 1，并阻止人工刷新与周期刷新重叠。该措施只削峰，不解决远端聚合根因。

## 边界

- 本轮仅只读调查和源码分析。
- 未修改 Sub2API 源码、版本、runtime、YAML、外部哨兵或生产运行面。
- 未停止任何进程。
