0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Codexが年640TBをSSDに書いていた、原因のTRACEログを追う

0
Posted at

手元のSSDが、キー入力していない時間帯まで一定のペースで削られていく。プロセスを追い詰めると、犯人はエディタでもブラウザでもなく、常駐させていたOpenAI Codex CLIのログ機構だった。あるユーザーの計測では、約21日の連続稼働でSSDへの書き込みが約37TBに達し、そのほとんどをCodexのSQLiteログが占めていた。年換算でおよそ640TB。1TBのSSDなら1年で約640回、ドライブ全体を上書きし尽くす計算になる。

この話が怖いのは、バグそのものよりも「静かさ」だ。エラーも警告も出ないまま、消耗品であるNANDフラッシュの寿命だけが削られる。報告はopenai/codex の Issue #28224にまとまっており、投稿者(Rui Fan氏)は実測データ付きで原因まで切り分けている。以下はその一次情報を追ったものだ。

💾 なぜ37TBも書けてしまうのか

Codexは操作フィードバック用のログを、ホームディレクトリのSQLiteファイルに書き続ける。実体は次の3ファイルだ。

~/.codex/logs_2.sqlite
~/.codex/logs_2.sqlite-wal
~/.codex/logs_2.sqlite-shm

面白いのは、ファイルサイズが暴れないことだ。DBは古い行を刈り取りながら運用されるため、見た目は1GB前後で落ち着いている。ところがSQLiteのAUTOINCREMENTカウンタを覗くと、保持している行と累計採番数のあいだに10000倍近い開きがある。

指標
現在のファイルサイズ 1.2 GiB
保持している行数 約50万行
採番済みのrow id累計 約55億

つまりファイルは太っていないのに、裏では膨大な行が「挿入されては刈られる」を繰り返している。投稿者が15秒間サンプリングしたところ、保持行数は変わらないまま36,211行が挿入されていた。挿入・インデックス更新・WALへの書き出し・刈り取りという一連の処理が、そのつどストレージへの物理書き込み(write amplification、書き込み増幅)を生む。ファイル監視ツールでサイズだけ見ていては絶対に気づけない類のコストだ。

TRACEログが7割を食っていた

では何がそんなに書かれているのか。保持バイトをログレベル別に割ると偏りが露骨だった。

レベル 保持バイトの割合
TRACE 70.7%
INFO 25.7%
DEBUG 3.0%
WARN 0.6%

TRACEは本来、開発者がバグを追うときだけ有効にする最も詳細なレベルだ。それが常時ディスクに焼かれている。内訳の筆頭はcodex_api::endpoint::responses_websocketのTRACEで527.4 MiB。APIとのWebSocketイベントの生ペイロードを一つ残らず記録していた。加えてcodex_otel.log_onlycodex_otel.trace_safeという、OpenTelemetryのミラー出力が二重に書き込まれ、これだけで25%を超える。要はテレメトリの下書きを、ローカルSSDに全文保存し続けていた。

根本原因はシンク(ログ出力先)の初期化にあった。SQLiteログ用のフィルタが、既定値をまるごとTRACEに設定していたのだ。

Targets::new().with_default(Level::TRACE)

この一行が効いているのがポイントで、コンソール表示の詳細度をどう絞ろうと、SQLiteシンクは自前の既定TRACEで全ターゲットを拾ってしまう。依存ライブラリの内部ログ(tokio-tungsteniteのWebSocket処理や、ld.so.cacheを開いたといったinotifyイベント)まで無差別に永続化されていた。ログレベル設計を一箇所間違えると、通信量ではなくストレージ寿命という思わぬ形で跳ね返る好例だと思う。

🛠 止め方と、公式の直し方

すぐ止めたいなら、Issue内で共有されている回避策が手堅い。Codexを終了してから、logsテーブルへのINSERTをトリガーで無効化する。

sqlite3 ~/.codex/logs_2.sqlite "CREATE TRIGGER IF NOT EXISTS \
  block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"

公式側も6月中に動いた。修正は3本のPRに分かれている。

PR 内容 反映バージョン
#29432 成功したWebSocketイベントのTRACE記録とOpenTelemetryログ/トレース発行を停止 0.142.0
#29457 target=logcodex_otelミラーをSQLiteシンクから除外 0.142.0
#29599 ブリッジ経由で漏れていた依存ログをシンク内で追加拒否 0.143.0

投稿者の再計測では、これらでログ量の約85%が削減された。カウンタや所要時間などのメトリクス、リモートへのOpenTelemetryエクスポートは残しつつ、ローカルへの生ペイロード保存だけを外す方針で、直し方としても筋がいい。

The local SQLite log sink currently enables TRACE for every target.(PR #29457より)

開発機のログは誰のためのものか

この一件は、Codex固有のうっかりミスとして片づけるにはもったいない。エージェント型ツールは「あとで挙動を再現・分析できるように」と観測ログを厚く取りたがるが、その保存先が開発者の物理ドライブになった瞬間、詳細度の既定値は寿命コストに直結する。SSDのTBW(総書き込み保証量)は消耗品の残量そのものであり、静かに減る。

実務者として持ち帰りたい教訓は二つある。ひとつは、常駐する開発ツールを入れたら~/.cache~/.local配下に肥大するログDBがないか一度確認しておくこと。もうひとつは、自分がツールを作る側なら、テレメトリの既定はTRACEではなくINFO以上に置き、生ペイロードの永続化はオプトインにすること。観測性は正義だが、その代金を無言でユーザーのハードウェアに請求してはいけない。バージョンを0.142.0以降に上げているかは、今日のうちに見ておいて損はない。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?