개발노트

25.03.07 Redis를 활용한 Spring Boot 캐싱 전략 본문

Spring Boot

25.03.07 Redis를 활용한 Spring Boot 캐싱 전략

ddong-kka 2025. 3. 7. 00:12

개요

Redis 사용이 처음이고 주로 캐싱에 사용할 것 같아 배운 내용을 정리해본다.

 

캐시( Cache ) 란 무엇인가?

캐시 ( Cache )란?

캐시(Cache)는 자주 사용하는 데이터를 미리 저장해두고 빠르게 제공하는 저장소를 의미한다. 데이터를 매번 원본 데이터베이스에서 가져오는 대신, 한 번 조회한 데이터를 캐시에 저장하여 이후 요청 시 빠르게 제공함으로써 성능을 최적화하는 방식이다.

 

캐시를 사용하는 이유

 

  • 성능 향상
    • 데이터베이스(DB)에서 데이터를 조회하는 것보다 캐시에서 데이터를 가져오는 속도가 훨씬 빠르다.
    • 따라서 애플리케이션의 응답 속도가 향상되고, 사용자 경험(UX)이 개선된다.
  • DB 부하 감소
    • 같은 데이터에 대한 반복적인 조회 요청을 줄여서 DB 서버의 부담을 줄일 수 있다.
  • 비용 절감
    • 클라우드 환경에서 운영 시, DB 조회 요청이 많으면 추가 비용이 발생할 수 있다.
    • 캐싱을 통해 불필요한 DB 요청을 줄이면 비용을 절감할 수 있다.
  • 고가용성 (High Availability)
    • DB 장애 발생 시, 캐시 데이터를 사용하면 일정 기간 동안 서비스를 지속적으로 운영할 수 있다.

 


Redis를 활용한 캐시 설정 방법 ( CacheConfig )

Redis는 메모리 기간의 저장소로, 빠른 속도를 자랑하는 NoSQL 데이터베이스이자 캐시로 많이 사용된다.

 

@Configuration
@EnableCaching // 스프링 캐시 활성화
public class CacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // RedisCacheConfiguration을 활용하여 기본 캐시 설정을 구성
        RedisCacheConfiguration configuration = RedisCacheConfiguration
                .defaultCacheConfig()
                .disableCachingNullValues() // null 값은 캐싱하지 않음
                .entryTtl(Duration.ofSeconds(120)) // 캐시 TTL(유효시간) 120초 설정
                .computePrefixWith(CacheKeyPrefix.simple()) // 캐시 키 접두어 설정
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())); // JSON 형식 직렬화

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(configuration) // 기본 캐시 설정 적용
                .build();
    }
}

 

 

 

주요 설정 설명

 

  • @EnableCaching
    • Spring Boot에서 캐싱 기능을 활성화하는 어노테이션
  • RedisCacheConfiguration.defaultCacheConfig()
    • 기본 캐시 설정을 적용
  • .disableCachingNullValues()
    • null 값을 캐싱하지 않도록 설정
  • .entryTtl(Duration.ofSeconds(120))
    • **TTL(Time To Live, 만료 시간)**을 120초(2분)로 설정하여, 120초 이후 자동 삭제
  • .computePrefixWith(CacheKeyPrefix.simple())
    • Redis의 키에 **고유한 접두어(prefix)**를 붙여 충돌 방지
  • .serializeValuesWith(RedisSerializer.json())
    • JSON 형식으로 직렬화하여 저장
    • Redis는 기본적으로 바이트 배열로 저장하므로, JSON 형식을 사용하면 가독성이 높아짐

 

.withInitialCacheConfigurations()의 역할과 사용법

여러 개의 캐시 설정을 적용해야 할 때

  • RedisCacheManager를 설정할 때, 모든 캐시에 동일한 설정을 적용하는 경우는 cacheDefaults(configuration)를 사용하면 된다.
  • 하지만 캐시마다 서로 다른 TTL(유효시간)이나 직렬화 방식이 필요하다면?
    • 이때 .withInitialCacheConfigurations()을 활용하여 캐시별로 개별 설정을 적용할 수 있다.

 

자주 변하는 데이터와 거의 변하지 않는 데이터의 TTL을 다르게 설정하거나 엔드포인트별로 TTL을 다르게 설정해야할 때

캐시 설정을 다르게 사용하면 좋다.

 

public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();

    // 특정 캐시 이름에 대한 개별 설정 적용
    cacheConfigurations.put("shortLivedCache", RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofSeconds(60))); // TTL 60초 설정

    cacheConfigurations.put("longLivedCache", RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(2))); // TTL 2시간 설정

    return RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(30)) // 기본 TTL 30분
            )
            .withInitialCacheConfigurations(cacheConfigurations) // 개별 캐시 설정 적용
            .build();
}

 


 

캐시 어노테이션

어노테이션 설명
@Cacheable 캐시에서 데이터를 가져오고, 없으면 실행 후 저장
@CachePut 실행 후 결과를 캐시에 업데이트
@CacheEvict 특정 캐시 데이터를 삭제
@Caching 여러 개의 캐시 관련 어노테이션을 함께 사용

 

 


@Cacheable

  • 기존에 캐시된 값이 있으면 DB 조회 없이 캐시 데이터 반환
  • 캐시에 값이 없으면 메서드를 실행한 후 결과를 저장
@Cacheable(cacheNames = "itemCache", key = "#id")
public ItemDto readOne(Long id) {
    return itemRepository.findById(id)
            .map(ItemDto::fromEntity)
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

 

1. id 에 해당하는 데이터가 itemCache에 존재하는지 확인

2. 존재하면 DB 조회 없이 캐시 데이터를 반환

3. 존재하지 않으면 DB에서 조회 후 결과를 캐시에 저장

 


 

@CachePut

  • 항상 실행 후 결과를 캐시에 저장 (업데이트 용도)
  • 기존 데이터가 있어도 무조건 실행 후 새로운 값을 저장
@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
    return ItemDto.fromEntity(itemRepository.save(Item.builder()
            .name(dto.getName())
            .description(dto.getDescription())
            .price(dto.getPrice())
            .build()));
}

 

 

1. create() 실행 후 새로운 ItemDto 생성

2. 결과(result.id)를 캐시에 저장하여 이후 조회 시 캐시 사용

 


 

@CacheEvict

  • 특정 캐시 데이터를 삭제할 때 사용
@CacheEvict(cacheNames = "itemCache", key = "#id")
public void delete(Long id) {
    itemRepository.deleteById(id);
}

 

 

1. id에 해당하는 데이터를 DB에서 삭제

2. 캐시에서도 해당 id의 데이터를 삭제

 

특정 메서드 지정 

@CacheEvict(cacheNames = "itemAllCache", key = "'readAll'")

 

 


 

@Caching

  • 여러 개의 캐시 관련 어노테이션을 동시에 적용
@Caching(evict = {
    @CacheEvict(cacheNames = "itemCache", key = "#id"),  // 개별 캐시 삭제
    @CacheEvict(cacheNames = "itemAllCache", key = "'readAll'") // 전체 목록 캐시 삭제
})
public void delete(Long id) {
    itemRepository.deleteById(id);
}

 

 

1. id에 해당하는 개별 캐시 삭제 (itemCache)

2. 전체 목록 캐시 삭제 (itemAllCache)