mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-16 22:38:39 +08:00
17 KiB
17 KiB
性能与未关闭 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:低风险、可量化收益
- 将原始逐请求指标改为固定容量 ring buffer,或直接改成计数器、直方图和最近慢请求;给所有维度设置基数上限。增加 10 万请求内存回归测试,目标是回落后增量小于 10 MiB。
- 日志只保留一个持久化落点:生产环境选择“Winston 文件 + PM2 stdout 丢弃”或“console + 受控 PM2 rotation”,避免双写。统一由单进程持有系统日志文件 transport。
- 日志 API 增加
tail/offset/limit与最大响应字节数,前端使用增量加载,不再整文件读入内存。 - 给 PM2 日志、
RunningInstances、任务统计和依赖下载缓存增加明确的容量/时间保留策略;清理任务输出被删除的字节数和剩余大小。 - 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。
优化后实测(提交 276dfc23,同机 Node 24 / Docker Desktop arm64)
指标内存
| 压力 | 基线 | 优化后 | 结果 |
|---|---|---|---|
| 50,000 条保留样本 | 50,000 | 1,000 | 减少 98% |
| 50,000 条 GC 后堆增量 | 8.76 MiB | 0.16 MiB | 减少约 98.2% |
| 500,000 条 GC 后堆增量 | 88.33 MiB | 0.11 MiB | 减少约 99.9% |
| 500,000 条写入耗时(5 轮平均) | 149.6 ms | 74.4 ms | 约快 50% |
| 500,000 条统计查询 | RangeError 栈溢出 |
正常 | 消除故障 |
真实容器用并发 50 发出 50,000 次 /api/health:两者空闲均约 161 MiB;基线负载后即时增加约 108.5 MiB、稳定后仍增加约 17.6 MiB,优化版分别约 81.0 MiB 和 8.4 MiB,改善约 25% 和 52%。三轮 10,000 请求耗时基线平均 3.86 秒、优化版 4.05 秒,差约 4.8% 且落在轮次波动内,因此不宣称 HTTP 吞吐提升。
大日志读取
对 100 MiB 日志做三轮读取:
| 指标 | 基线整文件读取 | 优化后 tail 读取 | 结果 |
|---|---|---|---|
| 返回数据 | 100 MiB | 256 KiB | 减少 99.75%(400 倍) |
| 平均耗时 | 56.1 ms | 1.34 ms | 约快 41.9 倍 |
| 堆峰值增量 | 100.11 MiB | 0.317 MiB | 减少约 99.7% |
| RSS 峰值增量 | 115.18 MiB | 0.349 MiB | 减少约 99.7% |
日志写入、SQLite 与镜像
- PM2 实际容器测试中,基线为约 7,288 行输出额外生成了 888,026 bytes 的
qinglong-out.log;优化后 PM2 日志目录没有文件,Docker stdout 每行只出现一次。测试同时发现/proc/1/fd/1会造成 Docker stdout 重复,最终改为容器中out_file/error_file=/dev/null,由pm2-runtime单独转发 stdout。 - 合成 100,000 条运行实例和 100,000 条任务统计的 SQLite 数据库为 24,481,792 bytes。删除 90% 历史记录耗时 0.26 秒,文件大小不会立刻变化;可选
VACUUM耗时 0.05 秒,文件降至 2,465,792 bytes,回收约 89.9%。 - 当前 Debian 镜像
/var/lib/apt/lists实测 19,527,172 bytes(18.62 MiB),新 Dockerfile 会在同一个安装层删除。Dockerfile--check无警告;完整镜像构建在 Debian 包升级网络等待阶段停止,因此不虚报最终镜像总大小。
P1:轻量架构调整
- 主进程只加载 cluster/bootstrap 必需模块,把 Express、监控、HTTP 中间件和业务容器全部延迟到 HTTP worker。目标是先回收主进程约 30–40 MiB private memory。
- Metrics 定时器只在实际使用指标的 worker 中启动,并对 path/tag 做归一化,防止高基数值撑大内存。
- 将任务“开始”和“结束”各自需要的状态、实例、统计写入合并到事务;避免一个生命周期五个以上独立提交。
- 在本地文件系统上基准比较 SQLite
WAL + synchronous=NORMAL + busy_timeout与现状;确认断电语义和网络文件系统兼容后再作为可选配置发布。 - 任务日志保留从全局天数扩展为“全局默认 + 单任务覆盖 + 总容量上限”,而不是只增加更多清理 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 管理动作
- 先关闭明确已实现的 #2219、#3052、#3053、#3059。
- 将 #2404、#2742、#2743 合并到 #3057,并把本文内存实测作为验收基线。
- 将 #2236、#2421 合并到 #3014;#2871 合并 #2902;#1695 合并 #2596;#2642/#2769/#2905 合并。
- 对“需要最新版复现”的旧 bug 统一贴模板,给出 14 天补充窗口;没有版本、架构、日志、最小复现则关闭为 stale/support。
- 轻量修复和架构 epic 使用不同 label/milestone,避免大设计长期占据回归队列。