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

15 KiB
Raw Blame History

性能与未关闭 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.tsback/services/metrics.ts 一致:每个 HTTP 请求同时进入一个 1,000 条队列和一个按一小时保留、没有数量上限的原始指标数组。后者会随每小时请求数线性增长,并不是 GC 可以主动释放的短期对象。

磁盘写入与占用

  • 稳定后连续 30 秒,cgroup wbytes 保持 1,077,248 不变;当前没有证据表明待机状态持续刷盘。
  • SQLite 使用 journal_mode=deletesynchronous=2FULL)、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 合并到多租户 RFC;PR #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,避免大设计长期占据回归队列。