현재 우리 회사는 Redis 클러스터 모드를 사용하지 않고 있습니다.
이러한 상황에서 Redis가 장애가 나면 어떻게 대응할 것인지 고민하게 되었고, 성능 또한 함께 개선하고 싶었습니다.
들어가기 전에 용어 정리:
그래서 Local Cache(L1) 도입을 결정했고, 도입 전에 다양한 시나리오를 바탕으로 구조를 설계해보았습니다.
Redis(L2)에 장애가 생겼을 경우, DB로 바로 가지 않고 JVM 내 L1(Local Cache) 를 통해 1차적으로 버틴다는 전략입니다.
하지만 L1은 JVM의 힙 메모리를 사용하는 만큼, 사이즈가 큰 데이터를 무분별하게 저장하면 OOM 위험이 있습니다.
따라서 다음과 같은 정책을 고려했습니다:
| 데이터 크기 | 처리 방식 |
|---|---|
| 소용량 데이터 | L1에 저장하여 빠르게 재사용 |
| 대용량 데이터 | L1에는 저장하지 않고, DB에서 직접 조회 후 필요 시 L2(Redis)에만 저장 |
⚠️ "사이즈가 크다"의 기준은 테스트와 JVM 힙 사용량에 따라 조정해야 합니다.
캐시 동작 흐름
1. L2(Redis) 캐시 조회
2. Redis 장애 발생 시:
├─ 2-1. L1 캐시 조회 (Caffeine)
│ ├─ Hit: 결과 리턴
│ └─ Miss: DB 조회
│ ├─ 데이터 작음: L1 저장 후 리턴
│ └─ 데이터 큼: 저장 없이 리턴
| 장점 | 설명 |
|---|---|
| 🧹 L1에 대용량 데이터 미저장 | GC/OOM 리스크 차단 |
| 🚀 L1에서 빠른 조회 | 성능 개선 |
| 📉 L2/DB만 대용량 처리 | 안정성 확보 |
| 💰 Redis 호출 감소 | ElastiCache 비용 절감 효과 |
"Cache Cascade Failure"란 한 계층이 터지면 다음 계층이 부하를 받다가 함께 터지는 현상입니다.
해결 방법
개인적으로는 방법 2가 이상적이지만, 방법 1이 현실적인 선택이라고 생각합니다.
Scale-out 환경에서는 L1 정합성 유지가 어렵습니다.
그 이유는 L1 동기화를 위해 Redis Pub/Sub을 사용하는데, Redis가 장애를 일으키면 동기화 자체가 불가능하기 때문입니다.
따라서 L1에는 짧은 TTL(Time To Live) 을 설정하여 최소한의 생존 용도로만 활용하는 것이 좋다고 판단했습니다.
더 나아가면 Kafka 등 MQ로 Pub/Sub을 대체하는 것이 최선이지만,
현재 환경에서는 Redis 외 대안이 없으므로, 장애 상황에는 AWS SNS 같은 서버리스 기반으로 비용까지 절감할 수 있는 구조가 더 나은 방향이라고 봅니다.
물론 이 모든 구조는 향후 유지보수자가 관리 가능해야 한다는 점도 함께 고려해야 합니다.
[요청 발생]
│
▼
[1. L1(Local Cache) 조회]
│
├─▶ Hit → ✅ 응답 리턴
│
▼ Miss
[2. L2(Redis) 조회]
│
├─▶ 성공 → L1에 put → ✅ 응답 리턴
│
▼ Miss or 실패 (CircuitBreaker)
[3. DB 조회 수행]
│
├─ 결과 있음
│ ├─ L1에 put
│ ├─ L2에 저장
│ └─ ✅ 응답 리턴
│
└─ 결과 없음 → null 리턴
@SmartCacheable이라는 이름의 AOP 기반 커스텀 어노테이션을 활용해, 위의 로직을 선언형으로 적용했습니다.
이 어노테이션이 붙은 메서드는 자동으로 L1 → L2 → DB 흐름을 따릅니다.
🔗 구현 코드 보기
@SmartCacheable(cacheName = "board", key = "#id", ttlSeconds = 300)
public BoardResponse.Get getById(Long id){
BoardEntity board = boardRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("게시글이 존재하지 않습니다."));
return new BoardResponse.Get(board.getId(), board.getContent(), board.getCreatedAt(), board.getUpdatedAt());
}
이 방식은 강력하지만, 단점도 존재합니다.
그래서 2탄에서는 Spring Cache 기반 구조로 코드 변경 없이 적용하는 방식을 소개할 예정입니다.
보다 실용적이고 유지보수 가능한 방향을 함께 고민해보려 합니다.
GitHub: https://github.com/cwangg897/smart
TMI: 글의 내용은 모두 제 생각이며, 디자인이나 맞춤법 검사는 GPT의 도움을 받았습니다.