lifecarelog
프론트엔드

세션 중간에 CLAUDE.md를 고쳤더니 캐시가 사라진 이유, 화면으로 잡기

프롬프트 캐시 무효화는 로그를 눈으로 훑어서는 안 보여요. cache_read 비율을 턴별로 그리는 작은 React 패널로 원인을 찾은 과정을 정리했어요.

7분 읽기

세션 중간에 CLAUDE.md를 고쳤더니 캐시가 사라진 이유, 화면으로 잡기

AI 코딩 도구를 하루 종일 켜 두면 비용이 조용히 올라가요. 그런데 어느 날은 같은 작업량인데 청구 토큰이 눈에 띄게 늘어 있더라고요. 원인은 세션 중간에 CLAUDE.md 한 줄을 고친 거였는데, 이걸 로그 텍스트만 봐서는 알 수 없었어요. 그래서 턴별 캐시 비율을 그리는 작은 화면을 만들었고, 그림이 뜨자마자 원인이 보였어요.

왜 로그로는 안 보이는지부터

Anthropic API 응답의 usage 객체에는 input_tokens 외에 cache_read_input_tokens와 cache_creation_input_tokens가 따로 와요. 캐시는 프롬프트 앞부분(prefix)이 바이트 단위로 같을 때만 재사용되고, 공식 문서 기준 기본 유지 시간은 5분, 캐시 읽기 단가는 기본 입력 단가의 10%예요. 최소 캐시 길이도 1,024토큰이라 짧은 프롬프트는 애초에 대상이 아니에요.

문제는 이 숫자들이 한 줄에 셋씩 섞여 나온다는 거예요. 30턴짜리 JSONL을 눈으로 읽으면 어느 턴에서 cache_read가 0으로 떨어졌는지 놓치기 쉬워요. 비율 하나로 줄여서 세로로 세워야 보여요.

usage JSONL 읽기턴별 cache_read 비율 계산리스트로 렌더급락 턴 표시

컴포넌트는 3개면 충분했어요

직접 만들어 보니 파일 1개, 80줄 안쪽으로 끝났어요. 계산 함수 하나, 행 컴포넌트 하나, 목록 컴포넌트 하나예요. 막대는 라이브러리 없이 브라우저 기본 meter 요소를 썼어요.

type Usage = {
  input_tokens: number;
  cache_read_input_tokens: number;
  cache_creation_input_tokens: number;
};
 
function cacheReadRatio(u: Usage): number {
  const total =
    u.input_tokens + u.cache_read_input_tokens + u.cache_creation_input_tokens;
  return total === 0 ? 0 : u.cache_read_input_tokens / total;
}
 
function TurnRow({ index, usage }: { index: number; usage: Usage }) {
  const ratio = cacheReadRatio(usage);
  const suspicious = index > 0 && ratio < 0.5;
  return (
    <li className="turn-row" data-suspicious={suspicious}>
      <span className="turn-index">{index + 1}</span>
      <meter min={0} max={1} low={0.5} optimum={1} value={ratio} />
      <span className="turn-ratio">{(ratio * 100).toFixed(0)}%</span>
    </li>
  );
}
 
export function CachePanel({ turns }: { turns: Usage[] }) {
  return (
    <ol className="cache-panel">
      {turns.map((u, i) => (
        <TurnRow key={i} index={i} usage={u} />
      ))}
    </ol>
  );
}

숫자 칸에는 font-variant-numeric: tabular-nums를 줬어요. 퍼센트가 한 자리에서 두 자리로 바뀔 때 막대가 흔들리지 않아야 급락이 눈에 걸리거든요. 의심 행은 배경을 color-mix로 살짝 띄우고 왼쪽 점 하나만 찍었어요. 한쪽 테두리를 두껍게 긋는 방식은 일부러 피했어요.

돌려 보니 원인이 한 턴에 찍혀 있었어요

실제 세션 로그를 넣고 돌려 보니 앞쪽 턴들은 캐시 비율이 90% 근처에서 일정했어요. 그러다 한 턴에서 0%로 떨어지고, 같은 턴의 cache_creation이 다시 크게 잡혔어요. 시각으로 대조해 보니 그 직전에 CLAUDE.md를 수정한 시각이었어요.

이유는 단순해요. 규칙 파일 내용은 시스템 프롬프트, 즉 prefix의 앞부분에 들어가요. 거기서 한 줄이라도 바뀌면 그 뒤 전체가 새 prefix가 되고, 캐시는 처음부터 다시 써져요. 파일을 고친 시점부터 세션이 끝날 때까지 매 턴이 캐시 쓰기 요금을 냈던 거예요.

적용해 봤더니 대응은 두 가지로 정리됐어요. 규칙 파일 수정은 세션을 새로 열기 직전에만 하고, 패널에는 "규칙 파일 변경 감지" 마커를 턴 옆에 같이 그려 두는 거예요. 마커 하나 추가하는 데 5분도 안 걸렸는데, 이후로는 비율이 떨어져도 원인을 추측할 일이 없어졌어요.

  • prefix 한 줄 변경 = 뒤 전체 무효화
  • 비율 하나로 줄여서 세로로
  • 급락 턴 옆에 원인 마커

어디에 다시 쓰게 되나요

테스트해 보면 이 패턴은 AI 사용량을 보여주는 화면이면 어디든 그대로 옮겨져요. 라이프케어로그에서 개발 중인 AI 앱 플랜티(plan-t)도 응답마다 usage가 오니까, 사용자에게 보이는 화면은 아니어도 운영 패널에서는 같은 행 컴포넌트를 쓸 계획이에요.

기준은 하나만 잡아 두면 돼요. 세션 전체 cache_read 합을 전체 입력 합으로 나눈 값이 0.85 아래로 내려가면 무효화가 있었다고 보고, 어느 턴인지 화면에서 찾는 거예요. 텍스트 로그를 다시 뒤지는 일보다 그림 한 장이 훨씬 빨랐어요.

#React#프롬프트 캐싱#frontend

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.