질문 8개를 캐시할 때 Redis를 도입하지 않은 이유

왜 이 주제가 중요한가요?

문서 질의응답 기능은 검색과 답변 생성을 함께 수행하기 때문에 일반 API보다 처리 비용이 큽니다. 그런데 모바일 화면에서 제공하는 질문이 미리 준비된 여덟 개로 고정되어 있다면 캐시 대상은 매우 작아집니다.

이런 상황에서 “캐시라면 Redis”라고 바로 결론을 내리기 쉽습니다. 하지만 저장할 데이터가 적고 기존 PostgreSQL을 이미 운영하고 있다면, 별도 캐시 서버를 추가하는 것이 오히려 더 큰 운영 비용을 만들 수 있습니다.

이 글에서는 캐시 저장소를 속도만으로 고르지 않고 데이터의 수명과 운영 복잡도로 판단한 과정을 정리합니다.

저장소를 고르는 질문은 “가장 빠른가요?”가 아닙니다

후보는 세 가지입니다.

선택지 | 재시작 후 유지 | 추가 운영 요소 | 적합한 상황
인메모리 Map | 유지되지 않음 | 없음 | 사라져도 되는 짧은 캐시
PostgreSQL 테이블 | 유지됨 | 기존 DB 활용 | 작은 영속 캐시와 조건부 무효화
Redis | 설정에 따라 유지 | 서버·볼륨·모니터링 | 높은 처리량, 공유 상태, 원자적 연산

Redis의 대표적인 장점은 TTL, 빠른 원자적 연산과 여러 프로세스 사이의 상태 공유입니다. 그러나 고정된 질문 여덟 개의 답변을 저장하는 상황에서는 이 장점이 반드시 필요하지 않았습니다.

만료 시각은 PostgreSQL의 생성·수정 시각으로 판단할 수 있습니다.

요청량이 많지 않다면 DB 읽기가 병목이 되지 않습니다.

단일 애플리케이션에서 시작한다면 분산 상태 공유가 필요하지 않습니다.

저장할 키가 적으므로 별도 메모리 저장소의 이점이 제한적입니다.

기술의 장점이 존재하는 것과 현재 문제에 그 장점이 필요한 것은 다른 이야기입니다.

인메모리 캐시는 왜 제외했나요?

인메모리 Map은 가장 단순하지만 애플리케이션을 재시작하면 내용이 사라집니다. 캐시 누락 시 생성 비용이 크고, 배포 직후 같은 답변을 다시 만드는 일을 피하려면 캐시가 재시작 후에도 유지되어야 합니다.

또한 애플리케이션 인스턴스가 여러 개가 되면 각 프로세스가 서로 다른 Map을 갖습니다. 현재는 단일 인스턴스라도 확장 가능성을 고려해 저장소 접근을 추상화하는 편이 좋습니다.

그래서 작은 영속 캐시에는 이미 운영 중인 PostgreSQL을 사용하기로 했습니다.

cache_key
answer
sources
model_version
prompt_version
created_at

질문 해시를 키로 사용하고 답변, 근거, 모델·프롬프트 버전과 생성 시각을 함께 저장하면 새 인프라 없이 필요한 조건을 충족할 수 있습니다.

캐시의 핵심은 저장보다 무효화입니다

캐시를 만드는 일보다 언제 버릴지를 정하는 일이 더 어렵습니다.

단순히 “한 시간 뒤 만료”로 정하면 두 가지 문제가 생깁니다.

근거 문서가 방금 수정됐는데도 이전 답변이 남을 수 있습니다.

아무 문서도 바뀌지 않았는데 주기적으로 비싼 답변을 다시 생성합니다.

RAG 답변의 수명은 시간보다 근거 문서의 변경과 연결됩니다. 따라서 다음 사건에서 관련 캐시를 무효화하는 편이 정확합니다.

문서가 발행·수정·삭제되었습니다.

문서의 공개 범위가 바뀌었습니다.

임베딩이나 검색 설정이 바뀌었습니다.

답변 모델 또는 시스템 프롬프트가 바뀌었습니다.

모든 사건을 추적하기 어렵다면 시간 기반 TTL을 보조 안전장치로 함께 사용할 수 있습니다.

캐시 키에 질문만 넣으면 부족합니다

같은 질문이라도 모델, 프롬프트와 검색 색인이 바뀌면 답변이 달라져야 합니다.

cache_key = hash(
normalized_question,
model_version,
prompt_version,
index_version,
visibility_scope
)

특히 공개·내부 문서 범위가 다른 시스템에서는 권한 범위를 캐시 키에 포함하거나 캐시 저장소 자체를 분리해야 합니다. 내부 근거로 생성된 답변이 공개 요청에 재사용되어서는 안 됩니다.

나중에 Redis로 바꿀 수 있게 만듭니다

PostgreSQL을 선택했다고 해서 영구적으로 고정할 필요는 없습니다. 애플리케이션에는 다음과 같은 작은 인터페이스만 노출합니다.

interface AnswerCache {
get(key: string): Promise<CachedAnswer | null>;
set(key: string, value: CachedAnswer): Promise<void>;
invalidate(scope: CacheScope): Promise<void>;
}

현재 구현은 PostgreSQL을 사용하고, 처리량이나 분산 요구가 커지는 시점에 Redis 구현으로 교체할 수 있습니다. 중요한 것은 미래의 가능성 때문에 현재 필요하지 않은 운영 요소를 미리 추가하지 않는 것입니다.

Redis가 적합해지는 시점

다음 조건이 나타나면 Redis를 다시 검토할 이유가 충분합니다.

매우 많은 키를 높은 빈도로 읽고 씁니다.

대량의 키가 짧은 TTL로 만료됩니다.

여러 애플리케이션 인스턴스가 상태를 공유합니다.

분산 락, 큐와 원자적 카운터가 필요합니다.

PostgreSQL 캐시 읽기가 실제 병목으로 측정됩니다.

“나중에 필요할 것 같습니다”보다 측정된 처리량과 운영 요구가 도입 근거가 되어야 합니다.

캐시가 UX를 바꿀 수 있습니다

생성 답변이 한 글자씩 표시되는 화면에서 캐시가 적중하면 완성된 문장이 즉시 나타납니다. 성능은 좋아졌지만 사용자는 서로 다른 동작으로 느낄 수 있습니다.

다음 중 어떤 경험을 제공할지 미리 정해야 합니다.

캐시 답변도 짧게 스트리밍해 일관된 표현을 유지합니다.

즉시 보여주고 “저장된 답변”임을 표시합니다.

답변은 즉시 표시하고 근거 확인 UI만 순차적으로 엽니다.

캐시는 백엔드 최적화처럼 보이지만 사용자 경험과 분석 이벤트에도 영향을 줍니다.

선택 체크리스트

캐시가 재시작 후에도 유지되어야 하나요?

여러 프로세스가 같은 상태를 공유하나요?

키 개수와 초당 요청량을 측정했나요?

TTL이 필요한가요, 사건 기반 무효화가 필요한가요?

모델·프롬프트·색인 버전이 키에 포함되나요?

공개 범위가 다른 답변이 섞이지 않나요?

저장소를 나중에 교체할 수 있는 인터페이스가 있나요?

새 인프라의 백업·모니터링·장애 대응 비용을 계산했나요?

듀오랩스가 보는 관점

기술 선택에서는 가장 유명한 도구보다 현재 데이터가 요구하는 최소한의 저장소를 고르는 것이 중요합니다. 작은 영속 캐시는 기존 PostgreSQL로 충분할 수 있고, 높은 처리량과 분산 상태가 필요해지는 시점에는 Redis가 더 적합할 수 있습니다.

도입하지 않기로 한 결정도 되돌릴 수 있습니다. 얇은 인터페이스와 측정 지표를 준비해 두면 지금은 단순하게 시작하고, 필요가 확인됐을 때 안전하게 확장할 수 있습니다.