grep

LLM 비용 64% 절감, 캐시 히트율 98% 달성기

무신사

2026년 7월 7일

원문에서 보기 ↗

메트릭 가시성 확보부터 Prompt Caching 도입까지

들어가며

안녕하세요, 29CM Pricing 팀에서 백엔드 개발을 맡고 있는 정다해입니다.

29CM에서는 상품 속성 추출, 그룹 평가, 고객 응대 등 여러 기능에서 AWS Bedrock 기반 LLM을 사용하고 있습니다. 운영 중 LLM 비용이 꾸준히 증가하는 추세가 관찰되어 비용 구조 분석을 시작했습니다.

분석을 시작하자마자 첫 번째 장애물에 부딪혔습니다. AWS Bedrock 콘솔은 일별 토큰·비용 합계는 보여주지만, 어떤 API가 얼마만큼의 토큰을 사용하는지는 API별로 분리해 알려주지 않습니다. 비용은 보이는데 원인이 보이지 않는 상태에서는 어디부터 줄여야 할지 결정할 수 없었습니다.

이 글은 LLM 비용 절감을 위해 메트릭 가시성을 먼저 만들고, 비용 발생 원인을 파악한 뒤, Prompt Caching을 도입한 과정을 정리한 기록입니다. 모든 최적화가 끝난 뒤 결과는 이렇습니다.

LLM 서비스의 비용은 늘어나는데 어디부터 손대야 할지 막막한 엔지니어에게 이 글이 작은 도움이 되었으면 합니다.

1. 문제 정의 --- 비용은 보이는데 원인이 보이지 않는다

LLM 비용이 늘고 있다는 걸 처음 알아챘을 때, 우리에게 있는 정보는 두 가지였습니다.

이 정보로는 아래의 핵심 질문에 답할 수 없었습니다.

따라서 어떤 부분부터 최적화해야 가장 큰 비용 절감 효과를 볼 수 있을지도 판단할 수 없었습니다. 첫 번째 문제는 비용이 아니라 가시성이었습니다. 비용을 줄이려면 가시성부터 마련해야 했습니다.

2. 첫 번째 작업 --- API별 토큰 메트릭 대시보드 구축

비용 최적화의 출발점은 가시성 확보였습니다. Bedrock 호출에서 다음 정보를 API별로 추적하도록 메트릭 파이프라인을 만들었습니다.

수집은 LangChain4j의 ChatModelListener로 구현했습니다. 구조는 세 조각입니다.

리스너 구현체의 핵심 로직만 추리면 다음과 같습니다.

class MetricsChatModelListener(
    private val registry: MeterRegistry,
) : ChatModelListener {
    override fun onResponse(ctx: ChatModelResponseContext) {
        val endpoint = LlmEndpointContext.current() ?: "other"   // 서블릿 필터가 채워둔 값
        val model = ctx.chatResponse().metadata().modelName()
        val usage = ctx.chatResponse().metadata().tokenUsage()
        fun recordTokens(type: String, count: Int) =
            registry.counter(
                "llm_tokens_total",
                "llm_endpoint", endpoint,
                "llm_model", model,
                "type", type,
            ).increment(count.toDouble())
        recordTokens("input", usage.inputTokenCount())
        recordTokens("output", usage.outputTokenCount())
        // cache_read / cache_write 토큰도 같은 방식으로 기록
    }
}

이렇게 수집한 메트릭을 Grafana 대시보드로 시각화했습니다. 이 대시보드가 켜진 시점부터 비로소 다음 단계, 즉 "어떤 API가 비용의 대부분을 만드는가"를 분석할 수 있었습니다.

3. 진짜 원인 발견 --- 한 API가 토큰의 대부분을 만들고 있었다

대시보드에서 endpoint별 토큰 사용량 패널을 펼친 순간, 답은 한눈에 보였습니다.

속성 추출 단일 API가 전체 LLM 토큰의 약 92%를 차지하고 있었습니다. 나머지 endpoint들은 다 합쳐도 10%가 안 돼 사실상 노이즈였습니다.

그러면 자연스레 묻게 됩니다.

이 API는 무엇이고, 왜 이렇게 토큰을 많이 쓰는가?

4. 원인 본질 분석 --- 같은 시스템 프롬프트 15K를 매번 다시 보내고 있었다

속성추출 API는 상품에서 색상/사이즈 같은 속성을 추출하는 배치에서 호출하는 API입니다. 호출 패턴은 이렇습니다.

문제는 호출 빈도가 아니라 요청 구조에 있었습니다.

요청 = [시스템 프롬프트 약 15K 토큰] + [상품 100개 약 2K 토큰]

시스템 프롬프트는 속성 추출 규칙, 예시, 금지 항목, 출력 JSON 스키마를 담은 긴 문서로 약 15,000 토큰입니다. 변동되는 상품 데이터(2K)에 비해 7~8배 큽니다.

문제는 이 속성 추출 API가 호출될 때마다 매번 똑같은 15K 시스템 프롬프트를 처음부터 다시 보낸다는 점이었습니다. 뒤에 붙는 상품 데이터(2K)만 바뀔 뿐 앞의 15K는 모든 요청에서 동일한데, 모델은 매 호출마다 그 15K를 처음부터 다시 처리하고 있었습니다. 정리하면 이 API가 토큰의 대부분을 만든 건 프롬프트가 유독 커서가 아니라, 동일한 15K 프롬프트를 반복 사용하는 고빈도 호출이 압도적으로 많았기 때문입니다.

5. 해결 방법 검토 --- AI 기반 비용 시뮬레이션으로 의사결정

원인이 명확해졌으니 이어서 묻게 되는 것은 **"어떤 옵션을 어떤 순서로 적용할 것인가"**입니다. LLM 비용 최적화의 일반적인 옵션은 셋입니다.

5.1 AI 기반 비용 시뮬레이션 도입

세 옵션을 직관에 의지해 결정하는 대신, 레포지토리 소스와 사용 모델의 가격표를 입력으로 LLM에 비용 시뮬레이션을 수행했습니다. 다만 역할은 분명히 분리했습니다. 호출 패턴을 코드에서 뽑아내고 시나리오를 구성하는 일까지만 LLM 에 맡기고, 최종 금액 계산은 LLM에 위임하지 않았습니다. 최종 비용은 Grafana 대시보드로 집계한 실제 input/output 토큰량에 모델별 단가를 곱해 산출하고, AWS Bedrock 콘솔 수치로 한 번 더 대조했습니다. LLM에 산술까지 맡기면 그럴듯한 숫자를 지어낼 수 있어, 계산은 결정론적인 경로에 두는 편이 안전합니다. 시뮬레이션의 입력과 출력은 다음과 같습니다.

입력

출력

시뮬레이션 결과 Prompt Caching이 1순위라고 판단했습니다. 근거는 이렇습니다.

이 시뮬레이션은 단순히 "어떤 옵션이 좋아 보이는가"를 직관으로 결정하지 않게 해 줍니다. 이후 실측 결과와 비교하면 가격 모델 자체의 신뢰성을 점검하는 도구로도 쓰입니다.

6. 적용 --- 프롬프트 구조 분리와 캐시 포인트 설정

Prompt Caching 적용은 단순히 캐시 포인트 한 줄을 추가하는 일이 아닙니다. 캐싱이 효과를 내려면 프롬프트가 캐싱에 적합한 구조로 분리되어 있어야 합니다.

6.1 사전 작업 --- 시스템 프롬프트와 메시지 프롬프트 분리

Prompt Caching은 prefix matching 방식으로 동작합니다.캐시는 요청의 앞부분부터 해시(Hash)를 계산해 일치하는 구간까지만 재사용하므로, 다음 두 가지 구조적 조건이 필수적입니다.

기존 코드는 이 구분이 명확하지 않은 상태였습니다. 캐싱 적용 전에 다음 작업을 먼저 수행했습니다.

이 구조 분리가 끝난 뒤에야 캐시 포인트가 비로소 작동합니다.

6.2 Bedrock Converse API에 캐시 포인트 추가

// 시스템 프롬프트 블록 뒤에 캐시 마커(CachePointBlock)를 붙인다.
val cachePoint = SystemContentBlock.builder()
    .cachePoint(
        CachePointBlock.builder()
            .type(CachePointType.DEFAULT)   // 기본 캐시 타입
            .ttl(CacheTTL.VALUE_1_H)        // 1시간 TTL
            .build()
    )
    .build()
// 고정 시스템 프롬프트 블록들 뒤에 캐시 포인트를 append
val system = systemPromptBlocks + cachePoint

핵심은 세 줄입니다.

Bedrock은 이 마커를 만나면 시스템 프롬프트 블록까지의 K, V를 캐시에 저장하고, 동일한 prefix를 가진 다음 요청이 도착하면 메모리에서 읽어옵니다. 이때 TTL은 캐시 히트가 발생할 때마다 다시 1시간으로 갱신됩니다. 즉 1시간 안에 같은 프롬프트로 호출이 이어지면 캐시가 그대로 유지되고, 1시간 동안 히트가 한 번도 없을 때만 만료됩니다(AWS Bedrock 문서 기준).

우리는 캐시 워밍과 배치 호출 패턴을 고려해 1시간 TTL이 이 워크로드에 적당하다고 판단하고 도입했습니다. (이 캐시는 우리 AWS 계정 내부에서만 재사용되고 다른 고객과 공유되지 않으며 시스템 프롬프트에도 개인정보 같은 민감정보가 없으므로, Prompt Caching을 켜더라도 프롬프트 내용이 외부로 노출될 우려는 없습니다.) 한 가지 덧붙이면, 위 CacheTTL.VALUE_1_H 옵션은 작업 당시 LangChain4j에는 존재하지 않았습니다. Bedrock 모듈의 캐시 포인트가 TTL 파라미터 없이 생성되도록 고정돼 있어 5분 기본값만 가능했고, 1시간 TTL을 쓰려면 라이브러리를 우회해 SDK를 직접 호출해야 했습니다. 우리 호출 패턴에 1시간 TTL을 적용해 보고자, BedrockChatRequestParameters에 cacheTtl 파라미터를 추가하는 PR #4920으로 기여해 보았습니다. 현재 main에 머지된 상태로 다음 릴리즈를 기다리고 있으며, 새 버전이 릴리즈되면 SDK를 직접 호출하던 우회 코드는 걷어낼 예정입니다.

7. 결과 --- 캐시 히트율 98%, 비용 64% 감소

적용 후 1주일간 메트릭 대시보드로 측정한 결과입니다. 참고로 여기서 64%는 특정 API가 아니라 전체 LLM 청구액 기준입니다.

앞서 3장에서 확인했듯 속성 추출 API가 전체 토큰의 약 92%를 차지하고 있었기 때문에, 이 단일 API의 입력 비용을 캐싱으로 방어하자 전체 비용 또한 연쇄적으로 감소했습니다.

캐시 히트율 98%면 사실상 모든 호출이 캐시를 타고 있다는 뜻입니다. 배치 시작 시점의 첫 호출(또는 캐시가 만료된 뒤 첫 호출)이 캐시를 적재하고(cache_write), TTL이 히트마다 갱신되는 동안 이어지는 나머지 호출들은 모두 메모리에서 읽기만 합니다(cache_read). 호출 패턴이 가격 모델과 잘 맞아떨어진 결과입니다.

시뮬레이션 vs 실측 검증

5절에서 수행한 AI 비용 시뮬레이션의 예측과 실측을 비교하면 이렇습니다.

여기서 캐시 히트율은 시뮬레이션에 미리 넣은 가정값이 아니라, 호출 패턴·가격표·토큰 분포만으로 산출된 예측치입니다. 그 예측과 실측이 1%p 차이로 일치한다는 것은, 가격 모델이 우리 호출 패턴 위에서 예측대로 작동한다는 뜻입니다. 모델 다운사이즈, Batch API 등 이어지는 단계의 시뮬레이션 결과도 같은 신뢰도로 활용할 수 있습니다.

8. Prompt Caching 활용 패턴

우리는 시스템 프롬프트 캐싱(패턴 ①)이 호출 패턴에 가장 잘 맞아 이 대안을 단독으로 적용했습니다. 다만 Prompt Caching에는 환경별로 활용 가능한 다양한 패턴이 있습니다. 외부 사례에서 반복적으로 등장하는 핵심 6가지 가운데 효과 차이를 가장 크게 만든 두 가지를 짚습니다.

Cache Warming 은 병렬 발사 전 워밍 콜 1회를 먼저 보내는 패턴입니다. 첫 워밍 콜이 캐시를 적재하는 동안 나머지 요청들이 동시 발사되면 write가 끝나기 전이라 모두 cache miss가 됩니다. 워밍 콜 1회의 비용은 작고 그 뒤로 모든 호출이 hit하기 때문에 트레이드오프가 큽니다. Thomson Reuters Labs 사례에서는 워밍 적용 전후로 한 자릿수에 머물던 캐시 히트율이 80%대로 올라갔다고 보고합니다.

Relocation Trick 은 시스템 프롬프트 안에 의도치 않게 들어간 동적 값(타임스탬프, 요청 ID, 사용자 ID 등)을 유저 메시지로 옮기는 한 줄짜리 변경입니다. ProjectDiscovery 사례처럼 한 자릿수에 머물던 캐시 히트율이 70%대로 한 번에 뛰는 경우가 있는데, 거의 항상 이 변경이 원인이었습니다. 앞 6.1절에서 우리가 수행한 시스템/메시지 분리 작업과 같은 원리입니다.

9. 캐시를 깨뜨리는 안티패턴

Prompt Caching은 강력한 도구지만, 캐시가 동작하지 않을 때 그 사실을 알기 어렵습니다. API는 에러를 던지지 않고 cache_read 값이 0으로 찍힐 뿐입니다. 메트릭 대시보드를 보고 있지 않으면 모르고 지나갑니다.

흔한 안티패턴은 다음과 같습니다.

원칙은 하나입니다. 변경 빈도가 낮은 것을 앞에, 높은 것을 뒤에 배치합니다. 앞이 바뀌면 뒤는 모두 무효화되기 때문입니다.

10. 다음 단계

이번 작업은 LLM 비용 최적화의 첫 번째 레버에 해당합니다. 그 뒤로 누적할 수 있는 레버들이 아직 남아 있습니다.

각 단계는 독립적이지만 효과는 누적됩니다. 단계별 예상 절감율을 누적하면, 같은 시뮬레이션 기준으로 Step 1~5를 모두 적용했을 때 90% 이상의 절감이 가능할 것으로 예상합니다.

11. 마치며 --- LLM 비용이 오르고 있다면, Prompt Caching부터 검토해 보세요

LLM을 운영하다 보면 비용 그래프는 거의 항상 우상향합니다. 모델을 바꾸거나 호출을 줄이는 큰 결정은 정확도 검증과 구조 변경이 따라붙어 쉽게 손대기 어렵습니다.

그래서 가장 먼저 시도해 볼 만한 레버가 Prompt Caching입니다. 정확도에 영향이 없고, 코드 변경이 작고, 동일한 시스템 프롬프트가 반복되는 호출 패턴이라면 효과가 거의 결정론적으로 따라옵니다. 우리 케이스에서는 캐시 히트율 98%, 비용 64% 절감이라는 결과로 이어졌습니다.

돌아보면 챙겨야 할 것은 네 가지로 정리됩니다.

  1. 가시성이 먼저입니다. API별 · 토큰 종류별로 분해해서 보지 않으면 어떤 API가 비용을 만드는지, 캐싱이 효과를 내고 있는지조차 판단할 수 없습니다. Bedrock 콘솔의 집계 수치만으로는 부족하고, 호출 단에서 메트릭을 직접 수집해야 합니다.
  2. Prompt Caching은 한 줄짜리 옵션이 아닙니다. 시스템 프롬프트(고정)와 메시지 프롬프트(변동)를 먼저 명확히 분리해야 캐시가 의미 있게 동작합니다. 동적 값이 시스템 프롬프트에 섞여 있으면 캐시는 매번 무효화됩니다.
  3. 캐시 히트율은 반드시 모니터링하세요. API는 캐시가 동작하지 않아도 에러를 던지지 않습니다. cache_read가 0으로 찍힐 뿐, 메트릭을 보지 않으면 모르고 지나갑니다.
  4. AI를 의사결정 도구로 활용해보세요. 코드베이스와 가격표를 입력으로 비용 시뮬레이션을 돌리면 어떤 옵션을 어떤 순서로 적용할지 직관이 아닌 근거로 결정할 수 있습니다. 실측과 비교하면 가격 모델 자체의 신뢰성도 함께 검증됩니다.

LLM 비용 최적화는 Prompt Caching에서 끝나지 않습니다. 호출 자체를 줄이는 캐싱, 모델 다운사이즈, Batch API 전환이 그 뒤에 누적 가능한 레버로 남아 있습니다. 하지만 가시성을 먼저 확보하고 Prompt Caching을 적용해 두면, 다음 레버들은 같은 메트릭 위에서 같은 방식으로 검증해 나갈 수 있습니다.

참고 자료

TEAM MUSINSA CAREER

무신사는 2001년 온라인 커뮤니티로 시작해 2005년 무신사 매거진, 2009년 무신사 스토어를 오픈하며 빠르게 성장하고 있는 국내 대표 온라인 패션 스토어입니다. '입점 브랜드와 동반성장'이라는 경영 철학을 바탕으로 브랜드가 안정적으로 사업을 전개할 수 있도록 무신사가 보유한 노하우와 인프라를 지원합니다. 고객에게는 풍성한 패션 콘텐츠와 패션에 특화된 차별화된 서비스로 최상의 온라인 쇼핑 경험을 제공하고 있습니다. 글로벌 №1 패션 기업으로 성장할 무신사와 함께 새로운 도전과 혁신을 만들 인재를 기다립니다. 29CM는 '고객의 더 나은 선택을 돕는다'라는 미션으로 출발했습니다. 우리는 우리만의 방식으로 콘텐츠를 제공하며, 브랜드와 고객 모두에게 대체 불가능한 커머스 플랫폼을 만들어가고 있습니다. 이 미션을 이루기 위해 우리는 흥미로우면서도 복잡한 문제들을 해결하고 있습니다. 만약 우리와 함께 이 문제들을 해결해 보고 싶다면, 주저하지 말고 29CM에 합류하세요! 🚀 팀 무신사 채용 페이지(무신사/29CM 전체 포지션 확인이 가능해요) 🚀 팀 무신사 테크 소식을 받아보는 링크드인 🚀 팀 무신사 뉴스룸


LLM 비용 64% 절감, 캐시 히트율 98% 달성기 was originally published in MUSINSA techblog --- 무신사 테크 블로그 on Medium, where people are continuing the conversation by highlighting and responding to this story.