Intro.
프로젝트 막바지... 이제 웬만한 로직 구현은 끝냈고 성능 개선을 위해 이것 저것 찾아보던 중 팀원이 말했습니다.
Caffeine 적용해보는 건 어때요?? 🤔
띠용때용?? 그게 먼데여?? 로컬 캐시?? 레디스랑 달라요???
그래서 시도하게 된 로컬 캐시 적용기 (별거없음)
캐시는 떡칠을 하되 조심해서 사용해라....
Caffeine?
Caffeine
은 Java 8을 기반으로 하는 High Performance 캐싱 라이브러리입니다.
다음과 같은 특징이 있습니다.
refreshAfterWrite
로 자동 refresh 가능스프링에서도 지원해주는 캐시입니다.
Redis vs. Caffeine
그럼 Redis와는 어떻게 다를까요?? 간략하게 결론만 말하자면 아래와 같습니다.
레디스는 인메모리에, 카페인은 로컬에 데이터를 저장합니다.
Caffeine
은 Local cache로, 서버의 자원을 바로 사용하니 네트워크 트래픽을 유발하지 않아 처리속도가 빠릅니다.
허나 "Local"인 만큼 scale out 시 타 서버와 공유가 안되기 때문에 서버 간의 정합성 문제가 생길 수 있습니다.
Redis
는 Global cache로, 별도의 캐시 서버를 운영하는 방식입니다.
여러 서버가 같은 서버를 참조할 수 있으므로 정합성 문제는 해결되나, 네트워크 트래픽을 발생시키므로 로컬 캐시에 비해 처리 속도가 느립니다.
Quick Start
우리는 아직까지 스케일 아웃 계획이 없으니 일단 적용해보기로 했습니다.
카페인을 사용하기 위해서는 아래 두 의존성이 필요하므로 추가해줍니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
}
Cache 설정을 가진 Enum을 생성해줍니다.
캐시 이름, 만료 시간, 저장 가능한 최대 갯수 등을 정의해줍니다.
@Getter
@RequiredArgsConstructor
public enum CacheType {
USER_PROFILE("userProfile", 10, 10000),
POST_COMMENTS("postComments", 20, 10000);
private final String cacheName;
private final int expiredAfterWrite;
private final int maximumSize;
}
Config 클래스를 만들어 @EnableCaching
어노테이션으로 캐싱을 활성화해주고, CacheType 에 등록한 캐시들을 Caffeine 캐시 객체로 생성 후 SimpleCacheManager 객체에 등록해줍니다.
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
List<CaffeineCache> caches = Arrays.stream(CacheType.values())
.map(cache -> new CaffeineCache(cache.getCacheName(), Caffeine.newBuilder().recordStats()
.expireAfterWrite(cache.getExpiredAfterWrite(), TimeUnit.SECONDS) // 초,분,시,일...등 가능
.maximumSize(cache.getMaximumSize())
.build()
)
)
.collect(Collectors.toList());
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
}
완성입니다! 이제 사용할 메서드 위에 @Cacheable(cacheNames="cacheName")
으로 캐시 이름을 지정해주기만 하면 캐시를 적용할 수 있습니다!!!
@Cacheable(cacheNames = "postComments")
public List<CommentResponseDto> getCommentList(Long postId) {
return commentRepository.findAllByPostId(postId)
.stream().map(CommentResponseDto::new)
.collect(Collectors.toList());
}
Outro.
생각보다 정말정말 간단히 적용 가능해서 바로 사용해봤습니다 🙂
적용 이후 실제로 조회 요청 테스트를 했을 때, 매 요청마다 DB에 쿼리를 날리던 이전과 달리 초기 한 번만 조회해두면 expire까지는 조회 쿼리가 날아가지 않는 것을 확인할 수 있었습니다!
허나 당연하게도 expire까지 변경사항 적용이 안된다는 뜻이니... 변경 사항을 즉시 반영할 필요가 없으면서 조회가 많은 데이터에 사용하면 좋을 것 같다고 생각되네요.
(테스트 서버에 얘기 안하고 뿌렸다가 클라 개발자들이 갑자기 댓글 안달린다고 해서 깜짝 놀랬습니다. 알고보니 캐시...)
ex. 진행하던 SNS 서비스에서는 셀럽의 게시글 등에 적용할 수 있을 것 같습니다. 조회수는 어마어마하면서 수정은 적은..?
더불어 캐싱은 정말 조심해서 써야한다고 시니어분이 말씀해주셨는데, 그러면서 의외로 정말 짧게 몇 초라도 캐싱해두는 것들도 많다고 해서 처음엔 의아했습니다.
뒤늦게 알고보니 실제로는 떡칠해서 사용하는 경우가 많고... 그만큼 관리를 잘 해야한다는 뜻이 아니었나 싶네요.
역시 캐시의 세계는 재밌습니다.
Introduction to Caffeine
Spring docs 33. Caching
What is the difference between Redis and Caffeine?
https://wave1994.tistory.com/182
Spring boot 에 caffeine 캐시를 적용해보자 - 어떻게하면 일을 안 할까?