Spring Boot에 Redis 캐싱 적용하기

이진일·2026년 5월 12일
post-thumbnail

Spring Boot 프로젝트에 Redis 캐싱 적용하기

데이터베이스의 부하를 줄이고 성능을 높이기 위해 Spring 환경에서 Redis를 캐시 저장소로 활용하는 방법을 알아보겠습니다.


1. Spring Cache 설정

Spring은 어노테이션 기반의 캐싱을 지원합니다. 먼저 설정을 위해 @EnableCaching을 추가하고 RedisCacheManager를 Bean으로 등록해야 합니다.

🔹 CacheConfig 설정

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();
    }
}

2. 주요 캐싱 어노테이션 활용

설정을 마쳤다면 서비스 레이어의 메서드에 어노테이션을 붙여 간단히 캐싱을 적용할 수 있습니다

  • @Cachable: 데이터가 캐시에 있으면 메서드를 실행하지 않고 캐시 값을 반환합니다. (Cache-Aside)
  • @CachePut: 메서드를 항상 실행하고 그 결과를 캐시에 업데이트합니다. (Write-Through 느낌의 갱신)
  • @CacheEvict: 지정된 캐시 데이터를 삭제합니다. 데이터가 수정되거나 삭제될 대 캐시 일관성을 유지하기 위해 사용합니다.

🔹 실전 코드 예시

// 상세 조회 결과 캐싱 (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) { ... }

3. 동적 검색 결과 캐싱하기

검색어(q)나 페이지 정보(Pageable)에 따라 달라지는 검색 결과도 캐싱할 수 있습니다.

🔹 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);
}
  • 결과: Redis에는 itemSearchCache::[검색어, 페이지번호, 페이지크기] 형태의 키로 저장되어, 동일한 조건의 검색 요청 시 DB 조회를 건너뜁니다.

4. Write-Behind 전략 구현 (수동 캐싱)

특가 상품 판매처럼 단시간에 주문이 몰리는 경우, 매번 DB에 바로 쓰기보다는 Redis 리스트에 담아두었다가 일정 주기마다 벌크(Bulk)로 DB에 저장하는 Write-Behind 전략이 유용합니다. 이는 어노테이션으로 불가능하므로 직접 구현해야 합니다.

1단계: Redis에 주문 쌓기

구매 요청 시 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);
}

2단계: 스케줄링을 통한 DB 적재(@Secheduled)

일정 시간마다 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");
}

💡 정리하며

  • Spring Cache 어노테이션은 일반적인 비즈니스 로직에서 생산성을 높여줍니다.
  • Write-Behind와 같은 특수한 전략은 RedisTemplateScheduling을 조합하여 시스템 부하를 획기적으로 줄일 수 있습니다.
  • 서비스의 트래픽 특성에 맞는 적절한 캐싱 전략을 선택하여 더 견고한 백엔드를 구축해 보세요!

0개의 댓글