Codex 로깅 버그가 로컬 SSD에 TB 단위 쓰기를 발생시켜 SSD 수명을 빠르게 소모할 수 있음
1 day ago
3
- Codex가 로컬 SQLite 피드백 로그 DB에 지속적으로 대량 데이터를 기록하며, 한 사용자 환경에서 21일 가동 후 메인 SSD에 약 37TB가 기록됨
- 이를 환산하면 연간 약 640TB, 1TB SSD 기준 연 약 640회 전체 쓰기에 해당하며, 일부 컨슈머 SSD의 보증 수명(약 600 TBW)을 1년 이내에 소진할 수 있음
- 보존 행은 약 50만 개에 불과하지만 AUTOINCREMENT 카운터는 55억 ID를 넘겨, 보존 행과 누적 삽입 ID 사이에 약 1만 배 격차가 존재
- 원인은 SQLite 피드백 로그 싱크가 글로벌 TRACE 기본값(Targets::new().with_default(Level::TRACE))으로 설정되어, 의존성 내부 로그와 대용량 raw 프로토콜 페이로드까지 모두 영구 기록하기 때문
- 2026년 6월 22일 두 개의 PR이 병합되어 약 85%의 로그를 차단함에 따라 이슈가 종료됨
핵심 증상 및 영향 범위
- Codex가 다음 파일에 지속적으로 대량 기록 발생
- ~/.codex/logs_2.sqlite
- ~/.codex/logs_2.sqlite-wal
- ~/.codex/logs_2.sqlite-shm
- 21일 가동 후 메인 SSD에 약 37TB 기록, 프로세스/파일 단위 점검에서 Codex SQLite 로그가 주요 지속 기록원으로 확인됨
- 연간 약 640TB로 환산, 1TB SSD 기준 연 약 640회 전체 드라이브 쓰기 수준
- 일부 컨슈머 SSD는 약 600 TBW 등급으로, 보증 쓰기 수명을 1년 미만에 소진 가능
증거 데이터
-
Evidence1 — 쓰기 증폭(write amplification)
- 현재 logs_2.sqlite 파일 크기: 1.2 GiB
- 현재 보존 행: 506,149개
- 누적 할당 row id: 5,543,677,486개
- 보존 행(약 0.5M)과 누적 삽입 ID(55억+) 사이 약 1만 배 격차, WAL·인덱스·프루닝·체크포인트·페이지 재기록·기기 단위 증폭을 제외해도 10TB+ 규모의 과거 로그 churn 추정
-
Evidence2 — 레벨/타깃 분포
- 보존 행 681,774개, 추정 보존 로그 콘텐츠 약 1,035.6 MiB
- 레벨별 비중: TRACE 70.7%, INFO 25.7%, DEBUG 3.0%, WARN 0.6%
- 최대 target+level 쌍
- codex_api::endpoint::responses_websocket (TRACE) 527.4 MiB
- codex_otel.log_only (INFO) 141.2 MiB
- codex_otel.trace_safe (INFO) 121.2 MiB
- log (TRACE) 97.4 MiB
- 상위 소스는 글로벌 TRACE 로그, 미러링된 텔레메트리 로그, raw websocket/SSE 페이로드 기록이 대부분
- codex_otel.log_only + codex_otel.trace_safe가 추가로 25.3% 차지, 해당 카테고리 필터링 시 표본 기준 보존 로그 바이트의 약 96% 제거 가능
- 가장 빈번한 TRACE 소스(target=log)는 inotify event(예: ld.so.cache 128,764회), tokio-tungstenite 내부 호출, WouldBlock 등 저수준 항목이 다수
-
쓰기 증폭 측정
- 15초 표본에서 보존 행은 681,774개로 변동 없이, 약 36,211개 행이 삽입됨
- 삽입→인덱싱→WAL 기록→프루닝이 반복되는 insert-and-prune 패턴으로 쓰기 증폭 발생
추정 원인
- SQLite 피드백 로그 싱크가 글로벌 TRACE 기본값(Targets::new().with_default(Level::TRACE))으로 설치됨
- 이로 인해 의존성/내부 로그와 대용량 raw 프로토콜 페이로드까지 모든 타깃이 TRACE 레벨로 영구 저장됨
제안된 수정 방향
- 피드백 로그는 유지하되 기본 영구 저장 범위를 좁힐 것
- SQLite 피드백 로그 싱크에 글로벌 TRACE 사용 금지
- target=log, hyper_util, tokio-tungstenite 내부, inotify 스팸, 저수준 OpenTelemetry SDK 로그 등 저가치 노이즈의 임계값 상향 또는 제거
- raw websocket/SSE 페이로드 전체 저장 대신 요약(이벤트 종류, 소요시간, 성공/실패, 토큰 사용량, 페이로드 바이트 길이) 저장
- codex_otel.log_only / codex_otel.trace_safe 미러 이벤트는 디버깅에 유용한 경우 외 저장 회피
- 스레드 단위 캡만으로는 부족하므로 글로벌 로그 DB 크기/쓰기 상한 추가
- sqlite_logs_enabled = false 같은 옵션도 유용하나, 핵심 해결은 더 나은 기본 필터링
다중 플랫폼 재현 보고
-
macOS
- macOS 15.7.7 / Codex 26.616.51431 환경에서 logs_2.sqlite 113M, MAX(id)=34,277,360에 보존 행 31,405개, 60초 표본 2회에서 약 초당 60회 쓰기 확인
- 1~2시간 세션 동안 codex 프로세스가 약 50GB 기록한 사례 보고
-
Windows
- Windows Codex Desktop(codex.exe app-server --analytics-default-enabled)에서 RUST_LOG=warn 및 명시적 trace 설정이 없는데도 TRACE 행이 지속 삽입됨
- 보존 행 약 71k, sqlite_sequence의 logs 값은 1,850만 초과로 과거 insert/prune churn 다수
- 10분 분포에서 TRACE 1,812행, 상위 TRACE 타깃에 codex_api::endpoint::responses_websocket(3.5MB+), codex_api::sse::responses 포함
- 기대 동작: RUST_LOG=warn 시 의존성/내부 TRACE 및 대용량 페이로드를 지속 저장하지 않아야 함
추가 위험 및 임시 완화책
-
데이터 손실 위험
- 디스크가 가득 찬 상태에서 재부팅 시 Linux 로그인 실패 가능
- Codex /goal 모드가 디스크 공간 확보를 시도하며 파일/폴더를 삭제해 데이터 손실 발생 가능
-
임시 완화 스크립트
- 실행 중 WAL을 트리밍하는 trim-codex-wal.sh(PRAGMA wal_checkpoint(TRUNCATE) 사용), cron으로 15분마다 실행 가능
- 로그/WAL 파일 삭제 후 Codex 관련 프로세스에 SIGTERM→SIGKILL을 보내 디스크 공간을 즉시 확보하는 fix-codex-wal.sh
- logs 테이블 삽입을 무시하는 SQLite 트리거(block_log_inserts)를 추가하면 WAL 증가가 멈춤, 되돌릴 때는 DROP TRIGGER IF EXISTS block_log_inserts
- VACUUM은 DB를 재기록하므로 대용량 파일에서는 일회성 대형 쓰기를 유발할 수 있어, 트리거 추가 후 WAL 증가 중단 확인 뒤에 DELETE/VACUUM 실행 권장
- 사설 SQLite 스키마 수정이므로 향후 Codex 업데이트/마이그레이션이 테이블 재생성, 트리거 제거 등으로 동작할 가능성 있음
- 영구 수정 전까지 SSD 손상을 막으려면 해당 DB를 ramdisk에 두는 방법도 제시됨
해결 및 종료
- 2026년 6월 22일 두 개의 PR 병합으로 약 85% 로그 절감, 이에 따라 이슈가 완료 처리됨
- 모든 Responses WebSocket 이벤트 로깅 중단 (#29432)
- 영구 로그에서 노이즈 타깃 필터링 (#29457)
- 별도 제안 패치에서는 글로벌 TRACE 대신 INFO+ 기본 저장, codex_otel.log_only·codex_otel.trace_safe·hyper_util·log·opentelemetry_sdk 등을 WARN+로 상향
- 수정 사항이 rust-v0.142.0으로 릴리스됨
-
Homepage
-
개발자
- Codex 로깅 버그가 로컬 SSD에 TB 단위 쓰기를 발생시켜 SSD 수명을 빠르게 소모할 수 있음