캐싱 — 같은 질문을 두 번 계산하지 않는다
무엇을
임베딩·검색 결과에 LRU 캐시를 붙인다(8주 마일스톤 7주차 「운영 관점」).
왜 여기가 이득인가
같은 절차 문의가 반복된다 — 다산 서류 문의는 69종 서비스에 흩어져 있지만
상위 질문은 몰린다(decisions/201). 임베딩 한 번이 검색 한 번보다 비싸다.
⚠ 캐시에 무엇을 담는지 먼저 본다
마스킹 전 원문을 캐시 키로 쓰지 않는다. SEC-1 은 「마스킹 전 원문이 DB·로그 어디에도 남지 않음」이고, 메모리 캐시도 어디에 해당한다. 키는 마스킹 완료본이거나 그 해시여야 한다.
완료 조건
- 캐시 적중률과 적중/미적중 지연 차이를 잰다 — 적중률만으로는 이득을 말할 수 없다
- 캐시를 끈 값과 켠 값을 나란히 적는다. 품질 지표(Recall@5·MRR)가 변하지 않는 것을 확인한다 — 변하면 캐시가 결과를 바꾸고 있다는 뜻이고 그건 버그다
- 키에 마스킹 전 원문이 들어가지 않는 것을 테스트가 고정한다(SEC-1)
- 지식베이스를 재적재하면 캐시가 무효화된다(안 그러면 옛 조항을 계속 돌려준다)
2026-09-15 — 검색 결과 캐시를 붙였다. 적중률은 운영 트래픽이 없어 못 잰다
무엇을 캐시하나
임베딩이 아니라 검색 결과 전체다(ai/apps/retrieval/adapter/outbound/cached_retriever.py). 티켓은 「임베딩 한 번이 검색 한 번보다 비싸다」고 적었지만,
decisions/206 구성에서 비싼 것은 리랭킹이다(dense p95 59ms → +리랭킹 521ms, CPU). 임베딩만 캐시하면 리랭킹이 그대로 남는다.
- 키 =
sha256(인덱스 세대, top_k, 발화)— 원문이 키에 없다. 테스트가 키에 발화·이름이 없는 것을 고정한다(SEC-1) - 무효화 — 인덱스 UUID 를 세대로 쓴다(
--recreate재적재에서 바뀐다). 세대 조회를 못 하면 캐시를 믿지 않고 비운다. ⚠--recreate없이 덮어쓴 재적재는 UUID 가 같아 못 알아챈다 — 그래서 최대 수명 600초를 함께 둔다 - 어디에 붙였나 —
ai/provider.pybuild_model_retriever(임베딩 검색이 켜졌을 때만). BM25 단독(지금 운영)에는 안 붙였다 — p95 4ms 라 이득이 없고 오래된 결과 위험만 는다 - 순수 LRU 는
retrieval/domain/services/lru.py(시계 주입, TTL). 런북 「만들지 말 것」대로 인메모리다
실측
측정일 2026-09-15 · 커밋 38f2fc3-dirty · 골든셋 v1-150 B 96건 · 검색 dense + rerank · 로컬 MPS
ELASTICSEARCH_URL=http://localhost:9200 .venv/bin/python scripts/measure_cache.py --device mps --check-reindex
Recall@5 MRR p50 p95
캐시 없음 0.979 0.919 152.0ms 238.3ms
캐시 1회차(미적중) 0.979 0.919 157.2ms 237.6ms ← 미적중 비용 ≈ 5ms(키 계산·저장)
캐시 2회차(적중) 0.979 0.919 1.3ms 2.1ms
세 경우 상위 5 순위가 96문항 모두 같다: True
--recreate 재적재 뒤: 캐시 96건 → 1건 · 무효화 1회
⚠ 적중률은 측정 불가다
2회차 적중률 100% 는 같은 질문을 두 번 넣었기 때문이다 — 적중률이 아니다. 운영 트래픽이 없어 실제 적중률을 잴 수 없다. 참고로 AI Hub 다산 실제 고객 질문 6,697건에서 글자까지 같은 문장의 반복은 12.1%(고유 5,884)였다. 그런데 실시간 STT 전사는 같은 뜻이라도 띄어쓰기·어미가 달라 키가 달라지므로 이 12% 를 기대 적중률로 옮기지 않는다 — 실제는 더 낮을 가능성이 크다. 캐시가 이득인지는 운영 로그로 판단한다.