이전 글에서는 Caffeine을 활용한 로컬 캐시만으로도 API 성능이 충분히 개선되는 것을 확인했다.
그러나 이 방식은 단일 서버 환경에 한정된 최적화다.
서비스가 실제 운영 환경에서 서버를 여러 대로 수평 확장하는 순간, 각 인스턴스에 존재하는 Caffeine 캐시는 서로 다른 데이터를 보유하게 된다. 이는 캐시 일관성 문제로 이어지며, 다음과 같은 부작용을 일으킨다:
따라서 멀티 인스턴스 환경에서는 각 서버가 공유할 수 있는 중앙 캐시가 필요했고, 이에 따라 Redis를 중간 계층으로 추가하여 3단 캐시 구조를 설계하게 되었다.
단계별로 명확히 책임을 나누는 것이 핵심이었다. 각 계층은 다음과 같은 목적을 가지고 있다:
계층 | 설명 | 주요 목적 | 특징 |
---|---|---|---|
Caffeine (1차) | 각 서버 인스턴스의 메모리 내부에 존재하는 로컬 캐시 | 응답 속도 최적화 | 가장 빠름, 서버 재시작 시 초기화 |
Redis (2차) | 모든 인스턴스가 공유하는 중앙 캐시 | 인스턴스 간 캐시 일관성 유지 | 네트워크 IO 발생, TTL 설정 필요 |
DB (3차) | 모든 요청의 최종 데이터 소스 | 정확한 데이터 제공 | 성능 비용 높음, 캐시 미스 시만 접근 |
1. 요청 도착
2. Caffeine 에서 조회 (있으면 바로 반환)
3. 없으면 Redis 조회 (있으면 Caffeine에 저장 후 반환)
4. Redis에도 없으면 DB 조회 → Redis + Caffeine에 저장
이 흐름은 운영 중 실제 로그로도 확인할 수 있다:
⚠️ [Caffeine MISS] key = movies:all
⚠️ [Redis MISS] key = movies:all
📡 [DB QUERY] key = movies:all
📥 [Caffeine PUT] (from DB)
📦 [Redis PUT] (from DB)
모든 데이터를 Redis에 저장하면 일견 편해 보이지만, Redis는 중앙 캐시이자 네트워크 기반 시스템이다.
그만큼 다음과 같은 비용이 발생한다:
그래서 우리는 다음 기준을 적용했다:
if (hitCount >= <5) {
redisRepositoryPort.put(cacheKey, data)
}
이 기준은 운영 중 상황에 따라 threshold
값을 조절할 수 있도록 만들어 두었다.
캐시를 운영하려면 키 관리가 중요하다. 특히 동적 쿼리를 기반으로 하는 검색 기능은 캐시 키 충돌과 메모리 폭증을 동시에 유발할 수 있다.
movies:all
movies:search:{title}:{genreHash}
genreHash
는 장르 리스트를 정렬 후 SHA256 해시로 변환이러한 키 설계 전략을 통해 중복 캐시를 방지하고, 빠른 무효화가 가능한 구조를 확보했다.
우리는 이 3단 캐시 구조를 헥사고날 아키텍처와 모듈 구조에도 잘 녹여냈다.
다음과 같은 구성이다:
// 포트 인터페이스
interface CachedMoviePort {
fun find(key: String): List<MovieDto>?
fun save(key: String, data: List<MovieDto>)
}
// 서비스에서 호출
fun getMovies(): List<MovieDto> {
return cachedMoviePort.find("movies:all")
?: run {
val result = movieRepository.findAll()
cachedMoviePort.save("movies:all", result)
result
}
}
CachedMoviePort
는 외부 캐시 계층과의 통신을 추상화RedisCacheAdapter
, CaffeineCacheAdapter
는 각각 포트를 구현→ 이 구조는 테스트도 쉽고, 차후 Redis에서 다른 캐시 시스템(ZooKeeper, Hazelcast 등)으로 변경해도 코드 변경이 최소화된다.
단순히 캐시를 쓰는 것이 아니라, 각 계층의 역할을 분리하고 운영 목적에 따라 의미 있는 구조로 만드는 것이 중요하다.
구조 | 기대 효과 |
---|---|
Caffeine | 요청 대부분을 빠르게 처리 (초저지연 응답) |
Redis | 인스턴스 간 일관성 확보 (스케일아웃 환경 대응) |
DB | 최후의 수단, 정합성 보장 |