TL;DR
在上一篇文章中,我们通过“预建缓存 + PowerShell 原生读取”将对话记录的加载时间从 6 秒压缩至 88 毫秒,效果非常理想。然而两周后,Codex 桌面应用突然彻底卡死——点击任何历史对话都会导致应用无响应,不是变慢,而是完全冻结。

本次故障的根因有两个,而且隐藏得更深:
- SQLite WAL 日志无限膨胀:
state_5.sqlite的 WAL 文件从 0 暴涨至 4MB(数据库本身仅 300KB),每次读取需要扫描 870 页未合并的日志; - 日志数据库完全失控:
logs_2.sqlite膨胀到 70MB,其中 52% 是 TRACE 级别的冗余日志。
解决方案是一套组合策略:一键启动器(启动时自动清理)+ Windows 定时任务(每 3 小时自动维护)+ 增强版 rh 命令。
问题再次爆发
上一篇内容发布后,rh 命令一直稳定运行——88 毫秒内返回结果,毫无延迟,我甚至快忘记了桌面应用侧边栏里那个“加载中…”的提示。
直到某一天,Codex 桌面应用突然彻底卡死。注意,不是运行缓慢——点击任意历史对话后应用立即假死,任务管理器显示“无响应”。
有意思的是,终端中的 rh 依然能秒速输出结果。这说明数据本身没有损坏,问题出在应用自身的读取机制上。
排查过程:三个 SQLite 文件的秘密
Codex 的数据目录 ~/.codex 中存放着三个 SQLite 数据库:
state_5.sqlite 300KB ← 对话元数据(线程、标题、模型)
logs_2.sqlite 70MB ← 运行日志
goals_1.sqlite 24KB ← 目标任务
只关注大小似乎一切正常,直到我注意到同名的 .sqlite-wal 文件:
state_5.sqlite 300KB ← 数据库本体
state_5.sqlite-wal 4.1MB ← WAL 日志!!!
state_5.sqlite-shm 32KB
logs_2.sqlite 70MB
logs_2.sqlite-wal 4.2MB ← 同样是 4MB 的 WAL
WAL 文件体积是数据库本体的 13 倍,这才是真正的罪魁祸首。
什么是 WAL,为什么会膨胀到这种程度?
SQLite 默认使用 WAL(Write-Ahead Log)模式。简单来说:写操作不会直接修改数据库,而是先写入 WAL 文件;读操作需要同时查询数据库和 WAL,合并出最新结果;当 WAL 积累到一定量时,才会触发 checkpoint 合并回主数据库。
正常情况下这个过程对用户完全透明。但 Codex 桌面应用在运行期间持续高频写入(记录日志、更新状态),而 checkpoint 的频率跟不上写入速度,导致 WAL 像滚雪球一样越来越大。
正常 WAL:▏ 几 KB,随时合并
Codex 的 WAL:████████████████ 4MB,870 页等待合并
每次切换对话,应用读取 state_5.sqlite 时,SQLite 需要在 870 页 WAL 中逐页查找最新数据。870 次磁盘 IO,每次几百毫秒——UI 线程直接卡死。这才是问题的根本所在。
解决方案
1. 手动执行 checkpoint:将 WAL 合并回数据库
import sqlite3
conn = sqlite3.connect("state_5.sqlite")
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
conn.close()
效果立竿见影:
state_5.sqlite-wal: 4,140KB → 0KB
logs_2.sqlite-wal: 4,273KB → 0KB
但这只是临时措施。Codex 继续运行后,几小时内 WAL 又会涨回来。
2. 日志数据库大清理
logs_2.sqlite 为什么会有 70MB?查看日志级别分布就能明白:
TRACE: 17,578条 (52.1%) ← 全部是 HTTP 连接、文件监控、SSE 流的冗余日志
INFO: 7,508条
DEBUG: 7,328条
WARN: 1,302条
ERROR: 33条
超过一半是 TRACE 日志,每次 HTTP 请求、每个文件变动都会记录一条。删除 TRACE 和 DEBUG 后:
logs_2.sqlite: 70MB → ~15MB
3. 一键启动器:从此告别手动操作
手动执行 checkpoint 太不智能。于是我编写了一个启动器 codex_launcher.ps1,每次双击它就会自动执行:
- 检查 Codex 是否已在运行
- 对三个数据库执行 WAL checkpoint
- 日志超过 30MB 时自动清理 TRACE/DEBUG
- 清理临时文件
- 启动 Codex 桌面应用
将其放在桌面作为快捷方式,从此每次启动都是干净状态。
4. Windows 定时任务:无人值守自动维护
绕不开的问题是:Codex 运行时数据库被锁定,无法执行清理。因此我设置了一个 Windows 定时任务,每 3 小时自动运行一次——如果 Codex 恰好已关闭,则立即清理;如果仍在运行,则等待下一次触发。
schtasks /Create /TN "Codex DB Auto Cleanup" /SC DAILY /RI 180 /DU 24:00 /IT /F `
/TR "powershell -WindowStyle Hidden -File codex_cleanup.ps1"
5. 增强版 rh:搜索 + 详情展示
原来的 rh 只能列出对话和查看详情。增强版增加了关键词搜索功能:
rh # 列出对话(88ms)
rh 赛博朋克 # 搜索标题包含“赛博朋克”的对话
rh 019e6a94 # 查看指定对话的详细信息
rh --rebuild # 强制刷新缓存
现在 rh 同时能被 cmd 和 PowerShell 识别,并已复制到 PATH 目录中。
完整文件清单
| 文件 | 用途 | 新增 |
|---|---|---|
build_cache.py | SQLite + JSONL 转换为 JSON 缓存 | 原有 |
rh.ps1 | 列表 / 搜索 / 详情(PowerShell 原生) | 增强 |
codex_cleanup.ps1 | WAL checkpoint + 日志清理 + VACUUM | 新增 |
codex_launcher.ps1 | 清理 → 启动 Codex(一键操作) | 新增 |
codex_launcher.bat | bat 包装器,双击即用 | 新增 |
经验教训
这次深入调查让我对 SQLite WAL 有了切身体会:
Codex 的问题在于将大量 TRACE 日志和状态更新写入 WAL,却未能及时执行 checkpoint。应用开发者应该要么降低日志级别,要么定期主动触发 checkpoint。
对用户而言,上一篇解决的是“慢”,这一篇解决的是“死”。两者结合才能构成完整的优化方案:
终端浏览(快)← rh → 88ms → 上篇
应用不卡(稳)← cleanup + launcher → 本篇
自动维护(省心)← 定时任务 → 本篇
如果你也遇到过“以为修好了结果又崩了”的情况,不要急着否定之前的方案——先检查 SQLite 的 WAL 文件和日志数据库,这个坑比 Python 冷启动隐藏得更深。
