백엔드 캐시 전략 설계: TTL보다 먼저 정할 무효화와 일관성 기준
“응답 속도는 빨라졌는데, 수정한 데이터가 바로 안 보이면 어떻게 해야 할까?”
“TTL을 짧게 두면 데이터베이스 부하가 다시 커지지 않을까?”
“Redis가 잠시 실패했을 때 API는 느려져도 되는 걸까, 틀린 값을 주면 안 되는 걸까?”
캐시 설계는 TTL 값부터 정하기보다 데이터가 얼마나 오래되어도 되는지와 원본 변경 시 어떤 경로가 어떤 캐시 키를 책임질지부터 정해야 합니다. 이 기준이 있어야 TTL을 일관성의 대체재가 아닌, 오래된 값의 체류 시간을 제한하는 장치로 쓸 수 있습니다.
캐시는 원본 데이터베이스를 대체하는 별도 원본이 아닙니다. 캐시와 원본이 서로 다른 시점의 값을 가질 수 있음을 전제로, 읽기별 최신성 요구와 장애 시 동작을 API 계약 안에서 정하는 것이 출발점입니다.
어떤 데이터부터 캐시해야 할까?
반복 조회 비용이 크고 짧은 시간의 오래된 값을 허용할 수 있는 읽기부터 캐시 후보로 분리하는 편이 좋습니다. 결제 상태·재고·권한처럼 최신값이 곧 의사결정인 데이터는 캐시 적용 전 불일치의 비용을 먼저 따져야 합니다.
조회 빈도만으로 캐시 대상을 정하면 부족합니다. 자주 조회되더라도 값이 수시로 바뀌고 불일치 한 번의 영향이 크다면, 무효화와 검증 비용이 캐시의 이점보다 커질 수 있습니다.
읽기 경로마다 다음 질문에 답해 보세요.
- 원본 조회가 실제 병목인가?
- 값이 몇 초 또는 몇 분 이전 상태여도 사용자 흐름이 유지되는가?
- 변경이 발생했을 때 영향받는 캐시 키를 식별할 수 있는가?
- 캐시가 비어 있거나 사용할 수 없을 때 원본 조회로 안전하게 돌아갈 수 있는가?
공개 콘텐츠 목록, 자주 바뀌지 않는 설정, 계산 비용이 큰 집계 결과는 검토 대상이 될 수 있습니다. 다만 목록에 사용자별 권한이나 최신 상태가 섞이면 키와 무효화 범위도 함께 복잡해집니다. 사용자 식별자, 권한, 언어, 필터, 페이지처럼 응답을 바꾸는 입력이 키에 반영돼야 하는지 확인하세요.
캐시 무효화는 언제, 누가 책임져야 할까?
원본을 변경하는 쓰기 경로가 관련 캐시의 삭제 또는 갱신 책임을 가져야 합니다. TTL 만료만 기다린다면, 변경 직후 오래된 응답을 허용한다는 뜻이므로 그 범위를 명시해야 합니다.
캐시 어사이드(cache-aside)는 캐시를 먼저 조회하고, 미스가 나면 원본 저장소에서 읽어 캐시에 저장한 뒤 호출자에게 반환하는 방식입니다. 원본을 변경한 뒤 관련 캐시 항목을 무효화하고 다음 읽기에서 다시 적재할 수 있지만, 이 패턴 자체가 원본과 캐시의 일관성을 보장하지는 않습니다. 외부 프로세스가 원본을 바꾸는 경우에도 별도 처리 없이는 이전 값이 남을 수 있습니다. 출처: Cache-Aside 패턴 - Azure Architecture Center
| 변경 상황 | 우선 검토할 동작 | 설계에서 확인할 점 |
|---|---|---|
| 변경 직후 최신값이 중요함 | 관련 키 삭제 또는 갱신 | 영향받는 키를 식별할 수 있는가 |
| 짧은 지연을 허용함 | TTL 만료까지 유지 | 허용 가능한 오래됨이 합의됐는가 |
| 여러 조회 결과가 연결됨 | 영향 범위를 계산해 삭제 | 목록·상세·집계 키가 함께 남지 않는가 |
| 비동기 처리로 변경됨 | 이벤트 처리 뒤 무효화 | 유실·중복·지연 시 복구 기준이 있는가 |
중요한 것은 삭제와 갱신 중 하나를 고르는 일보다 책임 위치를 정하는 일입니다. 예를 들어 주문 상태 변경 API가 있다면, 그 API 또는 변경 이벤트 소비자가 관련 캐시를 처리해야 합니다. 읽기 API가 언젠가 최신값을 만들기를 기대하면 원본 변경과 캐시 처리의 연결이 끊어집니다.
무효화 키는 데이터 모델과 함께 문서화해 두는 편이 좋습니다. 상품 상세, 상품 목록, 카테고리별 목록처럼 하나의 변경이 여러 API 응답에 영향을 줄 수 있기 때문입니다. 상세 키만 삭제하고 목록 키를 남기면 목록의 최신성 요구를 지키지 못할 수 있습니다.
무효화 정책은 “언제 지울까”보다 “원본 변경을 감지한 곳이 어떤 키까지 책임질까”로 정의해야 합니다.
TTL은 어떤 기준으로 정해야 할까?
TTL은 중요한 변경을 대신 처리하는 값이 아니라, 캐시 항목이 오래 남을 수 있는 최대 시간을 제한하는 기준입니다. 즉시 최신값이 필요한 흐름에는 쓰기 시 무효화 또는 갱신을 함께 설계해야 합니다.
Redis에서 TTL이 설정된 키는 정해진 시간이 지나면 자동으로 삭제됩니다. 그러나 TTL 만료 전 원본이 바뀌었음을 자동으로 감지하지는 않으므로, TTL만으로 변경 직후 최신 응답을 보장할 수는 없습니다. 출처: Keys and values | Docs
팀이 합의할 항목은 “30초가 맞는가”라는 숫자 하나보다 다음에 가깝습니다.
- 최대 오래됨: 사용자가 받아도 되는 가장 이전 상태는 어느 정도인가
- 변경 빈도: 원본 데이터는 실제로 얼마나 자주 바뀌는가
- 원본 비용: 캐시 미스가 데이터베이스나 외부 API에 주는 부담은 어느 정도인가
- 무효화 신뢰도: 쓰기 경로가 영향받는 키를 빠뜨리지 않고 처리할 수 있는가
- 장애 시 정책: Redis 오류 때 원본으로 우회할지, 제한된 이전 응답을 허용할지
변경이 드문 공개 설정값은 긴 TTL을 검토할 여지가 있습니다. 반대로 관리자 수정 결과가 즉시 보여야 하는 콘텐츠라면 쓰기 시 무효화를 기본으로 두고, TTL은 무효화 누락이나 지연에 대비하는 상한으로 둘 수 있습니다. 이는 서비스별 최신성 요구와 원본 부하를 함께 고려해야 하는 선택입니다.
캐시 스탬피드는 어떻게 막아야 할까?
인기 키의 만료가 한 시점에 몰릴 수 있다면, TTL 분산과 재생성 요청 제어를 함께 검토해야 합니다. 다만 잠금을 추가할 때는 잠금 자체의 실패와 대기 요청의 처리 기준도 설계에 포함해야 합니다.
인기 캐시 키가 만료되면 여러 요청이 동시에 원본 저장소를 조회하는 캐시 스탬피드가 생길 수 있습니다. TTL에 무작위 지터를 더해 만료 시점을 분산하고, 요청 병합·단일 비행 또는 짧은 분산 잠금으로 한 요청만 재생성하도록 하는 방식이 완화 방법으로 제시됩니다. 출처: Improve performance using a cache - Azure Database for PostgreSQL
이때 지터 범위나 잠금 시간에 정답값은 없습니다. 원본 조회 시간, 키별 요청량, 허용 가능한 대기 시간과 오래된 데이터 허용 범위를 기준으로 부하 시험을 통해 정해야 합니다. 잠금 만료, 재생성 실패, 잠금 보유자 검증, 대기 요청의 타임아웃과 폴백까지 정하지 않으면 새 제어 장치가 장애 지점이 될 수 있습니다.
일관성이 중요한 API는 캐시를 어떻게 다뤄야 할까?
최신성이 기능의 정답을 좌우하는 API는 캐시 적중률보다 원본 기준의 정확성을 우선해야 합니다. 캐시를 사용하더라도 어떤 응답이 오래될 수 있는지와 쓰기 직후 읽기 처리 방식을 API 소비자와 합의해야 합니다.
같은 캐시 정책으로 모든 데이터를 묶기 어렵습니다. 공개 화면의 조회수처럼 약간의 지연을 허용할 수 있는 데이터와, 사용자가 방금 바꾼 설정값은 요구하는 일관성 수준이 다릅니다.
쓰기 직후 같은 사용자가 다시 읽는 흐름
프로필 수정 뒤 즉시 프로필 API를 다시 호출할 때는 이전 캐시가 보일 수 있습니다. 쓰기 성공 뒤 관련 키를 삭제하거나, 해당 요청 범위에는 갱신된 값을 직접 응답하는 방식을 검토할 수 있습니다. 어느 방식을 택하든 쓰기 성공과 캐시 처리 사이의 실패를 재시도·복구할 기준이 필요합니다.
권한과 상태가 바뀌는 흐름
권한, 접근 가능 여부, 처리 상태처럼 잘못된 값이 보안 또는 업무 처리에 영향을 주는 데이터는 보수적으로 다뤄야 합니다. 캐시 키가 사용자와 권한 맥락을 충분히 분리하는지 확인하고, 확신하기 어렵다면 원본 조회를 우선하는 선택도 고려할 수 있습니다.
캐시 장애가 발생한 흐름
Redis를 사용할 수 없을 때도 API가 계속 동작해야 하는지 엔드포인트별로 정해야 합니다. 원본 저장소로 우회하면 지연과 부하가 커질 수 있고, 이전 값을 계속 제공하면 최신성 요구를 어길 수 있습니다. 타임아웃, 재시도, 원본 부하 보호를 이 선택과 함께 정리하세요.
운영 전에 무엇을 관찰해야 할까?
캐시 운영은 적중률만으로 판단하지 말고, 오래된 응답·무효화 실패·원본 우회 부하를 함께 관찰해야 합니다. 그래야 빠른 응답이 요구한 최신성을 지키는지 확인할 수 있습니다.
최소한 다음 질문에 답할 수 있도록 로그와 메트릭을 설계해 보세요.
- 캐시 히트·미스가 엔드포인트와 키 유형별로 어떻게 나타나는가?
- 원본 변경 뒤 예상한 키가 실제로 삭제·갱신됐는가?
- 캐시 미스가 동시에 늘어날 때 원본 저장소의 지연과 오류는 어떻게 변하는가?
- Redis 오류 또는 타임아웃 때 어떤 우회 경로가 실행됐는가?
- 오래된 값이 허용되지 않는 API가 캐시를 통해 응답하지 않았는가?
키 이름, TTL, 무효화 주체, 장애 시 동작을 운영 문서로 남겨두면 변경 작업에서 책임 범위를 다시 확인하기 쉽습니다.
결론
캐시 전략의 핵심은 더 오래 저장해 적중률을 높이는 일이 아니라, 오래된 값을 허용할 수 있는 읽기를 분리하고 원본 변경과 캐시 무효화의 책임을 연결하는 것입니다.
API별 최신성 요구를 먼저 적고, 쓰기 경로가 삭제하거나 갱신할 키를 정하세요. 그다음 TTL을 무효화 실패와 지연에 대비하는 상한으로 두고, 캐시 장애 시 원본으로 우회해도 되는 API인지 확인하면 됩니다. 이 순서는 TTL 하나에 일관성 문제를 떠넘기지 않도록 돕습니다.
자주 묻는 질문
Redis를 도입하면 모든 GET API를 캐시해야 하나요?
아닙니다. 반복 조회 비용, 최신성 허용 범위, 무효화 가능 여부를 함께 검토한 API부터 적용하는 편이 좋습니다.
TTL만 짧게 설정하면 캐시 무효화가 필요 없나요?
그렇지 않습니다. TTL이 만료되기 전에는 이전 값이 남을 수 있으므로, 변경 직후 최신값이 필요한 데이터에는 쓰기 시 무효화 또는 갱신 정책이 필요합니다.
캐시 키는 어떻게 나누는 것이 좋나요?
응답 결과를 바꾸는 입력을 기준으로 나누는 것이 출발점입니다. 사용자, 권한, 언어, 필터, 페이지 같은 조건이 키에 빠지면 서로 다른 요청이 같은 값을 받을 수 있습니다.
캐시 미스가 많으면 TTL을 무조건 늘려야 하나요?
아닙니다. 미스 증가가 짧은 TTL, 지나치게 세분화된 키, 특정 시점의 만료 집중 중 어디에서 왔는지 먼저 구분해야 합니다.
제 도움이 필요하시다면
김알레오는 프론트엔드 개발자로서, 캐시 정책이 서버 내부에만 머물지 않고 사용자가 수정한 값이 화면에 언제 반영되는지와 서버 상태를 어떻게 다루는지로 이어진다는 관점에서 살펴봅니다. 화면의 변경 직후 갱신 흐름과 오래된 응답을 허용할 수 있는 사용자 경험을 함께 정리해야 할 때 검토를 요청할 수 있습니다.
알레오의 서비스 흐름과 웹 콘텐츠 운영 환경은 알레오 공식 웹사이트에서 확인할 수 있습니다.




