카카오테크 부트캠프

Redis 캐시, Cache Stampede 방지 전략 보고서 (TTL Jitter, Mutex Locking)

kanado 2025. 7. 13. 09:12

1. 배경 및 문제 정의

Cache Stampede란?

Cache Stampede는 특정 캐시 키가 TTL 만료된 순간, 다수의 클라이언트 요청이 동시에 캐시 미스를 일으켜 백엔드(DB, 서비스 계층)에 집중적인 부하를 발생시키는 현상이다.

기존 구조의 문제점

  • 모든 도메인에 대해 동일한 TTL(예: 30분)을 부여하고 있음.
  • 결과적으로, 모든 도메인의 캐시가 동일 시점에 만료되며, 인기 도메인일수록 동시에 재조회가 발생해 스탬피드 발생 위험이 커짐.

2. 해결 전략 요약

전략 설명  기대 효과
1. TTL Jitter 도메인마다 고유 TTL을 부여 (기본값 ±3분 랜덤) TTL 동시 만료 분산, 스탬피드 완화
2. Mutex Locking (Redisson) 캐시 미스 발생 시 락을 획득한 요청만 DB 조회 허용 동시성 제어, DB 중복 조회 차단

3. 전략 1: TTL Jitter – 캐시 TTL 분산 처리

적용 방식

  • 도메인은 최초 캐싱 시점에 고유 TTL (예: 35분 ± 3분)을 부여받음.
  • 해당 도메인 내 연관된 모든 캐시 항목은 동일 TTL로 관리됨.
  • 이후 TTL 연장 시에도 동일한 TTL을 사용하여 일관성 유지.
public Duration getTTLDurForTuningReport() {
    long baseSeconds = TUNING_REPORT_TTL.getSeconds(); // 35분
    long jitterSeconds = ThreadLocalRandom.current().nextInt(180); // 최대 ±3분
    return Duration.ofSeconds(baseSeconds + jitterSeconds);
}

@Async
public void refreshTuningReportTTL(Long userId, String domain) {
    Duration ttl = getTTLDurForTuningReport();

    refreshUserDomainTTL(userId, ttl);           // userId 기준 TTL 연장
    refreshAllUserReactionTTLByScan(userId, ttl); // 유저 반응 캐시 TTL 연장
    refreshReportListTTL(domain, ttl);           // 도메인별 리포트 목록 TTL 연장
}

기대 효과

  • TTL 일괄 만료 방지 → 스탬피드 분산
  • 도메인 단위 TTL 관리 → 캐시 제어 단위 일관성 확보
  • 캐시 TTL 자동 갱신 로직과 연계하여 신선도 유지 및 안정적 운영

4. 전략 2: Redisson 기반 Mutex Locking

동작 개요

  1. 캐시 미스 발생 시, 도메인 단위 락을 시도
  2. 락 획득 성공한 요청만 DB 조회 및 캐싱 수행
  3. 나머지 요청은 락 획득 실패 시 Double-Check 방식으로 캐시 재확인
  4. 락 획득 후에도 캐시 재조회하여 중복 조회 방지
private List<TuningReportListResponse.ReportItem> loadOrCacheReports(String domain, int page, int size, TuningReportSortType sort) {
    List<TuningReportListResponse.ReportItem> items = cacheManager.getCachedReportList(domain);
    if (items != null) return items;

    String lockKey = "lock:report:domain:" + domain;
    RLock lock = redissonClient.getLock(lockKey);

    try {
        boolean acquired = lock.tryLock(2, 5, TimeUnit.SECONDS); // 최대 2초 대기, 5초 락 유지
        if (!acquired) {
            log.warn("⚠️ 도메인 {} 캐싱 락 획득 실패 → fallback to 캐시 재확인", domain);
            return cacheManager.getCachedReportList(domain);
        }

        // Double Check
        items = cacheManager.getCachedReportList(domain);
        if (items == null) {
            log.info("🎯 캐시 미스 → DB 조회 및 캐싱 시작: domain={}", domain);
            var pageReq = PageRequest.of(page, size);
            var reports = sort.fetch(pageReq, transactionalService.getTuningReportRepository(), domain);
            items = reports.stream()
                    .map(transactionalService::toReportItemWithoutReactions)
                    .collect(Collectors.toList());

            cacheManager.cacheReportList(domain, items);
        }

    } catch (InterruptedException e) {
        log.warn("❌ 캐시 락 처리 중단: {}", e.getMessage());
        Thread.currentThread().interrupt();
        return List.of();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

    return items;
}

 

5. 전략 비교 및 시너지

항목 TTL Jitter 전략  Mutex Locking 전략
목적 TTL 만료 시점 분산 동시성 제어 및 중복 DB 조회 방지
단위 도메인별 TTL 랜덤화 도메인별 락 키 관리
장점 스탬피드 시점 자체를 분산 미스 발생 시 오직 1개 요청만 처리
병행 적용 시 효과 TTL 분산 + 요청 제어로 시너지 효과 극대화  

6. 결론 및 기대 효과

본 시스템은 TTL Jitter + Redisson Mutex Locking 전략을 함께 도입함으로써 다음과 같은 효과를 실현하였다.

  • Cache Stampede 완전 방지
  • 도메인 단위 TTL 및 캐시 관리의 일관성과 유연성 확보
  • DB 부하 분산 및 시스템 응답 속도 향상
  • 확장성 고려된 락 구조 및 TTL 관리 체계 도입
  • 안정적이고 신뢰도 높은 캐싱 시스템 구축

끝.