跳转至

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 同步,请勿在站点侧修改)