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
동작 개요
- 캐시 미스 발생 시, 도메인 단위 락을 시도
- 락 획득 성공한 요청만 DB 조회 및 캐싱 수행
- 나머지 요청은 락 획득 실패 시 Double-Check 방식으로 캐시 재확인
- 락 획득 후에도 캐시 재조회하여 중복 조회 방지
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 관리 체계 도입
- 안정적이고 신뢰도 높은 캐싱 시스템 구축
끝.
'카카오테크 부트캠프' 카테고리의 다른 글
| DB 부하 98% 감소, 캐시 적중률 87%: 트래픽 집중형 시스템의 Redis 도입기 (3) | 2025.07.18 |
|---|---|
| 동적인 데이터 캐시 동시성 제어 전략 비교 및 분산 락 선택 (0) | 2025.07.15 |
| Redis 캐시 키 설계 관련 보고서: 사용자 domain 정보 처리 전략 (1) | 2025.07.11 |
| SSE 도입 후 궁금증 완전 해소 – Polling 한계 극복부터 Nginx 설정, Tomcat 병목까지 총정리 (1) | 2025.06.29 |
| MVP 이후의 코드 품질 개선 전략 — 리팩토링 (0) | 2025.06.21 |