DSH 机 /tmp 爆满 →「项目记忆丢失」诊断与处置
- 日期:2026-09-23
- 机器:DSH 机 192.168.31.76(本机)
- 触发:
/tmp 曾爆满,DSH 无法执行任何命令;清空 /tmp 后,感觉「很多项目的记忆丢失」
- 结论(一句话):会话正文(真正的记忆)一条没丢;丢的是
/tmp 里的「大输出溢出缓存」和临时工作文件。根因是 DSH 的溢出目录默认落在 tmpfs 内存盘,需要把 TMPDIR 挪到磁盘并加清理/备份策略。
一、现场实测事实
| 项目 | 实测值 |
/tmp 挂载 | tmpfs(内存盘)7.8G,当前用 515M(7%);重启即清空 |
/(sda3) | 92G,已用 14G,可用 74G |
/home(sda5) | 805G,已用 295G,可用 470G |
| DSH_HOME | /home/zyw/.dsh(距上次统计 547M) |
| 会话日志 | ~/.dsh/sessions/ 共 187 个 .jsonl.zstd,zstd 完整性校验 187/187 全部通过,0 损坏 |
| 会话注册表 | ~/.dsh/storages/workspace.json → 工作区 Downloads,已注册 90 个会话(与磁盘目录一一对应,无缺失) |
| 孤儿会话 | /home/zyw 工作区有 6 个老会话(8/29–9/3 的测试会话)未注册进 workspace.json,故 GUI 侧栏看不到 |
| 运行方式 | pm2 应用 deepseek-harness(id 0,dsh web --no-open,cwd /home/zyw/Downloads),由 systemd pm2-zyw.service 开机自启 |
| 进程环境 | 未设置 TMPDIR → os.tmpdir() 回落到 /tmp |
| 当前 spill 根 | /tmp/dsh-spill-22VzTS(含 3 个大输出快照,仍在) |
损失清点(spill 指针失效范围)
| spill 根 | 状态 | 引用条数 |
dsh-spill-22VzTS | 存在(个别文件已缺) | 5 引用失效 / 3 文件在 |
dsh-spill-dusiyr | 已消失 | 7 |
dsh-spill-ZzPuwD | 已消失 | 7 |
dsh-spill-r74pBE / rSP6MD / pXgUcP / BEFdrr | 已消失 | 各 2–3 |
dsh-spill-2M3vdQ / QBrazY | 已消失 | 各 1 |
- 22 个历史会话至少有一条「大输出全文」指针失效(全文不可恢复,会话里的截断预览仍在)。
- 5 个会话仍指向存活的 spill 文件(能正常查看全文)。
/tmp 当前占用前列:dh 225M、q21 190M、ocr_test 79M、若干 PDF/临时目录 —— 都是会话期间的临时工作文件,重启即消失。
二、机制:DSH 为什么会被 /tmp 拖死、又为什么「看起来失忆」
- 溢出后端写 /tmp
@deepseek-ai/dsh-spill-local 用 os.tmpdir() 调 mkdtempSync(join(tmpdir(), "dsh-spill-")) 建立每个服务进程一个的私有 spill 根;会话文件落在 spill根/session-<sha256(sessionId)前12位>/。
- 超长工具输出只把全文放进 /tmp
@deepseek-ai/dsh-spill-policy(maxInlineBytes 阈值)在单次工具输出超限时:把全文写入 spill 文件,会话日志里只保留截断预览 + 指向该文件的提示/路径。也就是说:会话日志保存的是「记忆」,spill 文件保存的是「全文快照」。
- /tmp 是 tmpfs,且满了就是 ENOSPC tmpfs 由内存承载,重启一定清空;写满后
open()/mkdir() 直接失败 → DSH 的 spill 写入、子进程临时目录、Node 编译缓存等一起报错 → 表现为「DSH 任何命令都跑不动」。
- 清空 /tmp 的副作用 会话日志(在
~/.dsh/sessions,磁盘上)完好,但所有 spill 全文快照连同指向它们的路径一起没了 → 回看旧会话时「查看完整输出」失效;再加上服务重启后每次都会新建一个 spill 根,旧根成为孤儿,看起来就像「很多项目的记忆没了」。
- 放大因素
- 服务启停频繁(pm2 记录
restart_time: 79),每次新建 spill 根,旧根不自动清理;
- 系统
/usr/lib/tmpfiles.d/tmp.conf 里 /tmp 只配置了 D /tmp 1777(开机重建),没有按时间/大小清理,所以会一直堆积到爆。
三、处置方案
A. 立即执行(零风险,不动服务)
# 1) 先把现状整体备份一份(含会话/注册表/配置)
tar czf ~/dsh-backups/dsh-backup-before-tmpfix-$(date +%Y%m%d-%H%M%S).tgz \
-C /home/zyw .dsh/sessions .dsh/storages .dsh/settings.yaml .dsh/AGENTS.md 2>/dev/null
# 2) 清理滞留的孤儿 spill 根(先看再删)
ls -ld /tmp/dsh-spill-* 2>/dev/null
# 只删 3 天前的;存活进程正在用的 22VzTS 保留
find /tmp -maxdepth 1 -name 'dsh-spill-*' -mtime +3 -exec rm -rf {} + 2>/dev/null
# 3) 把 /tmp 里有价值的临时产物搬回磁盘(按需)
# 例: mkdir -p ~/Downloads/.dsh-work && mv /tmp/dh /tmp/q21 /tmp/ocr_test ~/Downloads/.dsh-work/
B. 根治:把 DSH 的临时目录挪到磁盘(需重启 DSH Web 约 10 秒)
影响:重启 deepseek-harness 会中断当前会话所在进程。会话已持久化,不会丢,刷新 GUI 后从侧栏恢复即可。 收益:① 不再把 7.8G 内存盘塞爆(磁盘可用 470G);② spill 全文快照按会话哈希落盘,跨重启存活,旧会话仍能看全文;③ 重启后 TMPDIR 生效。
# 1) 建磁盘临时目录
mkdir -p /home/zyw/.dsh-tmp && chmod 700 /home/zyw/.dsh-tmp
# 2) 写 pm2 ecosystem(显式声明 TMPDIR,便于长期维护)
cat > /home/zyw/dsh-ecosystem.config.cjs <<'EOF'
module.exports = {
apps: [{
name: 'deepseek-harness',
script: '/home/zyw/.config/nvm/versions/node/v22.23.2/bin/dsh',
args: ['web', '--no-open'],
cwd: '/home/zyw/Downloads',
interpreter: '/home/zyw/.config/nvm/versions/node/v22.23.2/bin/node',
env: { TMPDIR: '/home/zyw/.dsh-tmp' },
autorestart: true,
merge_logs: true
}]
};
EOF
# 3) 用新配置重建应用并持久化(保证 pm2-zyw.service 重启后仍生效)
export PATH=/home/zyw/.config/nvm/versions/node/v22.23.2/bin:$PATH
pm2 delete deepseek-harness
pm2 start /home/zyw/dsh-ecosystem.config.cjs
pm2 save
# 4) 验证:进程环境里应有 TMPDIR,且新的 spill 根出现在磁盘目录
pm2 env 0 | grep -i tmpdir
sleep 10; ss -tlnp | grep 3080
C. 预防
# 1) 定时清理 spill(保留 14 天,兼顾“看全文”与磁盘)
( crontab -l 2>/dev/null; echo '30 4 * * * find /home/zyw/.dsh-tmp /tmp -maxdepth 1 -name "dsh-spill-*" -mtime +14 -exec rm -rf {} + 2>/dev/null' ) | crontab -
# 2) /tmp 兜底:按大小/时间清理(可选,写 /etc/tmpfiles.d/dsh-tmp.conf)
# q /tmp/dsh-spill-* - - - 3d
- 习惯建议:会话中的下载/中间产物一律放
~/Downloads/.dsh-work/ 或 dl-hub/ 子目录,不要落在 /tmp(/tmp 只当易失缓存)。
- 备份建议:定期(每周)把
~/.dsh/{sessions,storages,AGENTS.md,settings.yaml} 打包到 ~/dsh-backups/;现有可用备份:~/dsh-backups/dsh-backup-pre-015rc2-20260919-231342.tgz(441MB,含 9/19 23:13 前的全部会话)。
四、验收/复查命令
# 会话日志完整性(应输出 total=N corrupt=0)
cd ~/.dsh/sessions; bad=0; t=0; for f in $(find . -name '*.zstd'); do t=$((t+1)); zstd -t "$f" >/dev/null 2>&1 || { bad=$((bad+1)); echo "CORRUPT $f"; }; done; echo "total=$t corrupt=$bad"
# 失效 spill 指针统计(DEAD 计数 = 已丢失全文的引用数)
for f in $(find ~/.dsh/sessions -name '*.zstd'); do for r in $(zstd -dc "$f" 2>/dev/null | grep -oE '/tmp/dsh-spill-[A-Za-z0-9]+/' | sort -u); do root=$(echo "$r" | cut -d/ -f3); [ -e "/tmp/$root" ] && echo ALIVE || echo DEAD; done; done | sort | uniq -c
# TMPDIR 是否生效
pm2 env 0 | grep -i tmpdir; ls -ld /home/zyw/.dsh-tmp/dsh-spill-* 2>/dev/null
五、可恢复性说明
- 会话正文/标题/工具调用记录:完好,可在 GUI 侧栏正常恢复。
- 超长工具输出的全文快照(spill):
/tmp 已清,不可恢复;会话内仍保留截断预览。
- 若当时按规范把产出发布到了
dl-hub/:正式交付物都在,不受影响。
- 9/19 之前的旧内容:可从
~/dsh-backups/dsh-backup-pre-015rc2-20260919-231342.tgz 中按需找回。
- 6 个
/home/zyw 孤儿会话:文件都在(~/.dsh/sessions/--home-zyw--/),需要的话可手工注册进 workspace.json 后即在 GUI 可见。