RUNBOOK:token_proxy 状态持久化(thinking_stages + cache_probe 快照)¶
交付物:本次为
proxy.py增加的内存态落盘能力(状态目录logs/state/)。 服务本身的日常运维(启停、日志、报表、价目表等)单一源是docs/SOP.md(第八、九、十、十三章), 本文只写本交付物相关的操作,避免两处漂移。 日期:2026-10-08
1. 服务概览¶
- 组件:
token_proxy(systemd 用户单元,监听0.0.0.0:8788)+ 本交付物新增的状态落盘。 - 状态文件(2 个,均在
logs/state/): thinking_stages.json:会话 → 思考阶段(collecting/coding/deploying/unknown),阶段变化时即写;cache_probe_snaps.json:会话 → 探测用截断前缀快照,只在进程退出(SIGTERM)时写。- 依赖:无外部依赖;写盘失败一律静默(尽力而为)。
- 异常时的业务影响:状态丢失不影响转发,只是退化为改动前的行为(重启后阶段可能降级、首次空闲保护失效)。
- 重要性等级:低 —— 纯优化项,可随时丢弃(
rm -rf logs/state即回到原行为)。
2. 接入与凭据¶
- 无独立接入方式:状态由
proxy.py内部读写,不对外暴露端口或接口。 - 无凭据。状态文件权限随目录(
logs/state/,无敏感内容:只是消息前缀与阶段标签)。
3. 日常速查表¶
cd /home/rain/Documents/services/token_proxy
systemctl --user status token-proxy # 服务状态
tail -n 1 logs/proxy.log # 启动行(含「状态恢复=阶段N/快照M」)
ls -l logs/state/ # 两个状态文件的时间与大小
du -sh logs/state/ # 状态目录体积
tail -n 50 logs/proxy.log | grep '缓存探测' # 近期探测记录
rm -rf logs/state # 清空状态(等价于关闭本能力,无需重启)
4. 日常操作¶
| 需求 | 做法 |
|---|---|
| 启停 / 重启 | systemctl --user restart token-proxy(重启是唯一会写快照的时机) |
| 查看当前恢复情况 | 重启后看启动行 状态恢复=阶段N/快照M:首次为 0(旧进程无落盘能力),第二次起应非零 |
| 临时关闭持久化 | TOKEN_PROXY_STATE_DIR 指向不可写路径,或直接 rm -rf logs/state |
| 立即落盘(不重启) | 不支持——设计上快照只在退出时写,故意不进请求热路径 |
5. 配置管理¶
- 唯一配置项是目录位置:环境变量
TOKEN_PROXY_STATE_DIR(默认<工程>/logs/state)。 - 改法:在 systemd 单元里加
Environment=TOKEN_PROXY_STATE_DIR=...,然后systemctl --user daemon-reload && systemctl --user restart token-proxy。 - 易错点:不要指向网络盘或频繁同步的目录——退出时写一次 ~100 KB 是设计预期,同步盘会造成抖动。
6. 备份与恢复¶
- 不需要备份:状态是纯缓存,丢了只是退化,无业务数据。
- 若确要留存当前状态:
cp -p logs/state/*.json /tmp/(含会话前缀,注意别外传)。 - 恢复:把文件放回
logs/state/后重启即可(超 24 h 的快照会被load_state自动丢弃)。
7. 监控与告警¶
- 关键观察点:启动行
状态恢复=字段。 - 一直是
阶段0/快照0→ 说明落盘没生效(查目录权限、查退出前落盘失败日志)。 - 日志位置:
logs/proxy.log;系统侧journalctl --user -u token-proxy。 - 建议阈值:
du -sh logs/state/持续 > 10 MB 时清理(按 64 会话 × ~100 KB ≈ 6 MB 为正常上限)。
8. 故障处置¶
场景 A:重启后启动行仍是 状态恢复=阶段0/快照0¶
症状:反复重启都不恢复。 诊断
ls -l /home/rain/Documents/services/token_proxy/logs/state/ # 预期两个 .json 都在
grep '退出前落盘失败' /home/rain/Documents/services/token_proxy/logs/proxy.log
处置:目录不存在 → mkdir -p logs/state 并确认属主;有落盘失败 → 按日志里的 OSError 查磁盘/权限。
验证:再重启一次,启动行应出现非零。
场景 B:状态文件损坏(JSON 解析失败)¶
症状:日志无异常,但快照不恢复。
诊断:python3 -c "import json;json.load(open('logs/state/cache_probe_snaps.json'))"
处置:rm -f logs/state/cache_probe_snaps.json 后重启——load_state 对坏文件是静默丢弃,删掉即可重建。
验证:重启后启动行 快照0(预期正常,非故障)。
场景 C:重启后首个请求仍出现大 miss¶
症状:重启后 cache_probe 没有产生探测行,该步 miss 很大。
诊断
tail -n 30 logs/proxy.log | grep '缓存探测' # 有无探测行
tail -n 30 logs/proxy.log | grep '状态恢复' # 本次启动恢复了几条
处置:若 快照0 → 属首次重启的正常现象(无历史可恢复);若快照非零仍不探测 →
查该会话空闲是否 ≥ cache_probe.idle_seconds(1 h)。注意:本能力只覆盖"快照存在时"的保护,
重启打断在飞请求导致的重发不在本能力范围内(见 docs/SOP.md 第五十四节)。
验证:空闲 ≥1 h 后再发请求,应出现 缓存探测 conv=… 空闲=…s 命中=…/…。
9. 安全与权限¶
- 状态文件含消息前缀(对话内容片段)→ 按对话内容对待:不提交到 git、不外传、目录权限保持默认。
- 无凭据、无证书、无账号模型。
- 访问控制:仅本机文件系统权限;状态不含转发凭据(Authorization 从不落盘)。
10. 容量与性能¶
- 体积:单会话 ≈ 100 KB(截断后缀),上限
cache_probe.max_convs(64)× 100 KB ≈ 6 MB。 - 写入频率:阶段文件随阶段变化写(每会话几次);快照只在退出时写一次 → 稳态零 I/O。
- 扩容触发条件:
du -sh logs/state/> 10 MB → 清空重建或调小max_convs。 - 已知瓶颈:无。退出时写 6 MB 需要 ~50 ms,可忽略。
11. 附录¶
- 变更档案:
state-persist-20261008/SOP.md(八章)| 备份台账:state-persist-20261008/backup/README.md - 相关档案:
cache-probe-20261008/(探测功能本体)|stage-thinking-20260930/(思考阶段功能本体) - 服务总手册:
docs/SOP.md(第八日常运维 / 第十三故障排查 / 第十四回滚与下线) - 修订记录:2026-10-08 首版(随本次变更产出)
来源:
/home/rain/Documents/services/token_proxy/state-persist-20261008/RUNBOOK.md| 最后更新:2026-10-08 12:27(由build/sync.py同步,请勿在站点侧修改)