[Spring] Caffeine

나르·2022년 2월 20일
0

Spring

목록 보기
13/25
post-thumbnail

Intro.

프로젝트 막바지... 이제 웬만한 로직 구현은 끝냈고 성능 개선을 위해 이것 저것 찾아보던 중 팀원이 말했습니다.

Caffeine 적용해보는 건 어때요?? 🤔

띠용때용?? 그게 먼데여?? 로컬 캐시?? 레디스랑 달라요???

그래서 시도하게 된 로컬 캐시 적용기 (별거없음)
캐시는 떡칠을 하되 조심해서 사용해라....


Caffeine?

Caffeine은 Java 8을 기반으로 하는 High Performance 캐싱 라이브러리입니다.
다음과 같은 특징이 있습니다.

  • max size 설정해두고, 해당 사이즈가 넘어갈 경우 eviction(내보내기)
  • 마지막 접근(read based) 혹은 최초 쓰기(write based) 에 따라 만료 시간 설정 가능
  • refreshAfterWrite 로 자동 refresh 가능
  • key, values 가 자동적으로 weak reference로 wrap되기 때문에 GC를 통해 삭제 가능
  • cache access 에 대한 statistics 제공하므로 모니터링 가능

스프링에서도 지원해주는 캐시입니다.

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 서비스에서는 셀럽의 게시글 등에 적용할 수 있을 것 같습니다. 조회수는 어마어마하면서 수정은 적은..?

더불어 캐싱은 정말 조심해서 써야한다고 시니어분이 말씀해주셨는데, 그러면서 의외로 정말 짧게 몇 초라도 캐싱해두는 것들도 많다고 해서 처음엔 의아했습니다.
뒤늦게 알고보니 실제로는 떡칠해서 사용하는 경우가 많고... 그만큼 관리를 잘 해야한다는 뜻이 아니었나 싶네요.
역시 캐시의 세계는 재밌습니다.

Ref.

Introduction to Caffeine
Spring docs 33. Caching
What is the difference between Redis and Caffeine?
https://wave1994.tistory.com/182
Spring boot 에 caffeine 캐시를 적용해보자 - 어떻게하면 일을 안 할까?

profile
💻 + ☕ = </>

0개의 댓글