최종프로젝트 주제로 e커머스를 설계를 하게 되면서
조회 api 사용시 응답속도 개선 및 변경될 일이 없거나 변경 기회가 적은 데이터에 대한 서버와 DB 와의 트래픽 의 비용감소 및 최적화 목적으로 캐싱을 적용을 하려고 한다.
기본적으로 캐싱 전략에 대해 알아야한다.
캐싱 전략
웹 서비스 환경에서 시스템 성능 향상을 기대할 수 있는 중요한 기술로
일반적으로 캐시는 메모리(RAM)을 사용하기 때문에 기존 데이터 베이스보다 훨씬 빠르게 데이터를 응답 할 수 있어 이용자에게 빠르게 서비스를 제공할 수 있다.
하지만 기본적으로 RAM은 용량이 커봐야 16~32G 정도로 데이터를 모든 캐시에 저장해버리면 용량 부족현상이 일어나 시스템이 다운될 경우가 있다.
따라서 어느 종류에 대한 데이터를 캐시에 저장할지, 얼마만큼 데이터를 저장할지 , 얼마나 오래된 데이터를 캐시에서 제거하는지에 대한 지침전략을 숙지해야한다.
팁
캐시를 효율적으로 이용하기위해서는 캐시에 저장할 데이터 특성도 고려
자주 조회되는데이터, 결과값이 자주 변동되지 않고 일정한 데이터, 조회하는데 연산이 필요한 데이터를 캐싱해두면 좋다.
참고로 전략을 수립하는데 있어 기본적으로 들어가야할 선수지식이 필요한데 redis로 예를들면
cache hit와 cahe miss 이다.
cache hit : 캐시 스토어(redis)에 데이터가 있을경우 바로 가져옴(느림)
cache miss : 캐시 스토어(redis)에 데이터가 없을 경우 어쩔 수 없이 db에 가져옴(빠름)
이걸 알고 전략 지식을 얻은다음에 전략을 세워서 하면 될것 같다.
캐시 전략의 종류와 조합은 여길 참고하면 좋을 것같다.
https://inpa.tistory.com/entry/REDIS-%F0%9F%93%9A-%EC%BA%90%EC%8B%9CCache-%EC%84%A4%EA%B3%84-%EC%A0%84%EB%9E%B5-%EC%A7%80%EC%B9%A8-%EC%B4%9D%EC%A0%95%EB%A6%AC#look_aside_+_write_around_%EC%A1%B0%ED%95%A9
나는 유저정보 조회 , 이벤트 정보 조회 업데이트 등에 사용하기 위해 읽기 쓰기 전략을 LookAside + wirte around로 적용 하였다.
기본적으로 캐싱을 하기위해선 Redis설정을 한뒤에 RedisCache 설정을 해주어야한다.
스프링부트 3.x 버전기준
Redis 설정
build.gradle에 redis 의존성을 추가해준다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
빌드를 해준 후 RedisConfig라는 클래스를 만들어 Redis와 연결하기위한 설정을 해준다.
RedisConfig
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}") // redis 주소
private String redisHost;
@Value("${spring.data.redis.port}") //redis 포트
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort));
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());//key직렬화
redisTemplate.setValueSerializer(new StringRedisSerializer());//value직렬화
redisTemplate.setHashKeySerializer(new StringRedisSerializer());//key 역직렬화
redisTemplate.setHashValueSerializer(new StringRedisSerializer());//value 역직렬화
redisTemplate.setConnectionFactory(redisConnectionFactory()); // redis 연결
return redisTemplate;
}
}
RedisCacheConfig
@EnableCaching // 캐싱을 적용한다는 어노테이션
@Configuration
public class RedisCacheConfig {
//Bean로 캐시매니저 설정을 설정했다.
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder()
.transactionAware()
.cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.withCacheConfiguration("userCache", userCacheConfig()) //설정을 추가해줄 수있다.
.withCacheConfiguration("eventCache", eventCacheConfig())
.cacheDefaults(defaultCacheConfig()) // 기본 캐시 설정
.build();
}
/**
* Key 직렬화 설정
*/
@Bean
protected RedisSerializationContext.SerializationPair<String> keySerialization() {
return RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer());
}
/**
* Value 직렬화 설정
*/
@Bean
protected RedisSerializationContext.SerializationPair<Object> valueSerialization() {
return RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()); //json형태를 직렬화한다.
}
//기본 캐시설정
@Bean
protected RedisCacheConfiguration defaultCacheConfig() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) //TTL 설정
.serializeKeysWith(keySerialization()) //key 직렬화
.serializeValuesWith(valueSerialization()) //value 직렬화
.disableCachingNullValues();
}
//추가설정한 캐시설정
@Bean
protected RedisCacheConfiguration userCacheConfig() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.serializeKeysWith(keySerialization())
.serializeValuesWith(valueSerialization())
.disableCachingNullValues();
}
@Bean
protected RedisCacheConfiguration eventCacheConfig() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.serializeKeysWith(keySerialization())
.serializeValuesWith(valueSerialization())
.disableCachingNullValues();
}
}
캐시 설정을 완료했다면 캐시 적용은 스프링에서 지원하는 캐시 추상화 어노테이션을 이용하면 간단하게 return 값을 key value 형태로 redis에 적용 시킬 수 가 있다.
클래스나 인터페이스에도 캐시를 지정할 수 는 있지만 이렇게 작업하는 경우는 드물고, 메소드 단위로 적용을 한다고 한다.
@Cacheable과 @CachePut 그리고 @CacheEvict 어노테이션을 통해 캐시를 제어할 수 있다. 이러한 어노테이션에 대해 알아보겠다.
@Cachable
캐시에 데이터가 없을 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가하고, 캐시에 데이터가 있으면 캐시의 데이터를 반환
메소드가 파라미터가 없으면 0이라는 디폴트 값을 key값으로 저장
파라미터들의 hashCode 값을 조합하여 키를 생성한다고 한다.
@Cacheable과 같이 쓸 속성에 대해서 알아보겠다.
value
이 속성은 메소드의 결과가 저장되어야 하는 캐시의 이름을 지정하는 데 사용된다.
단일 캐시일 수도 있고 쉼표로 구분된 여러 캐시일 수도 있습니다.
@Cacheable("myCache")
public String getData() {
// Method implementation
}
@Cacheable({"cache1", "cache2"})
public String getData() {
// Method implementation
}
// 이 예시로는 1,2 에 모두 저장된다.
key
속성은 캐싱을 위한 사용자 정의 키를 지정하는 데 사용된다.
기본적으로 Spring은 메서드 매개변수를 기반으로 키를 생성한다.
SpEL(Spring Expression Language)을 사용하여 사용자 정의 키 생성 논리를 정의할 수 있다.
@Cacheable(value = "myCache", key = "#id")
public String getDataById(Long id) {
// Method implementation
}
condition
이 속성은 캐싱이 발생하기 위해 충족해야 하는 조건을 지정하는 데 사용된다.
캐싱은 조건이 true로 평가되는 경우에만 발생한다.
@Cacheable(value = "myCache", condition = "#result != null")
public String getData() {
// Method implementation
}
//메서드 결과가 null이 아닌 경우에만 캐싱이 발생한다.
unless
이 속성은 '조건'과 반대입니다.
캐싱이 발생하기 위해 충족되어서는 안 되는 조건을 지정한다.
조건이 true로 평가되면 캐싱이 발생하지 않는다.
@Cacheable(value = "myCache", unless = "#result == null")
public String getData() {
// Method implementation
}
// 메서드 결과가 null이면 캐싱이 발생하지 않는다.
keyGenerator
기본 키 생성 전략을 사용하는 대신 캐시 키를 생성하는 사용자 지정 키 생성기 클래스를 지정하는 데 사용된다.
@Cacheable(value = "myCache", keyGenerator = "customKeyGenerator")
public String getData() {
// Method implementation
}
@CachePut
캐시 내용 수정 담당
메서드 실행에 영향을 주지 않고 캐시를 갱신해야 하는 경우에사용.
@CacheEvict
캐시 삭제를 담당. 값이 달라지는 경우, 잘못된 값을 반환하는 것을 막기 위해 캐시가 제거되어야 한다.
캐시 이름을 넣어주면 메소드가 실행될 때 캐시의 내용이 제거
메소드의 키에 해당하는 캐시만 제거
다음내용에 적용한 내용 및 적용전 적용 후의 성능향상에 대해 알아보겠다.
'내일배움캠프' 카테고리의 다른 글
내일 배움 캠프 최종 프로젝트 - GCP VM mobaXterm 접속 (0) | 2024.04.10 |
---|---|
내일 배움 캠프 최종 프로젝트 - CI/CD 설계 전 서버 구축(GCP Computing engine) (0) | 2024.04.09 |
내일배움캠프TIL-Docker를 활용해 MySQL을 컨테이너로 띄워 사용하자 (0) | 2024.03.13 |
내일 배움 캠프 TIL -QueryDSL을 사용해서 수정일 순으로 정렬해서 가져오자 (0) | 2024.03.11 |
내일배움캠프TIL - @RestControllerAdvice (0) | 2024.02.14 |