트립드로우 팀 프로젝트를 진행하고 있습니다.
트립드로우는 사용자에게 여행과 감상 기록에 대해 검색 기능을 제공합니다.
검색을 할 때는 주소, 시간 등을 조건으로 지정할 수 있습니다.
주소를 지정할 때는 시/도
, 시/군/구
, 읍/면/동
데이터를 기반으로 합니다.
이러한 데이터는 서버 DB에 Area 데이터로 저장되어 있으며, 클라이언트는 사용자에게 주소 조건 검색 기능을 제공할 때 전체 Area 조회 요청
을 보냅니다.
이 요청을 통해 총 3518개 Area 데이터가 반환됩니다.
백엔드는 전체 Area 조회 요청
에 대해 캐싱 작업을 진행했습니다.
왜 캐싱을 했는지, 어떻게 캐싱을 했는지, 그래서 무엇이 달라졌는지 하나씩 살펴보겠습니다.
캐싱(Caching)이란 캐시라는 작업을 하는 행위를 의미합니다.
캐시(cache)는 컴퓨터 과학에서 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다. 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간없이 더 빠른 속도로 데이터에 접근할 수 있다. wiki
캐싱을 하면 데이터가 미리 복사되어 있어, 더 빠른 속도로 접근할 수 있습니다.
전체 Area 조회 요청
을 통해 반환되는 Area 데이터는 다음과 같은 특징을 갖습니다.
이러한 측면에서 캐싱을 적용하는 것이 유리하다고 판단했습니다.
캐싱은 크게 Local Caching 와 Global Caching 으로 나눌 수 있습니다.
두 캐싱 방법은 아래와 같은 특징을 갖습니다.
Local Cachcing
Global Cachcing
Local Caching을 선택했을 때 서버 확장에 따른 데이터 정합성 문제는 상대적으로 해결 방법이 쉽게 떠올랐습니다.
현재 Area 데이터는 외부 API 를 사용해 DB에 저장하고 있습니다.
주소가 변경되었을 때 Area 데이터를 새로 받아와 DB에 저장하고, 그때 Cache를 비우면 될 것으로 판단했습니다.
한편, 단순히 전체 Area 조회 요청
에 캐싱을 적용하기 위해 별도의 infra를 구축하는 것이 과도하다고 판단했습니다. infra 작업에 소요될 시간과 학습비용이 크다고 예상했기 때문입니다.
따라서 Local Caching을 적용하기로 선택했습니다.
Spring의 공식문서를 살펴보면, Spring은 개발자가 특정 기술에 종속되지 않고 캐시를 구현할 수 있도록 추상화된 인터페이스 등을 제공합니다.
implementation 'org.springframework.boot:spring-boot-starter-cache'
spring-boot-starter-cache
의존성을 추가해서 캐싱 기능을 사용하면 됩니다!
캐싱을 어떻게 구현할지 고민하면서 여러 라이브러리를 살펴보았습니다.
선택 시 고민했던 선택지는 EhCache 와 Caffeine 두 가지입니다.
EhCache 는 오랜 기간 많은 사람들에게 사용된 정석같은 라이브러리 입니다. Caffeine은 비교적 최근에 떠오르는 신입 라이브러리입니다.
두 라이브러리를 살펴보면서 다음과 같은 근거를 찾았습니다.
현재 팀 프로젝트에는 EhCache의 다양한 기능을 필요로 하지 않습니다. 또한 필요한 설정에 대해 xml 파일을 추가하기보다 코드 단에서 설정하고 싶습니다.
이러한 의사결정 끝에 Caffeine을 적용하기로 했습니다.
Spring cache와 Caffeine의 조합으로 캐싱을 진행했습니다.
Caffeine을 사용하기 위해 의존성을 추가해주었습니다.
implementation 'com.github.ben-manes.caffeine:caffeine'
@Configuration
을 통해 CaffeineCache를 포함한 CacheManager를 Bean 으로 등록합니다.
@Cacheable
, @CacheEvict
은 Spring Cache 에서 제공하는 애노테이션입니다.
@Transactional
과 같이 AOP 개념이 적용되었으며, 다음과 같이 동작합니다.
-@Cacheable
: 메서드에 반환값을 캐싱합니다. 캐시에 데이터가 있을 경우 메서드를 실행하지 않고 캐시의 데이터를 반환합니다. cacheName을 기준으로 캐싱합니다.
-@CacheEvict
: 메서드가 호출될 때 cacheName에 해당하는 캐싱 데이터를 삭제합니다.
AreaService 에서 readAll 메서드를 실행 시 데이터를 캐시할 수 있도록 설정했습니다.(@Cacheable)
만약 새로운 Area 데이터가 생성될 경우 캐시를 삭제하도록 했습니다.(@CacheEvict)
캐싱을 하기 전 전체 Area 조회 요청
을 처리하는 시간은 100 ~ 120ms 이 걸렸습니다.
캐싱을 적용한 후에는 처리 시간이 14 ~ 18ms 를 기록했습니다.
캐싱을 적용 후 조회 요청이 매우 빨라졌습니다!