일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- fetch
- JPQL
- eager
- exclusive lock
- 다대다
- 일대다
- 즉시로딩
- 이진탐색
- BOJ
- 다대일
- 비관적락
- querydsl
- shared lock
- 힙
- CHECK OPTION
- 지연로딩
- 연결리스트
- 백트래킹
- 연관관계
- PS
- 유니크제약조건
- 동적sql
- execute
- 데코레이터
- 낙관적락
- SQL프로그래밍
- 스프링 폼
- FetchType
- dfs
- 스토어드 프로시저
- Today
- Total
흰 스타렉스에서 내가 내리지
메서드 호출 결과를 캐시에 저장하는 @Cacheable 과 @CacheEvict 본문
빈번하게 호출되는 API에 대해서, 매번 서비스 로직을 실행하고 응답하는 것은 비효율 적일 수 있다. (응답값이 같다는 가정 하에)
API 의 응답 결과를 Elasticache for Redis 를 이용하여 응답 값을 캐싱해주면 어떨까?
# API Caching ?
- API 의 호출에 따라 캐시를 이용하여, 서버의 부하를 줄이고 API 성능을 최적화 하여 응답시간을 단축시키는 역할을 한다.
- 데이터를 메모리에 저장하여, 빠른 검색을 가능하게 하는 기능을 제공한다.
# Cache 사용 목적
- DB 로부터 데이터를 조회하는 경우, 동일한 데이터를 반복하여 조회함으로써 불필요한 일을 반복하는 문제가 발생한다.
- 캐시를 통해, DB 로부터 반복적으로 데이터를 조회해 오는 일에 대해, 최초 데이터를 조회해 온 뒤 이후는 캐시에서 데이터를 조회해 오는 처리를 수행함으로써 API 의 성능을 올리며 응답시간을 단축하는 효율성을 가져올 수 있다.
# Cache 어노테이션
Annotation | 설명 |
@EnableCaching | Cache 를 사용하기 위해 '캐시 활성화' 를 위한 어노테이션 |
@CacheConfig | 캐시정보를 '클래스 단위' 로 사용하고 관리하기 위한 어노테이션을 의미한다. (주로 @Service 를 선언한 인터페이스의 구현체 부분에서 함께 선언하여 사용한다) |
@Cacheable | 캐시정보를 메모리 상에 '저장'하거나 '조회'해오는 기능을 수행 (주로 @Service 를 선언한 인터페이스의 구현체 부분에서 함께 선언하여 사용한다) |
@CachePut | 캐시정보를 메모리 상에 '저장'하며 존재 시 갱신하는 기능을 수행 (") |
@CacheEvict | 캐시정보를 메모리 상에 '삭제'하는 기능을 수행 |
@Caching | 여러 개의 '캐시 어노테이션'을 '함께 사용'할 때 사용하는 어노테이션 |
Annotation | 주요 기능 | 캐시 실행 시점 |
@Cacheable | 캐시 조회, 저장 기능 | - 캐시 존재 시 '메서드 호출 전 실행' - 캐시 미 존재 시 '메서드 호출 후 실행' |
@CachePut | 캐시 저장 기능 | - 캐시 존재 시 '메서드 호출 후 실행' - 캐시 미 존재 시 '메서드 호출 후 실행' |
@CacheEvict | 캐시 삭제 기능 | - beforeInvocation 속성값이 true 일 때 '메서드 호출 전 실행' - beforeInvocation 속성값이 false 일 떄 '메서드 호출 후 실행' |
# @CacheEvict 의 beforeInvocation?
- 기본값은 false
- 메서드 실행 전에 캐시에서 데이터를 삭제하여, 예외가 발생하면 캐시에서 데이터가 삭제되지 않음
- 예외가 발생할 가능성이 있으면 true 를 사용하지 말 것
# @Cacheable 과 @CachePut 의 차이는?
- @Cacheable 은 '캐시가 존재하지 않을 경우' 캐시를 저장하지만,
@CachePut 의 경우는 '캐시의 존재 여부를 떠나서' 항상 저장 혹은 갱신을 수행합니다.
캐시로 사용하면 좋을 값
1. 조회가 빈번히 발생하나, 수정은 거의 발생하지 않는 값
2. 조회에 오랜 시간이 발생하는 값
# RedisCacheConfiguration 설정
@EnableCaching
@Configuration
public class RedisCacheConfig {
@Bean
@Primary
public CacheManager studentIdCacheManager(RedisConnectionFactory redisConnectionFactory) {
// Student Id 기반 조회는 30초로 설정
RedisCacheConfiguration redisCacheConfiguration = generateCacheConfiguration()
.entryTtl(Duration.ofSeconds(30L));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
}
@Bean
public CacheManager refreshTokenCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = generateCacheConfiguration()
.entryTtl(Duration.ofDays(3));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
}
private RedisCacheConfiguration generateCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
- 사용 사례는 아래 곧 나옵니다!
- @Cacheable 어노테이션에서 쓰일 예정
# @Cacheable, @CacheEvict 적용
@Cacheable 어노테이션으로 해당 메서드의 반환 값을 캐싱할 수 있으며, @CacheEvict 어노테이션으로 해당 작업이 수행되면 캐시된 값을 삭제시키게 할 수 있다.
# Code
# EnableCaching
- 캐시를 사용하기 위해 필요한 어노테이션으로, Spring Boot 에서 캐싱을 활성화하는 데 사용되며, @Configuration 이 포함된 클래스에서 이를 적용한다.
# Domain : Student
- 간단하게 Student 를 조회하고, 생성하고, 수정하는 API 를 만들어 보았다.
- MySQL 의 Student 테이블에는 1000명의 데이터를 넣었다.
- value 값이 중복될 경우를 대비하여 key 값을 추가로 설정해 준다.
- redis-cli 에서 studentId::${id} 로 설정된다.
- 이제 Student 를 Id 기반으로 조회하면 먼저 O(1) 시간복잡도로 조회한다.
- In-Memory DB 이기 때문에 I/O 비용이 매우 적게 발생한다.
- 만약 Redis 서버에서 캐시 데이터를 발견하지 못하면 실제 DB 에 I/O 작업을 통해 데이터를 가져오고, 해당 데이터를 캐싱하여 TTL 기간만큼 캐시로 저장한다.
- Id 400 을 가진 학생을 최초 조회하였다.
- 응답에 87ms 소요되었다.
- Redis 서버 조회 결과, 응답 값이 캐시에 잘 저장된 것을 볼 수 있다.
- 우리가 의도한 바대로라면, 재 요청을 했을 시 응답시간이 감소해야한다.
- 응답 시간이 18ms 까지 현저하게 감소한 것을 확인할 수 있었고, RDB 로의 I/O 요청 (SQL쿼리) 역시 날아가지 않았다.
- TTL 을 30초로 설정해 두었기 때문에, 30초 뒤에는 Redis 서버에서 데이터가 삭제된다.
- 그리고 다시 API 요청을 하면, RDB 에 데이터를 요청해서 불러올 것이기 때문에, 시간이 더 오래 걸릴 것이다.
# Cache 된 30초 사이에 데이터가 변경되었다면?
- Get 요청을 통해 Cache 한 다음, Put 요청을 통해 데이터를 수정하고, 다시 Get 요청을 해본다.
- 당연히 Spring 애플리케이션은 Redis 에 id 400 에 대한 Student 데이터가 존재하는 한 (30초) 변경 전 데이터를 응답할 것이다.
- 따라서 변경 트리거가 발생할 API 에는, 기존 캐시 데이터가 삭제되도록 @CacheEvict 어노테이션을 설정해준다.
- 수정요청을 보내면 캐시가 사라지는 지 확인해보자
- 잘 될것임.
# Null key returned for cache operation 에러
위에 글 작성할 때 실행했던 데모 앱에서는 잘 됐었는데, 배포중인 서버에 연결하려고 하니 발생한 에러였다.
그러니까 @Cacheable 어노테이션의 key 값에 null 이 들어간다는 것이었다.
파라미터의 n번째를 뜻하게 #p0, #p1 등으로 수정하니 잘 해결되었다.
캐싱을 하니 응답속도가 확실히 빨라졌다!
'Spring' 카테고리의 다른 글
Spring WebFlux (2) | 2024.06.10 |
---|---|
프록시 동등성 비교 - equals() 와 hashCode() 오버라이딩 (0) | 2024.04.28 |
Redis 를 사용하여 Refresh Token 구현하기 (0) | 2024.03.28 |
JPQL 로 ORDER BY RAND() LIMIT N 사용하기 (0) | 2024.03.12 |
쿼리를 5,000번 날리는 API가 있다?😬 쿼리 줄이기 8시간 삽질 후기 (0) | 2024.02.15 |