Files
qinglong/docs/PERFORMANCE_AND_OPEN_ISSUE_TRIAGE_2026-08-16.md
T

201 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 性能与未关闭 Issue 梳理(2026-08-16
## 范围与结论
- 基线:`origin/develop` @ `31f78e4d`,运行镜像 `whyour/qinglong:debian`
- GitHub 当前有 86 个未关闭 issue;逐项读取了正文,并读取了其中 48 个有评论 issue 的全部评论。
- 待机内存问题有效且可量化。主要问题不是 Node.js 语言本身,而是三个 Node 隔离进程、PM2,以及没有上限的逐请求指标保留共同叠加。
- 稳定待机没有持续块设备写入;磁盘问题主要是活跃期的重复日志、每次任务生命周期的多次 SQLite 事务、无界历史表和日志读取/保留策略。
- 建议先做可回归验证的小步优化,不建议以“改写为 Go”作为当前性能问题的前置条件。
## 实测基线
### 内存
| 项目 | 结果 |
| --- | ---: |
| `docker stats` 稳定待机 | 约 193 MiB |
| cgroup `memory.current` | 约 234244 MB |
| HTTP worker PSS / RSS | 约 75 MiB / 107 MiB |
| gRPC worker PSS / RSS | 约 57 MiB / 89 MiB |
| cluster 主进程 PSS / RSS | 约 46 MiB / 77 MiB |
| PM2 PSS / RSS | 约 24 MiB / 52 MiB |
`/api/health` 发出 50,000 次成功请求后,等待客户端退出及 5 秒回收:
- cgroup 从 `244,035,584` 增至 `348,925,952` 字节,净增约 105 MB
- HTTP worker RSS 从约 107 MB 增至约 208 MB
- 块设备写入量没有变化。
这与 `back/middlewares/monitoring.ts``back/services/metrics.ts` 一致:每个 HTTP 请求同时进入一个 1,000 条队列和一个按一小时保留、没有数量上限的原始指标数组。后者会随每小时请求数线性增长,并不是 GC 可以主动释放的短期对象。
### 磁盘写入与占用
- 稳定后连续 30 秒,cgroup `wbytes` 保持 `1,077,248` 不变;当前没有证据表明待机状态持续刷盘。
- SQLite 使用 `journal_mode=delete``synchronous=2`FULL)、4 KiB page。一次任务执行至少涉及运行实例创建、任务状态更新、运行实例完成、任务恢复空闲、统计更新等多次独立事务,活跃期会产生额外 journal 与同步写放大。
- Winston 会写 `/ql/data/log`,同一日志又经 console 被 PM2 写入 `~/.pm2/logs`。主进程、HTTP worker、gRPC worker 都会创建 logger/file transportPM2 日志没有代码内保留上限,只在 reload 时 `pm2 flush`
- 任务 stderr 同时进入任务日志和系统日志;错误输出较多时会再次放大写入。
- 日志详情接口使用 `readFile(..., 'utf8')` 整文件加载,既造成瞬时内存峰值,也对应 #2256 的超长日志超时。
镜像与主要目录:
| 项目 | 大小 |
| --- | ---: |
| 镜像 | 599,130,109 bytes |
| `/ql` | 215 MiB |
| `/ql/node_modules` | 174 MiB |
| `/ql/static` | 32 MiB |
| `/usr/local/lib/node_modules` | 85 MiB |
| `/usr/local/lib/python3.11` | 40 MiB |
| `/var/lib/apt/lists` | 19 MiB |
| 全新 `/ql/data` | 5.9 MiB |
## 建议修复批次
### P0:低风险、可量化收益
1. 将原始逐请求指标改为固定容量 ring buffer,或直接改成计数器、直方图和最近慢请求;给所有维度设置基数上限。增加 10 万请求内存回归测试,目标是回落后增量小于 10 MiB。
2. 日志只保留一个持久化落点:生产环境选择“Winston 文件 + PM2 stdout 丢弃”或“console + 受控 PM2 rotation”,避免双写。统一由单进程持有系统日志文件 transport。
3. 日志 API 增加 `tail/offset/limit` 与最大响应字节数,前端使用增量加载,不再整文件读入内存。
4. 给 PM2 日志、`RunningInstances`、任务统计和依赖下载缓存增加明确的容量/时间保留策略;清理任务输出被删除的字节数和剩余大小。
5. Dockerfile 删除 apt lists,审计全局 Node 模块与应用依赖的重复内容;构建产物中不要保留仅构建期需要的包。
当前修复分支落实情况:
- P0-1 已完成:逐请求原始指标改为 1,000 条固定容量环形缓冲区,并补充 50,000 次写入回归测试。
- P0-2 已完成:容器内 PM2 输出直接接入 PID 1 标准输出/错误,停止写入 `~/.pm2/logs` 的重复持久化副本。
- P0-3 已完成:日志接口默认 256 KiB、最大 1 MiB,支持 tail/offset 增量读取,并保证 UTF-8 字符不被分块破坏;浏览器视图最大保留 1 MiB。
- P0-4 已完成:系统设置提供运行实例/任务统计保留天数,默认 0(禁用);清理前展示记录数与依赖缓存字节数,清理接口要求明确确认。依赖缓存和 SQLite `VACUUM` 均为手动勾选,不在启动或升级时自动执行。
- P0-5 已完成:Debian 镜像构建后清理 npm cache 和 apt lists。
### P1:轻量架构调整
1. 主进程只加载 cluster/bootstrap 必需模块,把 Express、监控、HTTP 中间件和业务容器全部延迟到 HTTP worker。目标是先回收主进程约 3040 MiB private memory。
2. Metrics 定时器只在实际使用指标的 worker 中启动,并对 path/tag 做归一化,防止高基数值撑大内存。
3. 将任务“开始”和“结束”各自需要的状态、实例、统计写入合并到事务;避免一个生命周期五个以上独立提交。
4. 在本地文件系统上基准比较 SQLite `WAL + synchronous=NORMAL + busy_timeout` 与现状;确认断电语义和网络文件系统兼容后再作为可选配置发布。
5. 任务日志保留从全局天数扩展为“全局默认 + 单任务覆盖 + 总容量上限”,而不是只增加更多清理 cron。
### P2:需要设计评审
- 提供 Lite 模式:调度器与 HTTP 同进程运行,省去 gRPC worker;标准模式继续保持隔离。需要故障域、重启和任务注册一致性测试。
- 若继续推进集群/多节点,先定义控制面、执行器、任务租约、心跳、幂等和日志归属;不要以“共享 SQLite/MySQL”替代分布式调度设计。
## 86 个未关闭 Issue 的逐项建议
状态含义:`立即处理` 是确认有效且影响当前主线;`轻量处理` 是小型代码/文档/回归测试;`架构议题` 需要 RFC 或独立 epic`关闭/合并` 表示已修复、重复、支持问题或需要提交者用最新版本重现。
### 立即处理(10
| Issue | 结论 | 建议 |
| --- | --- | --- |
| #2256 | 有效,整文件读取日志造成超时和内存峰值 | 日志分片、tail、最大响应大小 |
| #2742 | 有效,与 #3057 合并 | 用内存基线和压力回归替代“运行几天观察” |
| #2743 | 有效,与 #2742/#3057 合并 | 优先修复指标保留和进程基线,再复测受限容器 |
| #2871 | 有效,2.21.0 仍有反馈 | 与 #2902 合并,修复源地址生成并加发行版矩阵测试 |
| #2902 | 有效,重复 #2871 | 合并后关闭 |
| #3017 | 有效安全 epic | 拆成凭据迁移、scope、SSRF、错误脱敏、更新完整性等独立任务 |
| #3051 | 当前回归仍需验证 | 对 shell status payload 建端到端测试,重建最新镜像复测 |
| #3055 | 用户报告有效,但依赖库单测 5/6 字段都可注册 | 增加真实 gRPC 注册与触发集成测试,排查发行镜像/存量任务迁移 |
| #3057 | 有效,实测已复现 | 先修无界指标和多进程基线,不需要先改写语言 |
| #3060 | 有效的容器权限/依赖安装回归 | 覆盖匿名卷、bind mount、rootless 三类安装 smoke test |
### 轻量处理或局部设计(36
| Issue | 结论与最小处理 |
| --- | --- |
| #2015 | 当前仅支持整组 AND/OR;若要嵌套条件,定义轻量 filter AST,避免继续堆 UI 特判 |
| #2236 | 与 #2421/#3014 合并为一个 cron 可视化 issue |
| #2254 | 增加有限重试策略;必须有退避、最大次数和不可重试退出码 |
| #2269 | 增加时区、5/6 字段、DST 的调度集成测试;最新版本无法复现则关闭 |
| #2341 | 与 #2688 合并,把 boolean 实例模式升级为 `replace/skip/parallel/queue` 枚举 |
| #2360 | 标签在列表中恢复可见与筛选入口,纯前端小改 |
| #2418 | 配置方法已有评论解法;仅保留消息超长时的截断/分片问题 |
| #2421 | 合并到 #3014 |
| #2635 | 备注字段是小型 schema/UI 变更,可与名称/标签排序统一设计 |
| #2645 | 增加依赖唯一键、恢复数据去重迁移和幂等安装测试 |
| #2687 | 用最新版本和代理 URL 重现;补代理/超时错误提示 |
| #2688 | 合并到 #2341;当前“单实例”会杀旧任务,不等同于“跳过新任务” |
| #2701 | 需要最小仓库配置样本;JSON 控制字符应在输入边界给出字段级错误 |
| #2715 | Server 酱 tags 是局部通知配置扩展 |
| #2756 | 重测 Chrome、base path、缓存与 health 401;补登录跳转循环保护 |
| #2757 | 复用订阅现有 `autoAddCron` 配置,明确新增任务默认启停状态 |
| #2780 | `ql check` 应在系统包失败时停止并回滚/给出恢复指令,不能继续破坏可运行环境 |
| #2782 | 通知免打扰时段是局部配置;明确延迟发送还是静默丢弃 |
| #2797 | 保留;展开 AggregateError 内部原因并为每种通知后端返回可操作错误 |
| #2801 | 私有仓库凭据与 SSH/HTTPS 模式做连通性诊断,信息不足时转支持问题 |
| #2859 | 最新版请求重现;增加 `env.js` 镜像启动 smoke test和缓存头校验 |
| #2863 | 区分官方 Docker 与 npm/Linux 安装支持等级;若继续支持后者则加 CI |
| #2883 | 最新版复测临时编辑保存;失败时保留请求与后端路径错误 |
| #2886 | 支持 requirements 文件需要锁定来源、工作目录和隔离策略,可先支持显式路径 |
| #2896 | Synology 内核缺少随机字节能力,先做环境诊断与明确错误,不宜静默降级安全随机数 |
| #2901 | 当前默认监听 `::` 并回退 `0.0.0.0`;增加 IPv6 Docker/host 网络 smoke test |
| #2925 | 信息不足;收集完整日志与重启方式,最新镜像不可复现则关闭 |
| #2927 | 使用成熟 ANSI 清理实现,覆盖复合 SGR,而不是只匹配 `\x1b[数字m` |
| #2984 | 通知审计有效,但需先确定脱敏、保留期和容量上限,避免制造新磁盘问题 |
| #3013 | 与性能修复合并:全局默认、单任务覆盖、总容量上限 |
| #3014 | 合并 #2236/#2421,前端生成器必须始终展示最终 cron 文本 |
| #3016 | 有效文档任务;从路由/validation 自动生成 OpenAPI,避免手工文档漂移 |
| #3020 | 有效小改;配置编辑器启用 `wordWrap: 'on'` 并保留开关 |
| #3027 | 需要明确旧值 `1` 的历史语义;用显式 `@once/@boot/disabled` 替代魔法数字 |
| #3054 | 有效纯 UI 改进;整行点击打开历史日志,操作按钮阻止冒泡 |
| #3058 | 有效安全需求;应用增加环境变量名称 allowlist,默认最小权限并审计拒绝事件 |
### 架构议题,单独立项(12
| Issue | 处理建议 |
| --- | --- |
| #1656 | 捕获任意脚本网络请求需要代理或运行时注入,成本和隐私风险高;不作为普通日志小改 |
| #1695 | 与 #2596 合并为“集群执行器”RFC |
| #1821 | 多数据库不等于多节点;只有明确 HA/共享控制面设计后再评估 MySQL/PostgreSQL |
| #2434 | PowerShell 运行时会增加镜像体积和维护矩阵,作为可选外部执行器评估 |
| #2464 | Bun 属于额外运行时矩阵,不应为单一性能假设直接加入基础镜像 |
| #2481 | 每任务 Python 虚拟环境需要依赖缓存、生命周期和磁盘配额设计 |
| #2596 | 合并到 #1695 |
| #2642 | 与 #2769/#2905 合并为多租户权限模型 RFC |
| #2656 | .NET 8 作为可选运行时/派生镜像评估,不进入默认镜像 |
| #2736 | 条件触发工作流已接近 n8n 类产品,应独立产品/RFC,不混入 cron 小功能 |
| #2769 | 合并到多租户 RFC |
| #2905 | 合并到多租户 RFCPR #2818 作为设计输入 |
### 建议关闭、合并或转支持(28)
| Issue | 理由 |
| --- | --- |
| #795 | HTTP 明文本质上应通过 HTTPS 解决;前端自定义“加盐”不能阻止重放。补反代 HTTPS 文档后关闭 |
| #796 | 可由数据库/API 导出;需求长期无上下文,关闭或改为 CSV 导出新需求 |
| #854 | 客户端 IP 已由 #3059 对应功能覆盖;应用调用审计并入 #2984 |
| #2042 | 当前 `NotReg` 已显式处理 NULL 与 notLike;最新版回归测试通过后关闭 |
| #2217 | Home Assistant 插件仓库属于独立打包生态,转社区集成 |
| #2219 | 当前已有运行实例历史与逐实例停止按钮,关闭为已实现 |
| #2329 | 评论已给出 Open API/文件方案,转文档 FAQ 后关闭 |
| #2340 | 老旧 IPQ/固件上的 V8 Bus error 属运行时/硬件兼容;用当前多架构镜像复测,否则关闭 |
| #2350 | 环境变量值如何分隔主要由脚本协议定义;补文档后关闭 |
| #2398 | 单一外站 DNS 解析问题且无环境信息,转网络支持 |
| #2404 | 与 #3057 合并,保留一个性能 epic |
| #2525 | 非官方旧容器/npm 安装路径;最新版本不可复现则关闭 |
| #2567 | 2024 年无任何环境信息的容器 entrypoint 错误,要求当前镜像重现,否则关闭 |
| #2705 | 当前已有全局 cron concurrency 与最多五个重复实例限制,补 UI/文档后关闭 |
| #2772 | 与 #2340 合并为特定软路由 CPU/固件兼容问题 |
| #2788 | maintainer 已说明黑名单语义;补订阅文档后关闭 |
| #2792 | PM2 版本提示是镜像更新/支持问题,当前镜像复测后关闭 |
| #2793 | 自动重启会掩盖 OOM;合并到性能问题,不单独实现定时自杀 |
| #2798 | Node 在特定硬件启动即 fatal,合并硬件兼容问题 |
| #2895 | Debian 中加载 musl 二进制通常是第三方依赖选错平台,转依赖支持 |
| #2903 | 当前运行时代码未使用 chokidar 监听 node_modules;最新镜像复测后关闭 |
| #2914 | maintainer 指定 2.20.1 已修复,当前版本复测后关闭 |
| #2948 | 评论已有正确升级链和 `/ql/data` 挂载方案;补升级文档后关闭 |
| #2964 | 本文已提供资源基线,并入 #3057 后关闭 |
| #2967 | maintainer 已解释黑白名单语义;补文档后关闭 |
| #3052 | `2ac4f07f` 已修复 dashboard open scope,关闭为已修复 |
| #3053 | `2ac4f07f` 已加入 INI 预览/强制打开处理,关闭为已修复 |
| #3059 | `31f78e4d` 已加入 IP 黑名单、客户端 IP 诊断和 trust proxy 配置,关闭为已修复 |
## 建议的 Issue 管理动作
1. 先关闭明确已实现的 #2219#3052#3053#3059
2.#2404#2742#2743 合并到 #3057,并把本文内存实测作为验收基线。
3.#2236#2421 合并到 #3014#2871 合并 #2902#1695 合并 #2596#2642/#2769/#2905 合并。
4. 对“需要最新版复现”的旧 bug 统一贴模板,给出 14 天补充窗口;没有版本、架构、日志、最小复现则关闭为 stale/support。
5. 轻量修复和架构 epic 使用不同 label/milestone,避免大设计长期占据回归队列。