🔗 깃허브 링크
Cache란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 저장 공간이 작고 비용이 비싼 대신, 매우 빠른 성능을 제공한다는 장점이 있다.
이와 같이 캐시는 웹 서비스 환경, 엔터프라이스급 개발 환경에서 DBMS 의 부하를 줄이고, 성능을 높이기 위해 캐시를 사용한다. 원하는 데이터가 캐시에 존재하는 경우 해당 데이터를 반환하며 이러한 경우를 Cache Hit 라고 한다. 하지만 반대로 원하는 데이터가 캐시에 존재하지 않을 경우는 DBMS 또는 서버에 요청을 해야하며 이러한 경우를 Cache Miss 라고 한다. 캐시는 위에서 설명한 것과 같이 저장 공간이 작기 때문에, 캐시 미스가 많이 발생할 수 있는 경우 캐시 전략에 따라 저장중인 데이터를 변경해야 한다.
📎 Cache 전략
캐시 전략을 잘 정리해둔 블로그가 있어 가져와 보았다.
캐시를 효율적으로 이용하기위해서는 캐시에 저장할 데이터 특성도 고려해야 한다.예를들어 자주 조회되는 데이터, 결과값이 자주 변동되지 않고 일정한 데이터, 조회하는데 연산이 필요한 데이터를 캐싱해두면 좋다.
출처
//REDIS
implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.2.0'
// // cache
implementation 'org.springframework.boot:spring-boot-starter-cache'
참고 블로그에 따르면 cache 설정은 따로 추가해주지 않아도 spring-boot-starter-web 와 같은 모듈에 자동으로 포함되어 있는 Spring-context 라는 모듈 덕분이라고 한다.
spring.data.redis.host=localhost
spring.data.redis.port=6379
application.properties 에 다음과 같은 설정을 해준 후 RedisConfig 클래스를 작성해준다. 캐시 관련 설정이다.
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3L)); // 캐쉬 저장 시간 3분 설정
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(cf)
.cacheDefaults(redisCacheConfiguration)
.build();
}
}
- 캐시 읽기 + 쓰기 전략 조합
- Look Aside + Write Around 조합
- 가장 일반적으로 자주 쓰이는 조합
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PortfolioService {
private final PortfolioRepository portfolioRepository;
private final UserRepository userRepository;
(생략)
@Cacheable(value = "portfolio", key = "'all'")
public List<PortFolioDto.PortfolioResponseDto> getAllPortfolios() {
List<PortFolio> portFolios = portfolioRepository.findAll();
return portFolios.stream()
.map(PortFolioDto.PortfolioResponseDto::new)
.collect(Collectors.toList());
}
}
@Cacheable
- 데이터를 캐시에 저장
- 메서드를 호출할 때 캐시의 이름 (value) 과 키 (key) 를 확인하여 이미 저장된 데이터가 있으면 해당 데이터를 리턴
- 만약 데이터가 없다면 메서드를 수행 후 결과값을 저장
캐시를 적용하였을 때와 적용하지 않았을 때를 비교하기 위해 테스트 코드를 작성하여 확인해 보았다.
<캐시 적용 x vs 캐시 적용 O 비교 테스트 코드>
@Test
public void testGetAllPortfoliosWithoutCache() {
// Redis 캐시 비우기
Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection().serverCommands();
// 캐시 없이 첫 번째 조회 시간 측정
long startTime = System.currentTimeMillis();
portfolioService.getAllPortfolios();
long endTime = System.currentTimeMillis();
System.out.println("캐시를 적용하지 않았을 때 시간: " + (endTime - startTime) + " ms");
// 캐시 없이 두 번째 조회 시간 측정
startTime = System.currentTimeMillis();
portfolioService.getAllPortfolios();
endTime = System.currentTimeMillis();
System.out.println("캐시를 적용하지 않았을 때 시간 (두번 째 호출 시간): " + (endTime - startTime) + " ms");
}
@Test
public void testGetAllPortfoliosWithCache() {
// 첫 번째 조회 시간 측정 (캐시 생성)
long startTime = System.currentTimeMillis();
portfolioService.getAllPortfolios();
long endTime = System.currentTimeMillis();
System.out.println("캐시를 적용했을 때 시간(첫번 째 호출 시간): " + (endTime - startTime) + " ms");
// 두 번째 조회 시간 측정 (캐시 사용)
startTime = System.currentTimeMillis();
portfolioService.getAllPortfolios();
endTime = System.currentTimeMillis();
System.out.println("캐시를 적용했을 때 시간(두번 째 호출 시간): " + (endTime - startTime) + " ms");
}
캐시를 적용하지 않았을 때
캐시를 적용하였을 때
Redis 확인
결과를 보면 캐시를 적용하였을 때 두번 째 조회부터 확실히 조회 시간이 줄어든 것을 확인해볼 수 있었다. 하지만 결과를 잘 보면 의문인 점이 있었다.
처음에는 별 이유가 없는건가 해서 다시 조회를 해봐도 같은 결과가 나왔다. 생각을 해보았을 때 아마 처음에 Redis 에 저장을 하는 과정 때문에 더 오래 걸리는건가 했지만 확실하지 않기 때문에 이유를 알아보았다.
첫번째 호출의 경우 캐시를 적용했을 때가 더 오래걸리는 이유
->캐시를 사용할 때 추가적인 캐시 초기화 및 저장 작업이 발생하기 때문입니다. 첫 번째 호출 시 캐시는 비어 있고, 데이터베이스에서 데이터를 조회한 후 캐시에 저장하는 작업이 추가되기 때문에 시간이 더 걸릴 수 있습니다.