Codex

Codex 高频写盘排查与止损:logs_2.sqlite 的 TRACE 日志怎么处理

编辑部阅读约 9 分钟

Codex 在长时间会话和流式输出时会高频写入 ~/.codex/logs_2.sqlite,让磁盘写入量、WAL 文件体积和界面卡顿同时冒出来。文章给出一套只读诊断命令,并用 SQLite 触发器拦截写入,配合备份、checkpoint 和升级复测来止损;同时说明 SSD 寿命与卡顿归因上的分歧,帮你判断哪些做法适合自己。

Codex 这类命令行编程代理在长时间会话里会持续产出大量事件,其中相当一部分会落进本地 SQLite 数据库 ~/.codex/logs_2.sqlite。近期不少用户发现,这个文件连同它的 -wal 旁路文件在几分钟内就能涨到几十甚至上百 MB,磁盘写入指示灯常亮,风扇转速和界面响应也跟着变差。有人担心消费级 SSD 会被写坏,也有人更新版本后仍然复现,还有人发现拦截写入后界面照旧卡顿——到底哪些症状源自这个日志库,哪些只是巧合,需要分开验证。

下面按「先量化、再止损、后归因」的顺序给出可操作的做法。所有命令默认在 macOS / Linux 的 shell 中执行,Windows 用户在 WSL2 或 Git Bash 里等价处理即可。

先量化:判断是否真的在高频写盘

不要凭感觉下结论,先做只读诊断。下面三条互相独立,任意一条异常都值得继续查。

1. 看文件与 WAL 的体积

ls -lh ~/.codex/logs_2.sqlite*

正常情况下主库和 -wal 应该维持在较小量级,并且 WAL 会周期性回落。如果 -wal 一直只在涨、从不缩小,说明 checkpoint 没跟上写入节奏。

2. 看行数与增长速度

SQLite 的 WAL 模式下,用只读 URI 打开最安全:

db="file:$HOME/.codex/logs_2.sqlite?mode=ro"
sqlite3 "$db" "select count(*) as rows, max(id) as max_id from logs;"
sleep 15
sqlite3 "$db" "select count(*) as rows, max(id) as max_id from logs;"

两次采样之间如果行数持续增加,就说明有写入在进行。表名不一定是 logs,如果报 no such table,先用 .tables 看一眼实际结构,不要照抄字段名。

3. 看是谁在写

lsof ~/.codex/logs_2.sqlite* 2>/dev/null

确认持有文件句柄的进程确实是 Codex 本体,而不是某个索引服务或备份工具。Linux 上还可以读 /proc/<pid>/io 的 write_bytes 做累计量对比,macOS 上可以用 sudo fs_usage -w -f filesys | grep -i codex 观察实时写入。

注意:用 mode=ro 打开处于 WAL 模式的库,某些版本可能报错或看不到最新数据。这时可以临时加 &immutable=1,但那样读到的可能是旧快照,仅用于大致估算,别拿它当精确结论。

为什么会写成这样

从公开反馈看,形成写放大的原因大致有几类,可能同时存在:

  • 日志级别过细:流式响应、工具调用、重试等事件逐条以 TRACE 级别落库,单次会话就能产生海量行。
  • 逐事件提交:每条日志一次事务提交,SQLite 每次提交都要追加 WAL 记录,写入次数被成倍放大。
  • checkpoint 跟不上:WAL 增长快于 checkpoint 触发频率,文件只增不减。
  • 额外的遥测导出:有用户开启 OpenTelemetry 的 logs 与 traces 后,少量请求就产生上万条 span,进一步抬高写入量。
  • 运行环境差异:有反馈称在 WSL2 下更容易遇到,也有 macOS 用户在升级后仍看到持续写入。

方案一:升级后复测,别只信「已修复」

社区里对是否已修复存在明显分歧:一部分人表示更新到最新版本后写入恢复正常;另一部分人(尤其是 WSL2 和部分 macOS 用户)表示升级后日志仍在增长。稳妥的做法是把升级当成第一步,而不是终点:

  1. 更新到官方发布的最新版本。
  2. 完整跑一次典型的长会话或流式任务。
  3. 重复上面的三条诊断,对比升级前后的行数增速与 WAL 体积。

具体版本修了什么、是否覆盖你所在平台,请以官方 release notes 和仓库 issue 的最新说明为准,不要依赖转述。

方案二:用触发器拦截日志写入(临时止损)

如果确认写入异常且暂时不想等修复,可以在数据库层面直接让插入变成空操作。这是最直接的止血手段:

# 先备份,别跳过这一步
mkdir -p ~/codex-db-backup
sqlite3 "file:$HOME/.codex/logs_2.sqlite?mode=ro" ".backup '$HOME/codex-db-backup/logs_2.sqlite'"

# 创建拦截触发器
sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"

执行完再采样一次 max(id),如果数值不再变化,说明拦截生效。想恢复日志功能时:

db="file:$HOME/.codex/logs_2.sqlite?mode=ro"
sqlite3 "$db" "select count(*) as rows, max(id) as max_id from logs;"
sleep 15
sqlite3 "$db" "select count(*) as rows, max(id) as max_id from logs;"
``````bash
sqlite3 ~/.codex/logs_2.sqlite "DROP TRIGGER IF EXISTS block_log_inserts;"

几点需要提前知道:

  • 客户端升级有可能重建数据库或更换表结构,触发器随之失效,升级后要复查。
  • 拦截后本地不再保留这些日志,排查其他问题时可能失去线索,建议先备份再上。
  • 这是绕过应用自身逻辑的做法,属于临时手段,不是长期方案。

方案三:清理历史数据,把 WAL 收回去

长期积累的库本身也占空间,可以在 Codex 完全退出后做一次清理:

# 确认没有进程占用
lsof ~/.codex/logs_2.sqlite* 2>/dev/null

# 清空历史日志(视需要保留最近若干行)
sqlite3 ~/.codex/logs_2.sqlite "DELETE FROM logs;"

# 把 WAL 收回去
sqlite3 ~/.codex/logs_2.sqlite "PRAGMA wal_checkpoint(TRUNCATE);"

如果 wal_checkpoint(TRUNCATE) 返回 busy,说明还有连接持有写锁,先关掉所有相关进程再试。不要直接 rm 掉 -wal 文件,那可能造成数据库损坏或丢失未合并的数据。需要真正缩小主库体积时,再考虑在备份之后执行 VACUUM,它需要额外的临时磁盘空间。

方案四:从源头减少日志与遥测

如果当前版本提供了日志级别、调试开关或遥测导出配置,把 TRACE 级别和不需要的 OTel 导出关掉,比事后拦截更干净。具体字段名、环境变量与默认值随版本变化,请查官方文档后再改,不要照抄来路不明的配置片段。

方案五:把长时间任务挪到专门的机器上

如果主要痛点是本地机器在跑长会话时被日志写入和 CPU 抢占拖慢,可以考虑把这类任务放到一台专职机器上,比如按月租用的新加坡独享远程 Mac:整台 Mac mini 放在机房,通过远程连接使用,可以长期在线跑 Claude Code 之类的工具,本地只负责编辑和查看。这只是可选做法之一,是否值得取决于你对本地磁盘和响应的敏感程度。

关于 SSD 寿命:别慌,但要算一算

社区里两种声音都很响:一种认为这种写入强度会把消费级 SSD 写废,另一种表示硬盘健康度一个百分点都没掉。实际情况取决于累计写入量和你那块盘的 TBW(总写入字节数)标称值:

  • 先估算写入速率:采样前后 -wal 体积差 ÷ 时间间隔。
  • 再折算成每天的写入量,与你硬盘规格书上的 TBW 对比。
  • 各家健康度工具的统计口径不同,百分比只作参考,别当成绝对寿命。

短期的 TRACE 写放大通常不至于立刻损坏硬盘,但持续数月的异常写入没有必要容忍,止损成本很低。

卡顿是不是它造成的?

这里同样有分歧:有人认为界面卡、鼠标指针消失是刷日志导致,也有人拦截写入后依然复现。建议用排除法:

  1. 退出 Codex,观察卡顿是否消失。
  2. 用触发器拦截写入后重跑同样的任务,看是否还卡。
  3. 换一个不含大日志的简单会话做对照。

三步下来还卡,就要往渲染、字体、输入法、磁盘索引等方向查,不要把所有问题都归到这一个文件上。

检查清单

  • 用只读方式确认 logs_2.sqlite 与 -wal 的体积和增速
  • 确认持有文件句柄的是哪个进程
  • 完整备份后再动手
  • 升级到最新版本并复测,而不是只看别人的结论
  • 需要立即止血时用触发器拦截,并记录恢复方法
  • 退出进程后再做 DELETE 与 wal_checkpoint(TRUNCATE)
  • 关掉不必要的日志级别与遥测导出
  • 估算写入量对照硬盘 TBW,决定是否长期容忍

小结

这个问题的核心是「日志级别过细 + 高频提交 + WAL 未及时回收」三者叠加造成的写放大。处理顺序建议是:先只读量化、再备份、然后按需升级、必要时用触发器临时拦截并清理历史数据,最后回归到源头的日志配置。至于 SSD 会不会坏、界面卡不卡,都要用你自己的采样数据说话,而不是照搬别人的结论。

方案三:清理历史数据,把 WAL 收回去

长期积累的库本身也占空间,可以在 Codex 完全退出后做一次清理:

# 确认没有进程占用
lsof ~/.codex/logs_2.sqlite* 2>/dev/null

# 清空历史日志(视需要保留最近若干行)
sqlite3 ~/.codex/logs_2.sqlite "DELETE FROM logs;"

# 把 WAL 收回去
sqlite3 ~/.codex/logs_2.sqlite "PRAGMA wal_checkpoint(TRUNCATE);"

如果 wal_checkpoint(TRUNCATE) 返回 busy,说明还有连接持有写锁,先关掉所有相关进程再试。不要直接 rm 掉 -wal 文件,那可能造成数据库损坏或丢失未合并的数据。需要真正缩小主库体积时,再考虑在备份之后执行 VACUUM,它需要额外的临时磁盘空间。

方案四:从源头减少日志与遥测

如果当前版本提供了日志级别、调试开关或遥测导出配置,把 TRACE 级别和不需要的 OTel 导出关掉,比事后拦截更干净。具体字段名、环境变量与默认值随版本变化,请查官方文档后再改,不要照抄来路不明的配置片段。

方案五:把长时间任务挪到专门的机器上

如果主要痛点是本地机器在跑长会话时被日志写入和 CPU 抢占拖慢,可以考虑把这类任务放到一台专职机器上,比如按月租用的新加坡独享远程 Mac:整台 Mac mini 放在机房,通过远程连接使用,可以长期在线跑 Claude Code 之类的工具,本地只负责编辑和查看。这只是可选做法之一,是否值得取决于你对本地磁盘和响应的敏感程度。

关于 SSD 寿命:别慌,但要算一算

社区里两种声音都很响:一种认为这种写入强度会把消费级 SSD 写废,另一种表示硬盘健康度一个百分点都没掉。实际情况取决于累计写入量和你那块盘的 TBW(总写入字节数)标称值:

  • 先估算写入速率:采样前后 -wal 体积差 ÷ 时间间隔。
  • 再折算成每天的写入量,与你硬盘规格书上的 TBW 对比。
  • 各家健康度工具的统计口径不同,百分比只作参考,别当成绝对寿命。

短期的 TRACE 写放大通常不至于立刻损坏硬盘,但持续数月的异常写入没有必要容忍,止损成本很低。

卡顿是不是它造成的?

这里同样有分歧:有人认为界面卡、鼠标指针消失是刷日志导致,也有人拦截写入后依然复现。建议用排除法:

  1. 退出 Codex,观察卡顿是否消失。
  2. 用触发器拦截写入后重跑同样的任务,看是否还卡。
  3. 换一个不含大日志的简单会话做对照。

三步下来还卡,就要往渲染、字体、输入法、磁盘索引等方向查,不要把所有问题都归到这一个文件上。

检查清单

  • 用只读方式确认 logs_2.sqlite 与 -wal 的体积和增速
  • 确认持有文件句柄的是哪个进程
  • 完整备份后再动手
  • 升级到最新版本并复测,而不是只看别人的结论
  • 需要立即止血时用触发器拦截,并记录恢复方法
  • 退出进程后再做 DELETE 与 wal_checkpoint(TRUNCATE)
  • 关掉不必要的日志级别与遥测导出
  • 估算写入量对照硬盘 TBW,决定是否长期容忍

小结

这个问题的核心是「日志级别过细 + 高频提交 + WAL 未及时回收」三者叠加造成的写放大。处理顺序建议是:先只读量化、再备份、然后按需升级、必要时用触发器临时拦截并清理历史数据,最后回归到源头的日志配置。至于 SSD 会不会坏、界面卡不卡,都要用你自己的采样数据说话,而不是照搬别人的结论。

Codex日志排查磁盘写入SQLiteWAL