우리는 API를 통해 조회한 데이터를 화면에 입혀 클라이언트에게 보여준다.
그런데 변경이 잘 되지 않으면서 자주 조회되는 데이터가 있다면 매번 api를 요청 할 때 마다 쿼리를 날리는 것이 서버에서는 부담이 될 수 있기에 조회 성능이 좋지 않다.
자주 사용하는 데이터는 캐싱을 통해 캐시저장소에 데이터를 저장 해 놓고 불러와 조회성능을 높여보자
회사에서 대학교 평가시스템 업무를 담당하고 있다. 대학교 학과정보는 변동되지 않으므로, 학과정보를 캐시에 넣어 사용 해 보자.
ehcache는 로컬 캐시 라이브러리이다.
캐싱을 사용 할 디렉토리나 모듈의 build.gradle에
implementation("org.ehcache:ehcache:3.9.5")
를 추가하자
다음은 언제나 그랬듯 캐시 라이브러리를 사용하기 위한 설정파일이다.
class CacheConfig
class CacheLogger : CacheEventListener<Any, Any> {
private val log = LoggerFactory.getLogger(javaClass)
override fun onEvent(cacheEvent: CacheEvent<out Any, out Any>) {
log.info("Key: [${cacheEvent.key}] | EventType: [${cacheEvent.type}] | Old value: [${cacheEvent.oldValue}] | New value: [${cacheEvent.newValue}]")
}
}
정확히는 캐시 로그 파일이다. 캐싱 처리에 대한 모니터링을 할 수 있다.
@Configuration
을 통해 설정파일임을 알리고 @EnableCaching
어노테이션으로 캐싱을 사용할 수 있도록 한다. ApiAppication 클래스에 작성 해 줘도 된다.
다음은 xml파일로 스프링에게 캐싱정보를 알려준다. 캐싱 할 이벤트와 저장시간, 저장공간을 명명 해 놓는 것으로 보인다. dept라는 이름으로 10분간 데이터를 임시 저장소에 저장 해 놓고 꺼내다 쓴다.
key-type 태그를 보니 api를 호출할 때 intercept해서 판별하는 것 같다.
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>
<cache-template name="default">
<listeners>
<listener>
<class>kr.co.~.config.CacheLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>UPDATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>REMOVED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
</listener>
</listeners>
</cache-template>
<cache alias="dept" uses-template="default">
<key-type>java.lang.String</key-type>
<value-type>java.util.ArrayList</value-type>
<expiry>
<ttl unit="minutes">10</ttl>
</expiry>
<resources>
<heap>100</heap>
<offheap unit="MB">1</offheap>
</resources>
</cache>
다음은 서비스단에서 @Cacheable
어노테이션에 캐싱 할 이름을 정해주면 해당 정보가 조회될 때 미리 저장 된 캐시 저장소에서 이름으로 데이터를 찾는다. 아래 예시는 CQRS 개념에 따라 읽기 전용으로 만든 서비스이다. 대학교 정보로 해당 대학교에 소속 된 학과를 모두 찾는다.
@Service
@Transactional(readOnly = true)
class SchoolInfoQueryService(
private val deptRepository: DeptRepository,
) {
@Cacheable(cacheNames = ["dept"])
fun findDepts(collegeCode: String): List<DeptOut> {
return deptRepository.getListByCollegeCode(collegeCode)
.map { DeptOut.fromEntity(it) }
}
}
이제 위 서비스를 불러올 때 마다 매번 쿼리를 날리지 않고, 정해진 기한 안에서 캐싱 된 정보를 불러온다.
캐시를 사용하면 조회 성능이 높아진다는 장점이 있지만 단점으로는 업데이트가 일어나도 미리 캐싱된 정보를 가져오기 때문에 조회 타이밍이 맞지 않을 수 있다. 이 경우 서버를 다시 돌려야 했었다. 따라서 극단적으로 변경되지 않는 정보가 아니면 사용 할 경우가 많지는 않을 것 같다.