캐시 스탬피드

캐시가 만료되는 순간 다수의 요청이 동시에 DB로 몰려 과부하가 발생하는 현상입니다.

발생 과정

정상 상황:
요청 → [캐시 HIT] → 즉시 응답

캐시 스탬피드:
캐시 만료
  ↓
요청 1 → 캐시 MISS → DB 조회 시작
요청 2 → 캐시 MISS → DB 조회 시작  ← 동시에 수백~수천 요청이
요청 3 → 캐시 MISS → DB 조회 시작    DB로 직접 몰림
  ...
  ↓
DB 과부하 → 응답 지연 → 장애

해결 방법

1. 뮤텍스 락 (Mutex Lock)

첫 번째 요청만 DB를 조회하고, 나머지는 대기합니다.

요청 1 → 캐시 MISS → 락 획득 → DB 조회 → 캐시 저장 → 락 해제
요청 2 → 캐시 MISS → 락 대기 --------→ 캐시 HIT → 응답 O
요청 3 → 캐시 MISS → 락 대기 --------→ 캐시 HIT → 응답 O
// Redis 분산 락 예시
if (redis.setnx("lock:key", "1")) {     // 락 획득 시도
    try {
        String value = db.query();      // DB 조회
        redis.set("key", value);        // 캐시 저장
    } finally {
        redis.del("lock:key");          // 락 해제
    }
} else {
    Thread.sleep(100);                  // 잠시 대기 후 재시도
    return redis.get("key");
}

2. PER 알고리즘 (확률적 조기 갱신)

만료 전에 확률적으로 미리 캐시를 갱신합니다.

// 남은 TTL이 적을수록 갱신 확률 증가
double ttl = redis.ttl("key");
double random = Math.random();

if (random * ttl < BETA) {  // BETA: 갱신 민감도 상수
    refreshCache();          // 만료 전 미리 갱신
}

3. TTL 분산

동일한 TTL로 인해 동시에 캐시가 만료되는 것을 방지합니다.

// 고정 TTL 대신 랜덤 편차 추가
int ttl = BASE_TTL + random.nextInt(60); // ex) 300 ± 60초
redis.setex("key", ttl, value);

4. 백그라운드 갱신

캐시 만료 전 별도 스레드로 미리 갱신합니다.

캐시 TTL: 10분
갱신 스케줄러: 9분마다 실행 → 항상 캐시 유지

해결 방법 비교

방법장점단점
뮤텍스 락DB 요청 1회만 보장대기 시간 발생
PER 알고리즘자연스러운 갱신구현 복잡
TTL 분산구현 단순완전한 해결책 아님
백그라운드 갱신만료 없이 유지스케줄러 관리 필요

실무에서는 뮤텍스 락 + TTL 분산을 함께 적용하는 경우가 많습니다.

CSLOG에서 보기