
파트너사의 고객 지원 봇을 처음 진단했을 때 솔직히 예상 밖이었습니다. 월 API 비용의 68%가 실제 AI 처리가 아닌 히스토리 재전송에 쓰이고 있었거든요. 코드는 멀쩡히 돌아가고 있었고, 아무도 문제를 몰랐습니다. LLM 개발에서 토큰 낭비는 대부분 이렇게 조용히 쌓입니다.
실제로 가장 많이 보이는 낭비 패턴 진단
제가 직접 코드 리뷰와 프로덕션 모니터링을 하면서 반복적으로 마주친 패턴이 다섯 가지입니다.
첫 번째는 역할 지정 과잉, 이른바 Role Bloat입니다. Role Bloat이란 시스템 프롬프트에서 AI의 페르소나를 지나치게 길고 상세하게 설정하는 현상을 말합니다. "당신은 10년 경력의 시니어 소프트웨어 엔지니어이자 아키텍트이며 DevOps 전문가로서, 객체지향 설계와 함수형 프로그래밍 모두에 능통하고..." 같은 식입니다. 제가 직접 측정해보니 이런 역할 지시만으로 150~300 토큰이 추가로 소비되고 있었는데, "당신은 숙련된 소프트웨어 엔지니어입니다." 한 줄과 실제 출력 품질 차이는 거의 없었습니다.
두 번째는 Few-shot 예시 과다 삽입입니다. Few-shot이란 모델에게 작업 방식을 예시로 보여주는 기법인데, 많은 팀이 예시를 5개 이상 넣는 것을 당연하게 여깁니다. 실제로 써봤는데, 잘 선택된 예시 1~2개가 5개짜리 예시 묶음과 거의 동일한 품질을 냈습니다. 예시가 많을수록 좋다는 생각은, 적어도 입력 토큰 비용 측면에서는 틀렸습니다.
세 번째는 툴 정의의 verbose한 파라미터 설명입니다. 여기서 툴 정의(function calling)란 LLM이 외부 함수를 호출할 수 있도록 함수의 이름과 파라미터를 명세하는 방식을 말합니다. 이 명세가 매 API 호출마다 입력 토큰에 포함되는데, 불필요하게 길고 장황한 파라미터 설명은 모델 성능에 거의 영향을 주지 않으면서 토큰만 낭비합니다.
네 번째는 구조화된 출력 형식 미사용입니다. 내부 파이프라인에서 출력을 프로그래밍으로 파싱할 것임에도 "서론, 본론, 결론 구조로 나누어 각 섹션에 소제목을 달고, 핵심 포인트는 불릿으로 정리하되..." 같은 서술형 지시를 붙이는 경우가 많습니다. 이런 형식 지시가 출력 토큰의 40~60%를 차지하는 케이스를 실제로 본 적이 있습니다. "JSON 형식으로만 출력, 설명 없음" 한 줄이 훨씬 효율적입니다.
다섯 번째는 datetime.now() 시스템 프롬프트 삽입입니다. 캐싱을 설정해 놓고 시스템 프롬프트에 현재 타임스탬프를 동적으로 삽입하는 경우인데, 타임스탬프가 매 초 바뀌니 캐시 히트율이 0%가 됩니다. 이 패턴은 특히 캐싱 도입 후에도 비용이 줄지 않는다는 피드백을 받았을 때 가장 먼저 확인하는 항목이 됐습니다.
자주 발견되는 낭비 패턴을 정리하면 다음과 같습니다.
- Role Bloat: 과도한 역할 지시로 150~300 토큰 추가 소비
- Few-shot 과다: 예시 5개 이상 사용, 1~2개와 품질 차이 없음
- 장황한 function calling 명세: 매 호출마다 불필요한 입력 토큰 누적
- 서술형 출력 지시: 출력 토큰의 40~60%를 형식 설명이 차지
- 타임스탬프 삽입: 캐시 키 무효화로 캐시 히트율 0%
히스토리 관리, 왜 이게 비용 폭탄이 되는가
멀티턴 대화의 비용이 왜 기하급수적으로 오르는지 많은 분들이 직관적으로 이해하지 못합니다. Claude를 포함한 대부분의 LLM은 스테이트리스(stateless) 모델입니다. 스테이트리스란 모델 자체가 이전 대화를 기억하지 않는다는 뜻으로, 매 API 호출마다 전체 대화 이력을 통째로 재전송해야 합니다. 서버가 아니라 클라이언트가 상태를 들고 다니는 구조입니다.
이 구조를 이해하지 못한 채 무한정 히스토리를 쌓으면 어떻게 될까요. 제가 진단한 고객 지원 봇이 딱 그 케이스였습니다. 50턴 대화 기준으로 마지막 메시지의 입력 토큰이 첫 번째 메시지의 50배에 달했고, 월 비용의 68%가 히스토리 재전송 비용이었습니다. 실제로 필요한 최근 컨텍스트는 500
1,000 토큰이면 충분한 경우가 대부분인데, 20턴 대화가 5,000
10,000 토큰을 소비하고 있었던 겁니다.
히스토리 관리 전략에 대해서는 의견이 나뉩니다. "히스토리를 요약해서 압축하면 충분하다"는 시각도 있고, "슬라이딩 윈도우 방식으로 최근 N턴만 유지하는 게 더 단순하고 효과적"이라는 시각도 있습니다. 저는 후자 쪽입니다. 요약 자체가 추가 API 호출을 발생시키고, 요약 품질이 일정하지 않으면 컨텍스트 손실로 이어지는 경우를 실제로 겪었기 때문입니다. 태스크 특성에 따라 다르겠지만, 제 경험상 단순한 슬라이딩 윈도우가 운영 부담이 훨씬 적었습니다.
캐싱 전략, 단일 최적화 중 ROI가 가장 높다
캐싱 이야기를 꺼내면 "어차피 LLM API가 알아서 해주는 거 아닌가요?"라고 묻는 분들이 종종 있는데, 그렇지 않습니다. 프롬프트 캐싱(Prompt Caching)이란 동일한 시스템 프롬프트가 반복 전송될 때 첫 호출에서만 전체 처리 비용을 내고, 이후 호출에서는 캐시된 버전을 재사용해 비용을 대폭 낮추는 기능을 말합니다. Anthropic Claude API 기준으로 캐시 쓰기는 표준 입력 토큰 비용의 1.25배, 캐시 읽기는 0.1배가 적용됩니다(출처: Anthropic).
계산해보면 효과가 분명해집니다. 5,000 토큰짜리 시스템 프롬프트를 하루 1,000번 전송한다고 가정할 때, 캐싱 없이는 하루 500만 토큰의 입력 비용이 발생합니다. 캐싱 적용 후에는 첫 호출만 1.25배 비용을 내고 나머지 999번은 0.1배로 처리되니, 이론적으로 하루 비용이 약 90% 감소합니다. 제가 직접 적용해본 사례에서도 단일 최적화로는 가장 ROI가 높았습니다.
단, 앞서 언급한 타임스탬프 삽입 문제처럼 캐시를 무력화하는 패턴에는 항상 주의해야 합니다. 동적으로 바뀌는 값이 시스템 프롬프트 안에 있으면 캐시 키가 매번 달라지고 캐시 히트가 발생하지 않습니다. 해결책은 간단합니다. 날짜나 사용자 정보처럼 동적으로 변하는 요소는 시스템 프롬프트 밖으로 분리해서 사용자 메시지나 별도 컨텍스트 블록에 담으면 됩니다. 실무에서 캐싱 도입 후 비용이 줄지 않는다는 피드백을 받은 케이스의 상당수가 이 패턴 때문이었습니다.
LLM 토큰 최적화 가이드에서도 프롬프트 캐싱을 단일 최적화 항목 중 가장 먼저 적용할 것으로 권장하고 있으며, 반복적인 시스템 프롬프트 구조를 가진 서비스라면 캐싱 없이 운영하는 것이 오히려 이상한 상황이 됐습니다(출처: Redis Blog).
결국 토큰 낭비 문제는 기술적 무지의 문제가 아닌 경우가 많습니다. "일단 동작하게 만들자"는 초기 개발 문화가 그대로 굳어버리는 것이 근본 원인입니다. 제품이 성장하면 비용도 같이 성장하는데, 최적화 없이 쌓인 기술 부채는 나중에 리팩터링하기가 훨씬 어렵습니다. 현실적인 출발점은 거창한 리팩터링이 아니라, PR 리뷰 체크리스트에 "이 프롬프트의 토큰 수는 측정되었는가?" 한 줄을 추가하는 것입니다. 작은 문화적 변화가 비용 구조를 바꿉니다.
참고:
- LLM Token Optimization (Redis Blog, 2026): https://redis.io/blog/llm-token-optimization-speed-up-apps/
- Stop Burning Tokens - Developer Guide (Level Up Coding, 2026): https://levelup.gitconnected.com/stop-burning-tokens
- Stop Wasting LLM Tokens (Substack, 2025): https://shmulc.substack.com/p/stop-wasting-tokens
- Common Prompt Engineering Mistakes (Reintech, 2026): https://reintech.io/blog/common-prompt-engineering-mistakes