💡문득 똑같은 데이터를 페이지 호출 시 마다 DB에 접근해서 가져온다면, 성능 이슈가 있을 수 있지 않을까? 라는 생각이 들었다.(데이터 수가 많을 때)
그래서 In-memory 기반의 데이터베이스인 Redis를 사용해서 데이터를 저장하고 불러온다면 성능 개선을 할 수 있을 것 같았다. (TTL 설정을 통한 휘발성 데이터 저장)
또한, Springboot에서는 Redis와 연동하기 위한 library도 지원해주고, cache 기능도 지원한다고 한다.
Springboot에서 Redis를 활용하여 cache 기능을 설계하기 위한 전략은 [Redis] 캐시 설계 전략 지침을 참고하자.
이제 아래를 통해 Spring boot에 Redis를 연동하고, cache도 사용해보도록 하자!
1. 레디스 연동하기(의존성 주입)
implementation 'org.springframework.boot:spring-boot-starter-data-redis’
Gradle이라면 build.gradle에 해당 내용을 추가하여 Redis 의존성을 추가해준다.
그리고 application.yml(application.properties) 파일에 아래 내용을 추가해준다.(host와 port는 각자 알아서→ redis 기본포트는 6379임)
spring:
cache:
type: redis # 스프링에서 캐시 사용을 redis로 하겠다 설정
data:
redis:
host: localhost
port: 6379
1-1. RedisConfig 설정하기
💡 Tip
Spring boot 2.0부터 RedisTemplate와 StringTemplate를 자동생성 되어서 따로 빈에 등록안해도 된다고 한다.
RedisTemplate에는 serializer를 설정해주는데 설정하지 않는 다면 직접 redis-cli로 데이터 확인이 어렵다
@EnableCaching 설정을 통해 Spring Boot에 캐싱 사용을 한다고 알린다.
@Configuration
public class RedisConfig {
private final String redisHost;
private final int redisPort;
public RedisConfig(@Value("${spring.data.redis.host}") final String redisHost,
@Value("${spring.data.redis.port}") final int redisPort) {
this.redisHost = redisHost;
this.redisPort = redisPort;
}
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration(redisHost, redisPort));
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
// redisTemplate를 받아와서 set, get, delete를 사용
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
/**
* setKeySerializer, setValueSerializer 설정
* redis-cli을 통해 직접 데이터를 조회 시 알아볼 수 없는 형태로 출력되는 것을 방지
*/
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean(name = "cacheManager")
public CacheManager cacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.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();
}
}
2. 메서드에 캐시 적용하기
@Cacheable 은 캐시가 없다면 등록하고, 있다면 해당 캐시의 데이터를 가져오는 어노테이션이다.
캐시 적용을 원하는 메서드에 등록해보자.
// 회원정보 전체조회(findAll)
@Cacheable(cacheNames = "getAccount", key = "'ALL'", cacheManager = "cacheManager")
public List<AccountDto> findAll() {
List<AccountDto> accountDto = accountRepository.findAll();
return accountDto;
}
이렇게 하면 캐시 적용은 완료되었다.
API를 호출하여 Redis에 잘 반영됐는지 확인해보자.
3. 결과
1. 처음 API를 호출하였을 때 걸리는 시간: 88ms
처음 호출 시엔 DB에 접근하기 때문에, 조회 쿼리도 보이는 상황이다.
2. 두 번째 호출부터 걸리는 시간: 11ms
두 번째 쿼리부터는 DB가 아닌, Redis에 저장된 캐시(Cache)에서 데이터를 가져오기 때문에 조회쿼리가 보이지 않는다.
위의 내용은 데이터 조회를 위한 캐시 전략이었다.(Look Aside + Write Around)
- Look Aside(읽기 전략) - 캐시를 먼저 확인하고, 캐시에 데이터가 없을 경우 DB에서 데이터를 가져오는 전략
- Write Around(쓰기 전략) - 데이터를 업데이트 할 때마다 바로 캐시에 반영하지 않고, 변경된 내용만 DB에 반영하고 해당 캐시는 제거 또는 무효화 하는 전략
다음 포스팅은 아래 내용에 따라 캐시 삭제하는 법을 알아보겠다.
💡 redis cache를 사용하면 조회 시 캐시를 읽어오기 떄문에 DB에 저장된 데이터가 수정, 추가, 삭제 될 때마다 Cache 또한 삭제해주어야 한다.
데이터가 변경될 때마다 캐시를 비워주지 않으면 캐시 DB(Redis)와 하드 DB(MySQL)의 데이터가 서로 다른 데이터를 갖고 있어 캐시DB에서 데이터를 꺼내 올 때 변경되기 전인 오래된 정보를 사용하는 데이터 정합성 문제점이 발생한다.
'Languages | Frameworks > Spring' 카테고리의 다른 글
[QueryDSL] N + 1 현상 해결 과정 (0) | 2023.07.26 |
---|---|
@ControllerAdvice를 통한 예외처리 분리, 통합하기 (0) | 2022.12.20 |
[IntelliJ] 자주쓰는 단축키 정리 (0) | 2022.11.21 |
[Springboot] spring-security 적용기(2) - OAuth2 구글 소셜 로그인 (2) | 2022.11.02 |
@Autowired, @Component, @Service, @Repository 등 스프링 어노테이션에 관해.. (0) | 2022.11.02 |