
데이터베이스의 부하를 줄이고 성능을 높이기 위해 Spring 환경에서 Redis를 캐시 저장소로 활용하는 방법을 알아보겠습니다.
Spring은 어노테이션 기반의 캐싱을 지원합니다. 먼저 설정을 위해 @EnableCaching을 추가하고 RedisCacheManager를 Bean으로 등록해야 합니다.
TTL(유지 시간)과 직렬화 방식을 정의합니다.
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration configuration = RedisCacheConfiguration
.defaultCacheConfig()
.disableCachingNullValues() // null값 캐싱 방지
.entryTtl(Duration.ofSeconds(120)) // 기본 TTL 2분
.computePrefixWith(CacheKeyPrefix.simple())
.serializeValuesWith(
SerializationPair.fromSerializer(RedisSerializer.java())
);
return RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(configuration)
.build();
}
}
설정을 마쳤다면 서비스 레이어의 메서드에 어노테이션을 붙여 간단히 캐싱을 적용할 수 있습니다
// 상세 조회 결과 캐싱 (id를 키로 사용)
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
return itemRepository.findById(id).map(ItemDto::fromEntity).orElseThrow();
}
// 전체 조회 캐시 (아이템이 추가/수정되면 삭제되어야 함)
@Cacheable(cacheNames = "itemAllCache", key = "methodName")
public List<ItemDto> readAll() { ... }
// 데이터 수정 시 상세 캐시는 업데이트, 전체 캐시는 삭제
@CachePut(cacheNames = "itemCache", key = "args[0]")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)
public ItemDto update(Long id, ItemDto dto) { ... }
검색어(q)나 페이지 정보(Pageable)에 따라 달라지는 검색 결과도 캐싱할 수 있습니다.
@Cacheable(
cacheNames = "itemSearchCache",
key = "{ args[0], args[1].pageNumber, args[1].pageSize }"
)
public Page<ItemDto> searchByName(String query, Pageable pageable) {
return itemRepository.findAllByNameContains(query, pageable)
.map(ItemDto::fromEntity);
}
itemSearchCache::[검색어, 페이지번호, 페이지크기] 형태의 키로 저장되어, 동일한 조건의 검색 요청 시 DB 조회를 건너뜁니다.특가 상품 판매처럼 단시간에 주문이 몰리는 경우, 매번 DB에 바로 쓰기보다는 Redis 리스트에 담아두었다가 일정 주기마다 벌크(Bulk)로 DB에 저장하는 Write-Behind 전략이 유용합니다. 이는 어노테이션으로 불가능하므로 직접 구현해야 합니다.
구매 요청 시 DB가 아닌 Redis List에 데이터를 rightPush 합니다.
public void purchase(ItemOrderDto dto) {
// 1. Redis List에 주문 정보 적재
orderOps.rightPush("orderCache::behind", dto);
// 2. 실시간 리더보드 점수 업데이트 (Sorted Set)
rankOps.incrementScore("soldRanks", itemDto, 1);
}
일정 시간마다 Redis에 쌓인 데이터를 DB로 옮깁니다.
@Transactional
@Scheduled(fixedRate = 20, timeUnit = TimeUnit.SECONDS)
public void insertOrders() {
if (!orderTemplate.hasKey("orderCache::behind")) return;
// 현재 쌓인 주문들로 키 이름 변경 (원자적 처리 보조)
orderTemplate.rename("orderCache::behind", "orderCache::now");
// Redis 데이터를 리스트로 가져와 DB에 한꺼번에 저장
List<ItemOrderDto> orders = orderOps.range("orderCache::now", 0, -1);
orderRepository.saveAll(orders.stream()
.map(dto -> ItemOrder.builder().itemId(dto.getItemId()).count(dto.getCount()).build())
.toList());
// 처리 완료 후 Redis 키 삭제
orderTemplate.delete("orderCache::now");
}
RedisTemplate과 Scheduling을 조합하여 시스템 부하를 획기적으로 줄일 수 있습니다.