# 性能与未关闭 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` | 约 234–244 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 transport;PM2 日志没有代码内保留上限,只在 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。目标是先回收主进程约 30–40 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,避免大设计长期占据回归队列。