우리 서비스에서 일관된 데이터를 불러올 때 쿼리가 나가거나 재계산하는 것이 거슬려서 이번에 스프링 캐시를 도입해봤다. 스프링은 캐시 기능을 제공한다. 스프링 캐시 기능은 트랜잭션처럼 AOP를 통해 구현했기에 어노테이션을 사용하면 된다.
일단 간단히 어노테이션만 추가해뒀다. 현업에서 EhCache, Redis, Memcached를 많이 사용하는 것 같은데 일단은 나중에 공부 및 개선하도록 하고, 이번에는 찍먹해볼 겸 어노테이션을 통한 기본기본 캐시만 해보겠다.
.
.
일단 평점이랑 메뉴정보만 캐시해뒀다.
곧 코드 구조도 리팩터링 예정이라.. 남은 리뷰캐시는 그때 다시 생각해보기로 한다.
이제 로그에 잡다한 쿼리문이 안 보여서 좋다.
🪄공부
✨ 캐시 저장소
두가지 구성방식이 있다. 하나는 JVM 로컬캐시에 embedded하는 방식, 다른 하나는 JVM 외부에 레디스같은 독립 메모리 저장소를 둬서 네트워크를 통해 캐시하는 방식이다. 전자는 로컬캐시, 후자는 원격캐시이다. 원격캐시는 I/O와 네트워크를 사용해야 하므로 비용이 있지만, 모든 애플리케이션이 같은 캐시를 공유할 수 있다.
✨ 캐시 매니저
스프링이 캐시 기능을 제공하긴 하지만, 캐시 데이터를 '관리'하는 기능은 별도의 캐시 프레임워크에 위임한다. 그래서 원하는 캐시 매니저를 빈으로 등록해서 사용한다. 흔하게 사용되는 Cache Manager 구현체는 EhCacheCacheManager인데, 이것은 자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저 구현체이다. 이 EhCache는 스프링 캐시의 대표격인 것 같다.
캐시 매니저에 대한 설정을 아무 설정도 안 하면 기본적으로 ConcurrentMapCacheManager를 사용한다. JRE에서 제공하는 ConcurrentHashMap을 캐시 저장소로 사용할 수 있다. 캐시 정보를 Map 타입으로 메모리에 저장해둬서 빠르다. 하지만 실서비스용으로 사용하기엔 기능이 빈약하다고 한다.
✨ 캐시 추가, 삭제
캐시를 추가하는 어노테이션은 @Cacheable, 삭제하는 어노테이션은 @CacheEvict다.
두 개 이상의 캐시 어노테이션을 조합해 사용하고 싶으면 @Caching( ~~~ ) 이렇게 써야 한다.
🪄적용
✨ 메뉴 정보 요청시 캐시응답 :
우리 서비스의 주된 프로세스는 한주마다 또는 하루마다 갱신되는 데이터를 제공하는 것이다.
지금까지는 새로고침할 때마다 DB에서 다시 불러오는 비효율이 있었다. 가장 먼저 여기에 API 캐시를 적용했다.
응답이 수정되는 상황은 "한주 데이터를 새로 불러왔을 때", "메인메뉴를 재설정했을 때"이다.
그때 @CacheEvict를 제공하여 메뉴관련 캐시를 다 날려주기로 했다.
[1] 한 주 데이터를 새로 불러올 때 CacheEvict
[2] 메인메뉴 재설정시 CacheEvict
✨ 평점 요청시 캐시응답:
우리 서비스에서 평점은 요청시마다 재계산하는 로직이다.
리뷰가 등록/삭제되지 않는 이상 이 평점은 일정하다.
그래서 평점을 캐시해두고, 리뷰 등록/삭제시 캐시를 삭제할 수 있도록 넣어뒀다.
지금 생각해보니까
평점 계산값 자체를 캐시해두고나서 리뷰등록/삭제시 캐시값을 업데이트하는 방식이 좋을 것 같다.
데이터를 캐시에 전부 저장해놓았다가 특정 시점마다 한번씩 캐시 내 데이터를 DB에 반영하는 식으로 개선하자.
이 방식을 write back이라고 하는데, 평점값은 아주 빈번한 요청이고, 리뷰값을 통해 언제든 재계산 가능하니까 write back방식으로 두는 것이 알맞아 보인다. (이거 말고 일반적인 캐싱 방법은 look aside cache라고 부른다)
지금은 리뷰 등록/삭제시 캐시를 삭제해버리니 어차피 평점재계산이 빈번한 것은 사실이다.
평점 조회시 캐시를 사용 : 평점 재계산 방지
@Cacheable(value="getTotalRestaurantRate", key="#restaurantName")
public RestaurantTotalRateResponseDto getTotalRestaurantRate(String restaurantName) { ... }
@Cacheable(value="getDetailRestaurantRate", key="#restaurantName")
public List<RestaurantDetailRateResponseDto> getDetailRestaurantRate(String restaurantName) { ... }
[1] 리뷰삭제시 CacheEvict
[2] 리뷰등록시 CacheEvict
🪄 사용 중인 CacheManager 확인하기
어떤 CacheManager를 사용하고 있는지 확인하려면 아래 코드를 실행해보면 된다.
import org.springframework.boot.CommandLineRunner;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
@Component
public class CacheManagerCheck implements CommandLineRunner {
private final CacheManager cacheManager;
public CacheManagerCheck(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public void run(String... strings) {
System.out.print("\n\n" + "=========================================================\n"
+ "Using cache manager: " + this.cacheManager.getClass().getName() + "\n"
+ "=========================================================\n\n");
}
}
나는 아직 아무 CacheConfig를 안해둬서 기본적으로 ConcurrentMapCacheManager를 사용하고 있다.
머지않아 EncacheCacheManager를 사용해보려고 한다.
그 후 Redis도 공부해볼 것이다.
'JAVA > Application' 카테고리의 다른 글
[Redis] cannot deserialize from Object value (SerializationException) (0) | 2024.10.27 |
---|---|
다량의 이미지 업로드 속도 최적화 / 트래픽 대비 / 비동기 처리 (0) | 2024.09.23 |
[Java][Backend] null 반환 리팩터링 (0) | 2024.03.08 |
[Spring] AOP로 편리하게 jwt token 검증하기 (0) | 2024.03.02 |
[Java] 시스템 환경변수는 System.getProperty가 아니라 System.getenv로 가져온다 (4) | 2024.02.22 |