LLM 캐시 키가 요청의 절반을 누락합니다. TypeScript로 버전 관리형 캐시를 구축하세요.
요약
LLM 캐시 키가 요청의 일부 중요한 컨텍스트(시스템 프롬프트, 모델 스냅샷 등)를 누락하여 잘못된 결과를 반환할 위험이 있습니다. 이 글은 단순히 사용자 메시지뿐만 아니라 여러 의존성을 포함하는 버전 관리형 캐싱 전략을 TypeScript 코드로 구현하는 방법을 제시합니다.
핵심 포인트
- LLM 응답은 단순한 입력 텍스트 외에 다양한 스코프(Scope)에 의존한다.
- 캐시 키는 모델 이름, 시스템 프롬프트, 출력 스키마 등 모든 의존성을 포함해야 한다.
- 버전 관리형 캐시는 요청의 신뢰할 수 있는 컨트랙트를 정의하여 정확도를 높인다.
- TypeScript를 활용해 16가지 케이스를 검증하는 테스트 코드를 제공한다.
LLM 캐시 키가 요청의 절반을 누락합니다. TypeScript로 버전 관리형 캐시를 구축하세요.
가장 빠른 모델 호출은 건너뛰는 것입니다. 이것은 또한 답변을 안전하게 만들었던 경계를 무시하는 좋은 방법이기도 합니다.
앨리스(Alice)가 “제 환불 한도는 얼마인가요?”라고 묻고, 밥(Bob)도 똑같은 것을 묻습니다. 캐시는 동일한 텍스트를 보고 앨리스의 답변을 밥에게 반환합니다.
예외는 없습니다. 실패한 요청도 없습니다. 캐시 히트입니다.
저는 이러한 실수를 눈에 보이게 만드는 작은 TypeScript 테스트 코드를 만들었고, 그 다음 결과를 요청과 신뢰할 수 있는 스코프(scope)로 키를 지정했습니다. 이 코드는 로컬에서 실행되며 API 키가 필요 없고 16가지 케이스를 확인합니다. 환불 값은 가상의 것입니다. 실패는 의도적으로 구성된 것입니다.

프롬프트는 단지 하나의 입력일 뿐입니다
결과는 마지막 사용자 메시지보다 더 많은 것에 의존합니다. 시스템 프롬프트를 변경하거나, 모델 스냅샷(model snapshot), 출력 스키마(output schema), 검색된 지식(retrieved knowledge), 또는 디코딩 설정(decoding settings)을 변경하면 동일한 텍스트가 다른 요청을 의미할 수 있습니다.
신원(Identity) 역시 중요합니다. 사용자를 넘나드는 캐시는 재사용을 정보 유출로 바꿀 수 있습니다. RFC 9111은 승인된 응답의 공유 HTTP 캐싱에 명시적인 제한을 두고 있습니다. 이 빌드는 해당 RFC를 구현한 것이 아니라 애플리케이션 레벨의 LLM 캐시입니다. 제가 여기서 얻는 설계 교훈은 간단합니다. 재사용에는 단순히 문자열뿐만 아니라 스코프가 필요하다는 것입니다.
이 테스트 코드는 다음을 포함하는 고정 위치 튜플(fixed-position tuple)을 만듭니다:
- 키 형식 버전(key-format version), 테넌트(tenant), 주체(principal), 및 접근 개정판(access revision).
- 모델 이름과 스냅샷 개정판, 전체 시스템 텍스트, 그리고 순서가 지정된 텍스트 메시지.
- 정확한 스키마 텍스트, 도구 세트 개정판(toolset revision), 및 지식 개정판(knowledge revision).
- Temperature, top-p, 최대 출력 토큰(maximum output tokens), 및 시드(seed).
빌드 실행하기
공개 소스는 bobbyhalljr/llm-cache-key입니다. 여기에는 소스 코드, 모든 16가지 검사 항목, 이미지 및 정확한 출력이 포함되어 있습니다.
Node.js 22.18.0 이상을 사용하세요. **Node.js 22.20.0 (2026년 10월 9일)**에서 테스트되었습니다. 설치해야 할 npm 종속성은 없습니다.
git clone https://github.com/bobbyhalljr/llm-cache-key.git
cd llm-cache-key
npm test
또는 단일 파일을 실행하세요:
node --experimental-strip-types cache.ts
Node의 내장 로더는 이 파일에서 제거 가능한 TypeScript 구문을 제거합니다. 이는 타입 검사를 수행하지 않습니다. Node의 버전별 문서에 경계가 설명되어 있습니다.
입력 검사 후 핵심 키 구성은 다음과 같습니다. 저장소의 전체 파일에는 해당 검사와 캐시 구현이 포함되어 있습니다.
const tuple = ['llm-result-cache-v1', scope.tenant, scope.principal, scope.accessRevision,
req.model, req.modelRevision, req.system,
req.messages.map(m => [m.role, m.content]), req.schema,
...
고정된 튜플은 구분자 문제를 방지합니다. 필드를 :로 결합할 경우, 값 a:b에 c를 더하고 a에 b:c를 더하는 것이 동일한 연결된 텍스트를 생성할 수 있기 때문입니다. JSON은 경계를 유지합니다.
이것은 임의의 객체를 정규화하지 않습니다. 메시지 순서는 보존됩니다. 스키마는 정확한 문자열입니다. 동등한 스키마를 재포맷하면 보수적인 Miss가 발생합니다. 이는 이 교육용 빌드에 있어 허용 가능한 트레이드오프입니다.
Node의 createHash는 SHA-256 다이제스트를 제공합니다. 해시는 키를 압축합니다. 답변을 암호화하거나, 사용자를 인증하거나, 누락된 입력을 나타내지는 않습니다.
정확한 출력
npm test 스크립트는 동일한 로컬 데모와 검증을 실행합니다:
prompt-only | beta/bob | ACME_LIMIT=500
same-request | ACME_LIMIT=500
different-tenant | MISS
...
첫 번째 줄은 잘못된 설계입니다. 마지막 사용자 메시지 단독이 키이기 때문에, Bob은 Alice의 ACME_LIMIT=500 값을 받습니다. 이 데모는 자체적인 검증을 가지고 있지만 16개의 보호된 검사 중에는 포함되지 않습니다.
보호된 캐시는 동일한 요청에 대해 동일한 값을 반환합니다. 테넌트, 주체(principal), 또는 접근 개정판(access revision)이 변경되면 누락됩니다. 프롬프트, 스냅샷, 스키마, 도구 세트(toolset), 지식(knowledge), 또는 온도(temperature)가 변경되어도 누락됩니다. 메시지 순서 검사는 메시지를 역순으로 배열하면 다른 키를 생성하므로 false를 출력합니다.
범위(scope)와 NaN 디코딩 설정이 누락되면 조회 전에 오류가 발생합니다. 타입 주석만으로는 이러한 값을 런타임에 거부할 수 없습니다.
TTL은 접근 권한을 취소하지 않습니다
캐시는 시간 0에 1,000ms의 TTL로 작성됩니다. 999ms에서 도달(hit)하고 정확히 1,000ms에서 누락(miss)합니다. 이것들은 지연 시간 측정이 아니라 주입된 테스트용 시간입니다.
권한 변경은 그 타이머를 기다릴 수 없습니다. 1ms 만에 acl-7에서 acl-8로 전환해도 접근 개정판이 키의 일부이기 때문에 이미 누락됩니다.
이것은 애플리케이션이 읽기(read)할 때마다 새로운 개정판을 제공하는 경우에만 작동합니다. 오래된 개정판을 재사용하면 이전 키가 재사용됩니다. 프로덕션 환경에서는 읽기를 독립적으로 승인한 다음, 유효 접근 상태와 데이터 버전으로부터 키를 계산해야 합니다. 오래된 항목들은 정리될 때까지 저장소에 남아 있을 수 있지만, 변경된 키는 이 경로에서 그 재사용을 막을 뿐, 보존 자체를 막지는 않습니다.
캐시 히트(A cache hit)는 정책 결정입니다
이 피처는 cacheable 속성을 노출합니다. 이 값이 false인 경우, 읽기(read)와 쓰기(write) 모두 결과를 재사용하지 않습니다. 결과가 재사용될 수 있는 작업에 대해서는 명시적인 허용 목록(allowlist)을 사용하세요. 캐싱된 도구 성공(tool success)을 쓰기, 결제, 알림 또는 기타 부작용(side effect)을 건너뛰어도 되는 권한으로 간주하지 마세요.
확률적 답변(stochastic answer)을 캐싱하는 것 역시 제품 동작을 변경시킵니다. 설정이 동일하더라도 다음 모델 호출은 다른 답변을 생성할 수 있습니다. 첫 번째 답변을 고정하는 것이 이 작업에 적합한지 결정해야 합니다.
정직한 경계 (The honest boundary)
이것은 합성 요청(synthetic requests)과 가짜 정책 값(fake policy values)을 가진 로컬 Map입니다. 모델 호출을 수행하지 않으며, 비용이나 속도를 측정하지 않고, 어떤 프로덕션 보안 속성도 증명하지 않습니다. Redis 통합, 인증 서비스, 동시 쓰기 작성자(concurrent writer), 분산 무효화(distributed invalidation), 암호화, 크기 제한, 만료 정책(eviction policy) 또는 실시간 폐지 테스트가 없습니다.
지원되는 요청은 의도적으로 좁습니다: 텍스트 메시지와 고정된 제어 항목 세트입니다. 이미지를 추가하거나, 도구 인자(tool arguments), 검색 필터(retrieval filters), 로케일(locale), 새로운 샘플링 제어(sampling control) 또는 다른 출력에 영향을 주는 필드를 추가하면 키 계약(key contract)이 변경되어야 합니다. 알 수 없는 추가 필드는 이 피처에서 모델링되지 않습니다. 조용히 스냅샷을 변경하는 프로바이더 별칭(provider alias)도 사용자가 통제할 수 있는 개정이 필요합니다. 업데이트하지 않는 레이블은 무효화가 아닙니다.
프롬프트와 답변 저장을 민감한 데이터로 취급하세요. SHA-256 키는 예측 가능한 프롬프트를 익명화하거나 캐시된 값을 보호하지 못합니다. 또한 이 코드는 호출자로부터 전달되는 시계(clock)를 신뢰합니다. 프로덕션 캐시는 신뢰할 수 있는 시간 소스(time source)와 제한된 보존 기간을 필요로 합니다.
유용한 검토 질문은 다음과 같습니다: 이 키를 변경하지 않고 승인된 답변을 바꿀 수 있는 것은 무엇인가?
저는 실제 작업을 수행하는 AI 직원, Roster를 구축하고 있습니다. 더 빠른 작업이라도 올바른 신원(identity), 올바른 계약(contract) 그리고 결과를 재사용할 권한이 필요합니다.
주요 참고 자료 (Primary references)
- RFC 9111, 권한 부여된 응답의 공유 캐싱 (shared caching of authorized responses), 2022년 6월. LLM-캐시 준수 여부에 대한 주장이 아닌 설계 비유로 사용됨.
- Node.js 22.20.0 TypeScript 로더, 타입 검사 부재 포함.
- Node.js 22.20.0
crypto.createHash.} }{
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
