Contents
see ListRedis 캐싱 전략 - Cache Aside, Write Through, Write Behind
Redis를 활용한 캐싱 전략을 이해하면 애플리케이션 성능을 크게 향상시킬 수 있습니다.
캐싱 전략 비교
| 전략 | 읽기 | 쓰기 | 일관성 |
|---|---|---|---|
| Cache Aside | 캐시 → DB | DB 직접 | 낮음 |
| Write Through | 캐시 | 캐시 + DB 동시 | 높음 |
| Write Behind | 캐시 | 캐시 → 비동기 DB | 중간 |
| Read Through | 캐시 자동 로드 | - | 중간 |
Cache Aside (Lazy Loading)
// 읽기
public User getUser(Long id) {
String key = "user:" + id;
User user = redisTemplate.opsForValue().get(key);
if (user == null) { // Cache Miss
user = userRepository.findById(id);
redisTemplate.opsForValue().set(key, user, Duration.ofHours(1));
}
return user;
}
// 쓰기 (캐시 무효화)
public void updateUser(User user) {
userRepository.save(user);
redisTemplate.delete("user:" + user.getId()); // 캐시 삭제
}Write Through
// 쓰기 시 캐시와 DB 동시 업데이트
@Transactional
public void updateUser(User user) {
userRepository.save(user);
redisTemplate.opsForValue().set("user:" + user.getId(), user);
}Write Behind (Write Back)
// 캐시에 먼저 쓰고 나중에 DB 동기화
public void updateUser(User user) {
redisTemplate.opsForValue().set("user:" + user.getId(), user);
// 비동기로 DB 동기화 (배치 처리)
asyncDbWriter.enqueue(user);
}
// 주기적으로 배치 처리
@Scheduled(fixedRate = 5000)
public void flushToDatabase() {
List<User> users = asyncDbWriter.drainQueue();
userRepository.saveAll(users);
}TTL (Time To Live) 설정
// 고정 TTL
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(30));
// 조건부 TTL
long ttl = isPopular(id) ? 3600 : 300; // 인기 데이터는 1시간
redisTemplate.expire(key, Duration.ofSeconds(ttl));캐시 Stampede 방지
// 분산 락으로 중복 조회 방지
public User getUserSafe(Long id) {
String key = "user:" + id;
User user = redisTemplate.opsForValue().get(key);
if (user == null) {
String lockKey = "lock:user:" + id;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
if (locked) {
try {
user = userRepository.findById(id);
redisTemplate.opsForValue().set(key, user);
} finally {
redisTemplate.delete(lockKey);
}
} else {
Thread.sleep(100); // 대기 후 재시도
return getUserSafe(id);
}
}
return user;
}Spring Cache 통합
@Cacheable(value = "users", key = "#id")
public User getUser(Long id) {
return userRepository.findById(id);
}
@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
userRepository.save(user);
}
@CachePut(value = "users", key = "#user.id")
public User createUser(User user) {
return userRepository.save(user);
}전략 선택 가이드
- Cache Aside: 읽기 위주, 단순한 구현
- Write Through: 쓰기 일관성 중요
- Write Behind: 쓰기 성능 중요, 약간의 데이터 손실 허용