Repository navigation
[Bug] 0.2.0-rc.2:dsh web 的 JS 主线程常驻 1 核、进程 2.35 核 —— 会话冷读 + 投影缓存 + describe 无缓存(含线程级取证) #9517
Replies: 2 comments
|
你引的三处结构性观察里 (b)(c)(d) 都扎实,但 (a) 有一条决定性的事实错误 —— 那个缓存容量是可配置的,而且在你自己的 0.2.0-rc.2 上就可配。先把这条改掉,否则整篇报告的第一个诉求会被一句"它是配置项"挡回。 1.
|
|
都用nodejs技术栈了 , 性能问题就别管了吧, 反正nodejs的性能死活也好不到哪里去 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
EN TL;DR — On
@deepseek-ai/dsh0.2.0-rc.2 (dsh web, 36 vCPU / 16 GB, Node v22.23.3), onenodeprocess pinned 0.99 core of JS main thread (1.61–2.35 cores total), heap peak 13.3 GB, twice killed bySIGTERMtimeout. Traced to main-thread hot paths: (1) the mobile App's/m/api/sessionsfolding session titles by cold-reading the whole 389 MB corpus (zstd decode + per-lineJSON.parse, 8.9k–37k/s), amplified by its 7–9 s polling and by the cold-observation cachebeing hardcoded to 5defaulting to 5 but configurable (preparedSessionCacheSize); (2) a plugin's projection cache wiped every pass (cap 256 < 269 files); (3)settings.describe()with no result cache. Local patches +preparedSessionCacheSize=384cut it to 0.12–0.24 cores (~12×).1. 环境
@deepseek-ai/dsh0.2.0-rc.2;Node v22.23.3;Ubuntu 24.04.5 LTSdsh web(profileweb,16 插件,--max-old-space-size=8192)~/.dsh/sessions/269 个 zstd 日志 / 389.1 MB;投影缓存 269 个 JSONdsh-mobile-remote仓库,经/m/api/*接入dsh-mobile-remotev3.1.5 /dsh-api-dashboardv1.6.9 /@linxin666/dsh-usagev0.4.4master = d7432673(v0.2.1-alpha.2),下述 (a)–(d) 均存在preparedSessionCacheSize由默认 5 调到 384(≥ 269 个会话)2. 症状
node进程长期占满 1 个核的 JS 主线程(0.99 核),进程合计 1.61–2.35 核;36 核机器上 load 仅 ~1.7。stop-sigterm timed out → SIGKILL。3. 取证方法(全部只读)
线程级
/proc/<pid>/task/*/stat双采样差分 ·SIGUSR1+ CDPProfiler采 V8 profile · 全局JSON.parse打点(次数/字节/调用者)· CDP 永假条件断点计数 ·process._getActiveHandles()挂临时request监听。4. 热点 1:会话日志冷读全量解压 + 逐行
JSON.parse跑在主线程实测
JSON.parse峰值 37,000 次/秒,链路(两次独立采样一致):JSON.parse ← SessionLogScanner.consumeEventLine ← readZstdPrefix ← decodeStoredLog ← readColdSessionLog ← SessionCorpus.projectMany ← dsh-mobile-remote 的 GET /m/api/sessions。触发方是该插件自带的 App:7–9 秒轮询一次,而一轮折叠要几十秒 ⇒ 请求自我叠加。
冷读缓存容量硬编码 5、不可配置;实例化处只传并发度title、标题只能折叠日志 —— 属实。scheduler.yield()让出,worker 基建只用于校验 —— 属实。5. 热点 2:投影缓存每轮被整体清空(第三方插件,现场观测)
实测
loadCacheEntry21.45 MB/秒(102.6 文件/秒)。直接原因是dsh-api-dashboardv1.6.9 的TTL 3s+if (size > 256) clear(),而本机有 269 个投影缓存文件 ⇒ 命中率恒为 0。同步view()契约迫使插件同步读缓存文件6. 热点 3:
settings.describe()无结果缓存实测
Function.toJSON885 次/秒(网页轮询/api/dsh-usage/overview→readNamespace→describe)。内核settings/src/index.ts:302无ns过滤、:304每次 flatMap 全部 entry、:313-315只记 revision 不缓存 descriptor、:391内核自己也全量describe().find()。7. 本地修复与实测(第三方插件部分为现场观测)
dsh-mobile-remotecachedSnapshot()零 I/O 读面dsh-api-dashboardv1.6.921.45 → 0.01 MB/s@linxin666/dsh-usagev0.4.4readNamespace加 60 秒 TTLsession-query-sqlite:preparedSessionCacheSize: 3848. 建议(按 reviewer 的优先级重排)
立即可用(部署侧):①
preparedSessionCacheSize配到 ≥ 会话数;② 让 App 停止 7–9 秒轮询/m/api/sessions(或仅列表可见时轮询)。内核侧:①
给 header 加title②
describe({ ns })+ 用raw当 descriptor 缓存键(含:391内部查询);③ 冷读解码搬进 worker(注意 §4(c) 的退出语义)。9. 复现思路
① 造 > 256 个会话(每个几十 MB)使投影缓存文件数超上限;② 打开 App(秒级轮询)或让网页端持续触发投影重算;③
/proc/<pid>/task/*/stat双采样差分可见主线程接近 1 核、GC 线程合计 > 1 核,配合JSON.parse打点可见 MB/s 级解析量与调用者。致谢
修订时间:2026-10-13
感谢 @PerryLink 逐条核对并给出源码位置。其中 (a) 是我记错的事实 —— 若不更正,整篇报告的第一条诉求会被一句"它是配置项"挡回;他顺手给的两处更省改法我也已采纳。
按他的意见做的改动
preparedSessionCacheSize由默认 5 配到 384(≥ 本机 269 个会话)—— 是公开配置项,不是硬编码;cachedSnapshot()→ 实测 264/269 次零 I/O;cachedSnapshot()的listSessionTitles()",不再提"给 header 加字段";All reactions