캐시로 인한 조회수 랭킹 정합성 이슈

오형상·2025년 4월 8일
0

Ficket

목록 보기
8/8

문제 발생

이벤트 상세 조회 API에 @Cacheable을 적용하여 조회 시마다 DB 접근을 줄이고 응답 속도를 높이려 했습니다. 그러나 실제 서비스에서 적용 후, 조회수 랭킹이 갱신되지 않는 문제가 발생했습니다.


원인 분석

  • 캐시된 응답이 반환되면서, 실제 조회수 증가 처리를 담당하는 로직이 실행되지 않았습니다.

  • 특히 조회수 랭킹은 Redis의 ZSet을 기반으로 집계되고 있었는데, @Cacheable로 인해 API 내부의 ZINCRBY 호출이 누락되었습니다.

  • 이로 인해, 실제로 해당 이벤트를 조회했더라도 조회수가 증가하지 않아 랭킹에는 반영되지 않는 문제가 지속적으로 발생했습니다.


해결 방법

  • @Cacheable 애너테이션을 제거하고, 캐시 로직을 수동으로 제어하는 방식으로 전환했습니다.
  • RedisTemplate을 활용하여 캐시를 직접 관리하였습니다.
  • 주요 로직은 다음과 같습니다:
    1. Redis에 캐시된 데이터가 있으면 해당 데이터를 반환
    2. 없을 경우 DB에서 조회 후 Redis에 저장
    3. 캐시 유무에 관계없이 ZSet 점수 증가 로직을 반드시 실행

개선 전

@Cacheable(value = "eventDetail", key = "#eventId")
public EventDetailRes getEventDetail(Long eventId) {
    // 1. 쿠키를 통해 중복 조회 확인
    if (!isDuplicateView(request, response, eventId)) {
        incrementViewCount(eventId); // 2. 조회수 증가
    }

    // 3. 이벤트 조회
    Event event = eventRepository.findById(eventId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_NOT_FOUND));

    // 4. 응답 객체 변환
    return EventDetailRes.toEventDetailRes(event);
}

개선 후

/**
 * 이벤트 상세 조회 및 조회수 증가
 */
public EventDetailRes getEventDetail(HttpServletRequest request, HttpServletResponse response, Long eventId) {
    // 1. 캐시 키 생성
    String cacheKey = RedisKeyHelper.getEventDetailCacheKey(eventId);

    // 2. 캐시에서 데이터 확인
    EventDetailRes cachedEvent = (EventDetailRes) redisTemplate.opsForValue().get(cacheKey);

    // 3. 쿠키를 통해 중복 조회 확인
    if (!isDuplicateView(request, response, eventId)) {
        incrementViewCount(eventId); // 조회수 증가
    }

    // 4. 캐시가 존재하면 반환
    if (cachedEvent != null) {
        return cachedEvent;
    }

    // 5. 캐시가 없으면 DB 조회
    Event event = eventRepository.findById(eventId)
            .orElseThrow(() -> new BusinessException(ErrorCode.EVENT_NOT_FOUND));

    // 6. 변환 및 캐시에 저장 
    EventDetailRes eventDetailRes = EventDetailRes.toEventDetailRes(event);
    redisTemplate.opsForValue().set(cacheKey, eventDetailRes, Duration.ofHours(24)); // 24시간 TTL

    return eventDetailRes;
}

마무리

이번 이슈를 통해 캐시 적용 시 정합성과 성능의 균형을 어떻게 맞출 것인지에 대한 중요성을 다시금 실감했습니다.
특히 조회수처럼 변동이 잦은 데이터는 캐시 전략을 신중하게 설계해야 하며, 상황에 따라 수동 캐시 제어 방식이 더 유리할 수 있음을 확인할 수 있었습니다.


0개의 댓글