본문 바로가기

Java & Spring 실전 개발

Spring Boot Redis 캐시 적용 전 반드시 확인할 체크리스트: 장애를 막는 운영 기준

반응형

요약: Redis 캐시는 Spring Boot 서비스 성능을 빠르게 개선할 수 있지만, TTL·무효화·장애 대응·데이터 정합성을 설계하지 않으면 오히려 운영 장애의 원인이 됩니다. 이 글은 Redis 캐시를 적용하기 전에 반드시 확인해야 할 체크리스트를 운영 기준으로 정리합니다.

대상 독자: Spring Boot API 응답 속도를 개선하려는 백엔드 개발자, Redis 캐시 도입을 검토하는 운영자, 캐시 장애를 줄이고 싶은 개발팀

핵심 키워드: Spring Boot Redis 캐시, Redis TTL, Cache Aside, 캐시 무효화, 장애 대응, 백엔드 성능 최적화

Spring Boot 프로젝트에서 성능 개선 이야기가 나오면 가장 먼저 등장하는 선택지가 Redis 캐시입니다. DB 조회를 줄이고 응답 시간을 낮추는 효과가 명확하기 때문입니다. 하지만 실무에서는 Redis를 붙였다는 이유만으로 장애가 사라지지 않습니다.

오히려 캐시 정책이 없으면 오래된 데이터가 노출되고, Redis 장애 시 API 전체가 느려지며, 캐시 키가 폭증해 메모리가 터지는 상황도 생깁니다. 그래서 Redis 캐시는 “도입”보다 “운영 기준”이 먼저입니다.

1. Redis 캐시를 적용하기 전에 먼저 판단할 것

모든 API에 캐시를 붙이면 성능이 좋아질 것처럼 보이지만, 실제로는 그렇지 않습니다. 캐시는 읽기 빈도가 높고 변경 빈도가 낮은 데이터에 적합합니다.

확인 항목캐시 적용 판단 기준

읽기 빈도 동일 요청이 반복적으로 발생하는가?
변경 빈도 데이터가 자주 바뀌지 않는가?
정합성 민감도 몇 초~몇 분의 지연을 허용할 수 있는가?
DB 부하 캐시 적용 전 실제 병목이 DB 조회인가?
장애 영향도 Redis 장애 시 원본 DB로 우회해도 되는가?

예를 들어 공지사항, 상품 목록, 코드성 데이터, 권한 설정 조회처럼 반복 조회가 많은 데이터는 캐시 후보가 될 수 있습니다. 반대로 결제 금액, 재고 차감, 포인트 잔액처럼 정합성이 중요한 데이터는 캐시 적용에 매우 조심해야 합니다.

2. 가장 흔한 구조: Cache Aside 패턴

Spring Boot에서 Redis 캐시를 붙일 때 가장 많이 쓰는 방식은 Cache Aside 패턴입니다. 애플리케이션이 먼저 Redis를 조회하고, 캐시가 없으면 DB에서 읽은 뒤 Redis에 저장합니다.

public ProductResponse getProduct(Long productId) {
    String key = "product:" + productId;

    ProductResponse cached = redisTemplate.opsForValue().get(key);
    if (cached != null) {
        return cached;
    }

    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new IllegalArgumentException("상품을 찾을 수 없습니다."));

    ProductResponse response = ProductResponse.from(product);
    redisTemplate.opsForValue().set(key, response, Duration.ofMinutes(10));
    return response;
}

이 구조는 단순하고 이해하기 쉽습니다. 다만 캐시 미스가 동시에 몰릴 때 DB 부하가 한 번에 증가할 수 있으므로, 트래픽이 큰 서비스에서는 캐시 스탬피드 방지도 함께 검토해야 합니다.

3. TTL은 “대충 10분”으로 정하면 안 된다

TTL은 Redis 캐시의 생명주기입니다. 운영에서 가장 흔한 실수는 모든 캐시에 동일한 TTL을 주는 것입니다. 데이터 성격에 따라 TTL은 달라져야 합니다.

데이터 유형권장 TTL주의점

코드성 데이터 30분~24시간 관리자 변경 시 즉시 삭제 필요
상품/게시글 목록 1분~10분 사용자에게 오래된 정보가 보일 수 있음
검색 결과 30초~5분 쿼리별 키 폭증 주의
사용자별 데이터 짧게 설정 권한, 개인정보, 로그아웃 처리 주의

TTL이 너무 길면 데이터 정합성이 깨지고, 너무 짧으면 캐시 효과가 떨어집니다. 운영에서는 “사용자가 오래된 데이터를 봐도 되는 시간”을 기준으로 TTL을 정하는 편이 안전합니다.

4. 캐시 키 설계 체크리스트

Redis 캐시 장애의 상당수는 키 설계에서 시작됩니다. 키가 너무 넓으면 충돌이 나고, 너무 세분화되면 키 수가 폭발합니다.

좋은 캐시 키 예시

product:detail:10001
product:list:category:book:page:1
user:permission:42
search:keyword:spring-boot:sort:latest

피해야 할 캐시 키 예시

product
list
user
search:random-query-without-limit

캐시 키에는 도메인, 데이터 유형, 식별자, 조건을 넣는 것이 좋습니다. 또 운영 중 삭제와 추적이 쉽도록 일정한 네이밍 규칙을 팀 내 표준으로 정해야 합니다.

5. 캐시 무효화 전략을 먼저 정해야 한다

Redis 캐시에서 가장 어려운 문제는 저장이 아니라 삭제입니다. DB 데이터가 바뀌었는데 캐시가 남아 있으면 사용자는 오래된 데이터를 보게 됩니다.

전략설명적합한 상황

TTL 만료 일정 시간이 지나면 자동 삭제 정합성 요구가 낮은 목록 데이터
수정 시 삭제 DB 수정 후 관련 캐시 삭제 상품 상세, 게시글 상세
이벤트 기반 삭제 Kafka, 메시지 큐로 캐시 삭제 이벤트 전파 여러 서비스가 데이터를 공유하는 MSA
버전 키 사용 캐시 키에 버전 또는 갱신 시각 포함 대량 무효화가 필요한 경우

실무에서는 TTL과 수정 시 삭제를 함께 쓰는 경우가 많습니다. 삭제 실패가 발생하더라도 TTL로 최종 만료되게 만드는 안전장치가 필요하기 때문입니다.

6. Redis 장애 시 API가 같이 죽지 않게 만들기

캐시는 보조 저장소입니다. Redis가 잠시 느려지거나 장애가 발생해도 핵심 API는 원본 DB로 우회할 수 있어야 합니다.

public ProductResponse getProductSafely(Long productId) {
    String key = "product:" + productId;

    try {
        ProductResponse cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return cached;
        }
    } catch (Exception redisException) {
        log.warn("Redis cache read failed. key={}", key, redisException);
    }

    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new IllegalArgumentException("상품을 찾을 수 없습니다."));

    ProductResponse response = ProductResponse.from(product);

    try {
        redisTemplate.opsForValue().set(key, response, Duration.ofMinutes(10));
    } catch (Exception redisException) {
        log.warn("Redis cache write failed. key={}", key, redisException);
    }

    return response;
}

중요한 포인트는 Redis 예외가 사용자 응답 실패로 이어지지 않게 하는 것입니다. 물론 모든 API를 무조건 우회시키면 DB가 터질 수 있으므로, Redis 장애 시 DB 부하를 제한하는 rate limit, circuit breaker, timeout 설정도 함께 고려해야 합니다.

7. 운영에서 반드시 봐야 할 Redis 지표

Redis 캐시는 적용 후 모니터링이 없으면 위험합니다. 최소한 아래 지표는 대시보드에서 확인해야 합니다.

지표확인 이유

cache hit ratio 캐시가 실제로 DB 부하를 줄이고 있는지 확인
used memory 키 폭증 또는 TTL 누락 여부 확인
evicted keys 메모리 부족으로 키가 밀려나는지 확인
expired keys TTL 정책이 정상 동작하는지 확인
command latency Redis 응답 지연이 API 지연으로 이어지는지 확인
connected clients 커넥션 누수 또는 과도한 연결 증가 확인

캐시 hit ratio가 낮다면 캐시 키가 너무 세분화되어 있거나, TTL이 너무 짧거나, 애초에 캐시에 맞지 않는 데이터를 저장하고 있을 수 있습니다.

8. Spring Cache 추상화를 사용할 때 주의할 점

Spring Boot에서는 @Cacheable, @CacheEvict를 사용하면 캐시 코드를 간단하게 만들 수 있습니다. 하지만 추상화가 편한 만큼 내부 동작을 모르면 장애 원인을 찾기 어려울 수 있습니다.

@Cacheable(cacheNames = "productDetail", key = "#productId")
public ProductResponse getProduct(Long productId) {
    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new IllegalArgumentException("상품을 찾을 수 없습니다."));
    return ProductResponse.from(product);
}

@CacheEvict(cacheNames = "productDetail", key = "#productId")
public void updateProduct(Long productId, ProductUpdateRequest request) {
    Product product = productRepository.findById(productId)
        .orElseThrow(() -> new IllegalArgumentException("상품을 찾을 수 없습니다."));
    product.update(request);
}

이 방식은 빠르게 적용할 수 있지만, 캐시 키 생성 규칙, null 캐싱 여부, TTL 설정, 직렬화 방식, self-invocation 문제를 반드시 확인해야 합니다.

9. Redis 캐시 적용 전 최종 체크리스트

체크리스트완료 기준

캐시 대상 선정 읽기 빈도와 변경 빈도를 기준으로 대상 API 선정
TTL 정책 데이터 유형별 TTL 문서화
무효화 정책 DB 수정 시 삭제 대상 키 정의
장애 우회 Redis 장애 시 원본 DB 조회 또는 제한적 fallback 설계
메모리 관리 키 네이밍, TTL 누락 방지, maxmemory 정책 확인
직렬화 JSON, String, Binary 중 팀 표준 결정
모니터링 hit ratio, memory, latency, evicted keys 대시보드 구성
테스트 캐시 미스, 캐시 히트, 삭제, Redis 장애 상황 테스트

10. 결론: Redis 캐시는 성능 기능이 아니라 운영 기능이다

Redis 캐시는 단순히 빠른 저장소를 하나 붙이는 작업이 아닙니다. TTL, 키 설계, 무효화, 장애 우회, 모니터링이 함께 있어야 운영 가능한 캐시가 됩니다.

Spring Boot 서비스에 Redis 캐시를 적용할 계획이라면 먼저 “어떤 데이터를 얼마나 오래 믿을 수 있는가?”를 정해야 합니다. 이 질문에 답하지 못한 상태에서 캐시를 붙이면 성능 개선보다 장애 가능성이 더 커질 수 있습니다.

자주 묻는 질문

Redis 캐시는 모든 조회 API에 적용해도 되나요?

아니요. 조회 빈도가 높고 변경 빈도가 낮으며, 일정 시간 오래된 데이터를 허용할 수 있는 API에 우선 적용하는 것이 안전합니다.

Redis 장애가 나면 API도 실패하게 만들어야 하나요?

대부분의 캐시 용도에서는 Redis 장애가 사용자 요청 실패로 이어지지 않도록 fallback을 설계하는 것이 좋습니다. 다만 DB 보호를 위한 timeout과 circuit breaker도 함께 필요합니다.

TTL은 몇 분으로 설정하는 것이 좋나요?

정답은 없습니다. 사용자가 오래된 데이터를 허용할 수 있는 시간을 기준으로 정해야 합니다. 목록 데이터는 짧게, 코드성 데이터는 길게 가져가는 식으로 데이터별 정책을 분리하는 것이 좋습니다.

함께 읽으면 좋은 글

반응형