Spring EhCache 간단한 적용법

Cache란?

예를 들어 회원 정보를 가져오는 API가 있다고 해보자. 회원 정보는 DB에 있으므로 정보를 가져오기 위해선 DB에 sql을 날려 조회해야 한다. 조회 요청이 자주 일어나지 않는다면 별 문제가 없겠지만 자주 일어난다면 DB 커넥션을 가져오고 해제하는 과정 등 회원 정보를 가져오기 위한 부가적인 작업들을 반복적으로 수행하게 된다.

이 때 사용할 수 있는 게 Cache이다. 한번 조회했던 회원 정보를 일정 기간 Cache로 보관하여 요청이 왔을 때 DB에 접근하지 않고 해당 정보를 반환해주면 된다. 즉 Cache를 이용하면 성능 개선을 할 수 있다.

다만 Cache를 이용할 때 주의해야 할 점이 있다. 만약 회원 정보가 실시간으로 바뀌는 것이고 이를 실시간으로 조회해야 한다면 Cache 정보는 일정 기간동안 갱신되지 않기 때문에 사용자 입장에서는 정보가 바뀌지 않았다고 여길 것이다. 따라서 Cache정보는 주로 변이가 잘 일어나지 않는 정보를 조회할 때 사용하는 것이 바람직하다.

Cache는 기본적으로 key-value의 구조이다.

EhCache

Ehcache는 자바 진영에서 사용하는 오픈소스 프로젝트이다.

github: https://github.com/ehcache

의존성을 추가하고 약간의 설정만 해주면 쉽게 cache를 적용할 수 있다. 스프링에 어떻게 적용하는지 살펴보겠다.

  • 의존성 설치
implementation 'org.springframework.boot:spring-boot-starter-cache'

// https://mvnrepository.com/artifact/org.ehcache/ehcache
implementation group: 'org.ehcache', name: 'ehcache', version: '3.8.1'

// https://mvnrepository.com/artifact/javax.cache/cache-api
implementation group: 'javax.cache', name: 'cache-api', version: '1.1.1'
  • 설정 파일 생성
@Configuration
@EnableCaching
public class CacheConfig {

}
  • /resources 하위에 ehcache.xml 파일 생성
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.ehcache.org/v3"
  xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
  xsi:schemaLocation="
            http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
            http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

  <cache alias="getSchoolByAlias">
    <key-type>java.lang.String</key-type>
    <value-type>kr.co.apexsoft.gradnet2.entity.adms.domain.RecruitSchool</value-type>
    <expiry>
      <ttl unit="seconds">30</ttl>
    </expiry>

    <listeners>
      <listener>
        <class>kr.co.apexsoft.gradnet2.apply_api._listener.EhCacheListener</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>EXPIRED</events-to-fire-on>
      </listener>
    </listeners>

    <resources>
      <heap unit="entries">3</heap>
      <offheap unit="MB">10</offheap>
    </resources>
  </cache>
</config>
  • 메소드에 캐시 적용
@Cacheable(cacheNames = "getSchoolByAlias", key = "#alias")
public RecruitSchool findSchoolByAlias(String alias) {
    return recruitSchoolRepository.getSchoolByAlias(alias);
}
  • Listener 클래스 생성
@Slf4j
public class EnCacheListener implements CacheEventListener<Object, Object> {

    @Override
    public void onEvent(CacheEvent<?, ?> event) {
        log.info("cache event logger message. event:{}, getKey: {}, getOldValue: {}, getNewValue: {}", event.getType(), event.getKey(),
            event.getOldValue(), event.getNewValue());
    }
}
  • application.yml에 xml 파일 경로 지정
spring:  
  cache:
    jcache:
      config: classpath:ehcache.xml

위 과정만 잘 따라서 했다면 cache가 적용될 것이다. 적용되었는지는 요청을 날려보면서 쿼리가 DB로 날라가는지를 확인해보면 된다.

그럼 위 과정에서 설명이 필요한 것에 대해 적어보겠다.

  • ehcache.xml
    • <cache alias="getSchoolByAlias">
      • 말그대로 캐시에 대한 별칭을 적용하는 것이다. alias="별칭"으로 작성하면 된다.
    • <key-type>java.lang.String</key-type>
      • 캐시는 key-value 형식으로 되어 있다고 설명했었다. 즉 이 태그는 key의 타입을 지정하는 것이다.
    • <value-type>kr.co.apexsoft.gradnet2.entity.adms.domain.RecruitSchool</value-type>
      • key-type과 같이 value의 타입을 지정하는 것이다.
    • <expiry>
        <ttl unit="seconds">30</ttl>
      </expiry>
      • 캐시 값의 만료기간을 의미하며 위 코드는 30초가 되면 캐시 값이 초기화되도록 설정한 것이다.
    • <listeners>
        <listener>
          <class>kr.co.apexsoft.gradnet2.apply_api._listener.EhCacheListener</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>EXPIRED</events-to-fire-on>
        </listener>
      </listeners>
      • 위 태그는 Listener객체를 등록하는 것이다. Listener가 필요하지 않다면 태그를 지정하지 않아도 된다.
        • <listener>태그에 하나의 객체를 등록한다.
        • <class>는 Linster 클래스 경로를 지정한다.
        • <event-firing-mode>는 캐시가 동기로 초기화 될 지 비동기로 초기회 될지를 설정하는 태그이다.
        • <event-ordering-mode>는 event 순서를 key 단위로 정렬할 지 여부를 설정하는 태그이다.
        • <events-to-fire-on>는 event가 발생되는 시점을 지정하는 태그이다. 위 내용으로는 CREATED, EXPIRED 즉 캐시 생성과 만료 시에 이벤트가 발생된다.
    • <resources>
        <heap unit="entries">3</heap>
        <offheap unit="MB">10</offheap>
      </resources>
      • <heap>태그는 jvm의 heap영역에 캐시 정보를 저장할 때 사용하는 것이다. unit은 저장의 기본단위를 의미하고 위 코드는 최대 3개의 항목을 heap에 저장하겠다는 의미이다. 3개가 꽉 찼는데 또 캐시가 들어오게 되면 가장 오랫동안 참조하지 않은 정보를 삭제하고 새로운 캐시를 저장한다.
      • offheap은 heap 바깥에 즉 GC의 관리 영역이 아닌 곳에 캐시를 저장하겠다는 의미이다. byte단위로 저장이 된다. 따라서 직접 메모리를 할당, 해제해야 하고 이는 EhCache에서 해준다. 위 코드는 10MB까지 캐시 정보를 저장하겠다는 의미이다.
        • offheap으로 캐시를 저장할 때 중요한 건 value 타입이 되는 객체는 Serializable 인터페이스를 구현해야 한다는 것이다. 왜냐하면 java에서 객체를 byte로 직렬화 할 때 Serializable 인터페이스를 구현한 객체에 대해서만 직렬화가 가능하다. 때문에 캐시 정보로 저장될 클래스는 Serializable를 implements 해야한다.
직렬화란?
자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할 수 있도록 바이트 스트림(stream of bytes) 형태로 연속적인(serial) 데이터로 변환하는 포맷 변환 기술을 일컫는다. 그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.
출처: https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%A7%81%EB%A0%AC%ED%99%94Serializable-%EC%99%84%EB%B2%BD-%EB%A7%88%EC%8A%A4%ED%84%B0%ED%95%98%EA%B8%B0

 

  • @Cacheable(cacheNames = "getSchoolByAlias", key = "#alias")
    • @Cacheable은 캐시를 적용할 메소드에 붙이는 어노테이션이다.
    • cacheNames 속성은 ehcache.xml에서 정의한 alias 값이다.
    • key속성은 캐시의 key가 되는 값의 변수명을 정의한 것이다. #xxx 이런 식으로 작성하면 된다.
  • CacheEventListener 클래스
    • 캐시 이벤트를 Listen하고 있는 객체이다. 캐시 이벤트가 발생할 때 로그를 남기는 등의 처리를 할 수 있다.