Rust + simd-json을 사용한 2 GiB/s AI 토큰 로그 파싱

발행: (2026년 1월 31일 오전 11:44 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

위에 제공된 소스 링크 외에 번역할 텍스트를 알려주시면 한국어로 번역해 드리겠습니다.

문제

저는 매일 Claude Code, Codex CLI, 그리고 Gemini CLI를 사용합니다. 어느 날 API 청구서를 확인했는데 예상보다 훨씬 높았고, 토큰이 어디로 가는지 전혀 알 수 없었습니다.

기존 추적 도구들은 너무 느렸습니다. 세 개의 CLI에 걸쳐 3 GB의 세션 파일(9,000개 이상의 파일)을 스캔하는 데 40 초가 넘게 걸렸습니다. 저는 즉시 확인할 수 있는 방법이 필요했습니다.

그래서 저는 toktrack를 만들었습니다 — 로컬에서 모든 것을 2 GiB/s 속도로 파싱하는 터미널 기반 토큰 사용량 추적기입니다.

데이터

각 AI CLI는 세션 데이터를 다르게 저장합니다:

CLI위치형식
Claude Code~/.claude/projects/**/*.jsonlJSONL, 메시지당 사용량
Codex CLI~/.codex/sessions/**/*.jsonlJSONL, 누적 카운터
Gemini CLI~/.gemini/tmp/*/chats/*.jsonJSON, thinking_tokens 포함

단일 Claude Code 세션 파일은 다음과 같이 보일 수 있습니다:

{
  "timestamp":"2026-01-15T10:00:00Z",
  "message":{
    "model":"claude-sonnet-4-20250514",
    "usage":{
      "input_tokens":12000,
      "output_tokens":3500,
      "cache_read_input_tokens":8000,
      "cache_creation_input_tokens":2000
    }
  },
  "costUSD":0.042
}

이를 수천 개의 세션에 걸쳐 몇 달 동안 곱하면, 파싱해야 할 JSONL이 기가바이트 단위가 됩니다.

Why simd-json

표준 serde_json도 훌륭하지만, 3 GB 규모의 라인‑구분 JSON을 파싱할 때 라인당 마이크로초 단위의 차이가 누적됩니다.

simd-jsonsimdjson을 Rust로 포팅한 것으로, SIMD 명령어(AVX2, SSE4.2, NEON)를 사용해 JSON을 훨씬 빠르게 파싱합니다. 핵심 트릭은 가변 버퍼를 이용한 제자리 파싱입니다.

#[derive(Deserialize)]
struct ClaudeJsonLine<'a> {
    timestamp: &'a str,               // borrowed, zero‑copy
    #[serde(rename = "requestId")]
    request_id: Option<&'a str>,      // borrowed, zero‑copy
    message: Option<&'a str>,
    #[serde(rename = "costUSD")]
    cost_usd: Option<f64>,
}

String 대신 &'a str을 사용함으로써 각 필드에 대한 힙 할당을 피할 수 있습니다. simd-json은 가변 바이트 버퍼에서 제자리로 JSON을 파싱하고, 우리의 구조체는 그 버퍼에서 슬라이스를 빌려 사용합니다.

주의할 점 하나: simd-jsonfrom_slice&mut [u8]를 요구하므로, 각 라인의 가변 복사본을 소유하고 있어야 합니다.

let reader = BufReader::new(File::open(path)?);
for line in reader.lines() {
    let line = line?;
    let mut bytes = line.into_bytes(); // owned, mutable
    if let Ok(parsed) = simd_json::from_slice(&mut bytes) {
        // extract what we need, bytes are consumed
    }
}

이 방법을 사용하면 내 데이터셋에서 표준 serde_json에 비해 17–25 % 정도의 처리량 향상을 얻을 수 있었습니다.

rayon을 이용한 병렬 처리 추가

단일 스레드 파서는 약 ~1 GiB/s의 속도를 기록했습니다. 9,000개 이상의 파일을 다룰 때, 파일 수준에서 rayon을 사용하면 쉽게 병렬화할 수 있습니다:

use rayon::prelude::*;

let entries: Vec<_> = files
    .par_iter()
    .flat_map(|f| parser.parse_file(f).unwrap_or_default())
    .collect();

Rayon의 par_iter()는 파일들을 자동으로 스레드에 분배합니다. simd-json과 결합하면 처리량이 ~2 GiB/s까지 상승했으며, 순차 파싱에 비해 3.2배 향상되었습니다.

단계처리량
serde_json (baseline)~800 MiB/s
simd-json (zero‑copy)~1.0 GiB/s
simd-json + rayon~2.0 GiB/s

어려운 부분: 각 CLI가 다름

실제 복잡성은 파싱 속도가 아니라, 단일 트레이트 뒤에서 세 가지 완전히 다른 데이터 형식을 처리하는 것이었습니다:

pub trait CLIParser: Send + Sync {
    fn name(&self) -> &str;
    fn data_dir(&self) -> PathBuf;
    fn file_pattern(&self) -> &str;
    fn parse_file(&self, path: &Path) -> Result<Vec<TokenRecord>, Box<dyn Error>>;
}

Claude 코드

간단합니다 — message.usage 필드가 있는 각 JSONL 라인은 하나의 API 호출입니다.

Codex CLI

복잡합니다. 토큰 수는 누적이며 — 각 token_count 이벤트는 증분이 아니라 현재 총합을 보고합니다. 모델 이름은 별도의 turn_context 라인에 있어 파싱이 상태를 유지해야 합니다:

line 1: session_meta   → extract session_id
line 2: turn_context   → extract model name
line 3: event_msg      → token_count (cumulative total)
line 4: event_msg      → token_count (larger cumulative total)

세션당 마지막 token_count만 유지해야 합니다.

Gemini CLI

표준 JSON( JSONL이 아님)을 사용하며, 다른 CLI에서는 추적하지 않는 고유한 thinking_tokens 필드를 포함합니다.

ratatui를 사용한 TUI

  • Overview — GitHub 스타일의 52주 히트맵을 사용한 총 토큰/비용
  • Models — 모델별 세부 내역과 퍼센트 바
  • Daily — 스파크라인 차트가 포함된 스크롤 가능한 테이블
  • Stats — 카드 그리드에 표시된 주요 지표

히트맵은 2×2 유니코드 블록 문자를 사용하여 52주 데이터를 컴팩트한 공간에 맞추며, 백분위 기반 색상 강도를 적용합니다.

결과

측정항목시간
콜드 스타트 (캐시 없음)~1.2 s
웜 스타트 (캐시 사용)~0.05 s

캐싱 레이어는 일일 요약을 ~/.toktrack/cache/에 저장합니다. 과거 날짜는 변경할 수 없으며—오늘만 다시 계산됩니다. 이는 Claude Code가 30 일 후에 세션 파일을 삭제하더라도 비용 기록이 유지된다는 의미입니다.

사용해 보기

npx toktrack
# or
cargo install toktrack

GitHub:

Claude Code, Codex CLI, 또는 Gemini CLI를 사용하고 토큰이 어디로 가는지 알고 싶다면 — 한 번 사용해 보세요.

Back to Blog

관련 글

더 보기 »

AI가 개발자를 대체할까요?

표지 이미지: AI가 개발자를 대체할까요? https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-up...