redisbackend
캐시 무효화, TTL만 믿으면 안 되는 이유
"캐시는 붙이는 게 쉽고 지우는 게 어렵다." 응답 속도 개선하려고 Redis를 붙였다가, 수정한 데이터가 5분 동안 옛날 값으로 보이는 문제로 되돌아온 경험이 있다면 공감할 것이다.
TTL은 최후의 방어선
TTL은 "언젠가는 맞아진다"를 보장할 뿐이다. 결제 상태, 재고 수량, 정산 금액처럼 틀리면 안 되는 데이터는 변경 시점에 능동적으로 무효화해야 한다.
기본기: 변경 시 삭제
갱신(update)보다 삭제(delete) 가 낫다. 갱신은 DB 트랜잭션과 캐시 쓰기 사이의 레이스에서 옛 값이 이길 수 있다.
@Transactional
public void updatePrice(Long productId, BigDecimal price) {
productRepository.updatePrice(productId, price);
// 커밋 후에 지워야 한다 — 커밋 전에 지우면
// 다른 요청이 옛 값을 다시 캐싱한다
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override public void afterCommit() {
redis.delete("product:" + productId);
}
});
}
핵심은 afterCommit. 트랜잭션 안에서 지우면, 커밋 직전에 들어온 다른 요청이 아직 커밋 안 된 옛 값을 읽어 다시 캐시에 넣는다. 이 레이스는 부하가 걸리면 반드시 나타난다.
남는 구멍과 실용적 타협
afterCommit 삭제도 완벽하진 않다. 삭제 직후 조회가 몰리면 DB로 트래픽이 쏠리고(캐시 스탬피드), 삭제 자체가 실패할 수도 있다.
- 스탬피드: 짧은 락이나 논리적 만료(soft TTL)로 완화
- 삭제 실패: 어차피 TTL이 있다 — 그래서 TTL은 최후의 방어선으로 항상 깔아둔다
정리하면: 능동 무효화(afterCommit 삭제)가 주 방어, TTL이 보험. 순서를 바꾸면 장애가 난다.
조회 0