Cache 캐시 적용하기

떡ol·2022년 10월 26일
0

1. Cache란?

Cache란 특정 데이터를 생성또는 조회하고 그 결과값을 저장해두어, 다음번 실행 시엔 다른 작업없이 결과값만 불러와 사용할 수 있게 한다. 이 과정을 통해 빠르게 로딩가능하여 성능개선에 효과를 가져올 수 있다. 웹 개발에서는 주로 DB를 불러오는 작업 중 실시간 받아올 필요가 없는 자료들(매뉴, 공지사항 등)을 불러 올때 사용한다. velog도 게시글을 수정해서 올리다보면 목록에 업데이트가 안이루어지는데 조금 지나 새로고침하면 정상적으로 반영이 된다. 이런 예시가 Cache가 적용된 부분이다.

2. Bean 등록하기.

해당파트는 spring 3.0 이하버전에서 사용된 소스이며, 이후 버전부터는 사용법이나 속성 이름, 명칭이 다를 수 있습니다.

아래의 라이브러리를 추가해준다.

	implementation group: 'net.sf.ehcache.internal', name: 'ehcache-core', version: '2.7.7' //스프링 3.0이상에서는 자동추가 되어있다고한다...
	implementation 'org.springframework.boot:spring-boot-starter-cache'

다음과 같이 context 파일을 만들어 namespaces에 cache를 추가하고 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<cache:annotation-driven cache-manager="cacheManager" key-generator="cacheKeyGenerator"/>
	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
	<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:/ehcache-config.xml"/>
	<bean id="cacheKeyGenerator" class="com.config.CacheKeyGenerator"/>
</beans>

2.1 key-generator

keyGenerator는 Cache를 작동하게 할 키 값을 뭐로 할지 커스텀 할 수 있는 부분이다.
위에 소스에서는 com.config.CacheKeyGenerator를 만들어서 커스텀해서 사용한다.

public class CacheKeyGenerator implements KeyGenerator {

	@Override
	public Object generate(Object target, Method method, Object... params) {
		final List<Object> key = new ArrayList<Object>();
		key.add(method.getDeclaringClass().getName());
		key.add(method.getName());
		
		for(final Object o : params) {
			key.add(o);
		}
		System.out.println("매서드" + method); //확인용
		System.out.println("파람" + params); //확인용 
		System.out.println("캐시 키"+ key); //확인용 
		return key;
	}
    
}

다음과 같이 method의 이름 및 파라미터 값을 받아서 키로 설정하는 것을 확인할 수 있다.
이렇게해서 설정하면 다른소스에서 불러오더라도 해당 키 값이 포함되어있으면 작동하게 된다.
일반 사용자에게는 크게 필요가 없으므로 프로퍼티 부분을 작성안하고 그냥 건너띄어도 된다.

2.2 EhCacheManagerFactoryBean, EhCacheCacheManager

  • EhCacheManagerFactoryBean : 캐시 설정파일을 불러와 EhCacheCacheManager bean에 주입하기 위해 선 작업을 하는 객체이다. 객체 class 파일을 열어봐도 configLocation 말고는 특별한거는 보이지 않는다.
  • EhCacheCacheManager : 최종적으로 컨트롤하는 캐시 의존 객체이다.

모두 셋팅이 되었으면 <cache:annotation-driven> 속성값에 하나씩 매칭시켜 사용 context param에 등록하여 사용하면 된다.

2.3 cacheConfig.xml

위에서 FactoryBean에 configLocation이 가리키는 파일이다. 해당 파일에서는 캐시를 더 자세히 컨트롤 가능하게 설정한다.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemalocation="ehcache.xsd"
	updateCheck="false" 
	monitoring="autodetect"
	dynamicConfig="true"
>
<!-- 
updateCheck : cache 스케마 업데이트 버전알림 버전 변경사항 있으면 콘솔로그에 뜸
monitoring : autodetect으로 설정하며 서버 단위로 분산 캐시 메모리 관리가 가능해진다. 
dynamicConfig : 파일이 바뀌면 알아서 다시 읽는다.
diskStore : 캐시 저장 경로로 java임시메모리 경로 쓸게 아니면 커스텀 하면된다.-->

	<diskStore path="java.io.tmpdir" /> 

	<defaultCache 
		timeToIdleSeconds="0"
		timeToLiveSeconds="10"
		overflowToDisk="false"
		maxElementsOnDisk="1000"
		diskPersistent="false"
		diskExpiryThreadIntervalSeconds="0"
		eternal="false"
		memoryStoreEvictionPolicy="LRU" />	
	
	<cache name="test"
		maxElementsInMemory="1000"
		timeToIdleSeconds="0"
		timeToLiveSeconds="100"
		memoryStoreEvictionPolicy="LFU"
		eternal="false"
		overflowToDisk="false" />
	
</ehcache>

캐시의 각 속성설명은 하단을 확인하자.

  • diskExpiryThreadIntervalSeconds: 디스크에 저장된 캐시들에 대해  만료된 항목를 제거하기 위한 쓰레드를 실행 할 주기 설정
  • diskSpoolBufferSizeMB: 디스크 캐시에 쓰기 모드로 들어갈때, 사용될 비동기 모드의 스폴 버퍼 크기 설정, OutOfMemory 에러가 발생 시 수치를 낮추도록 한다.
  • diskPersistent: VM이 재기동 할때 캐싱된 객체들을 디스크에 계속 유지 할지 여부
  • diskAccessStripes: 디스크 퍼포먼스를 조정하기 위한 스트라핑 설정
  • eternal: 시간설정에 대한 무시 설정 (boolean), true 면 모든 timeout 설정은 모두 무시 되고 Element에서 캐시가 삭제 되지 않음
  • maxElementsInMemory: 메모리에 캐싱 되어질 객체의 최대수
  • maxEntriesLocalHeap: 힙메모리 최대량
  • memoryStoreEvictionPolicy: 객체의 갯수가 설정된 maxElementsInMemory에 도달 했을 경우 메모리에서 객체들을 어떤게 제거 할지 에 대한 제거 알고리즘
    • FIFO (First In First Out): Cache에 node를 추가할 경우 등록시간을 기록하여, 지정한 Cache size가 overflow가 발생할 경우에, 등록시간이 가장 빠른 node를 입력된 node로 변경하여 Cache에 관리한다.    
    • LFU (Least Frequently Used): 가장 호출이 안된 node를 overflow 발생시 삭제하고, 새로 등록된 node를 등록하는 알고리즘으로, Cache에 등록된 값을 호출할 때 count값과 위치를 함께 관리하여, overflow 발생할 때 count값이 제일적은 node를 삭제한다.    
    • LRU (Least Recently Used): 가장 오랫동안 사용이 안된 node를 overflow발생시에 삭제하고, 새로 등록된 node를 등록하는 알고리즘으로, Cache에 등록된 값을 호출할 때 호출된 node 의 위치를 제일 앞으로 이동한다. overflow가 발생할 경우, 마지막 node를 삭제하여 관리한다.
  • clearOnFlush: flush() 메소드가 실행 될 때 메모리 캐시가 바로 살제 할지 여부, 기본적으로 true 이며 바로 삭제됨.
  • name: 캐시 이름
  • overflowToDisk: maxElementsInMemory 음계량에 가까우면 오버플로우되는 객체들을 디스크에 저장 할지 결정
  • timeToIdleSeconds: 다음 시간 동안 유휴상태(Idle) 후 갱신 할 지 설정(default: 0)
  • timeToLiveSeconds: 다음 갱신 하기 까지 캐쉬가 유지 되는 시간 (0이면 만료시간을 지정하지 않는다고 보고 유지 되지 않음, default: 0)
  • maxElementsOnDisk: 디스크 캐시에 저장 될 최대 객체의 수를 지정
  • maxEntriesLocalDisk: 로컬 디스크에 유지 될 최대 객체 수
  • maxEntriesInCache: Terracotta의 분산 캐시에서만 사용 가능하며, 클러스터링에 저장 할 수 있는 최대 캐시 수를 설정
  • transactionalMode: copyOnRead , copyOnWrite 시 트렉젝션 모드를 설정
  • statistics: JMX 통계정보 갱신 옵션
  • copyOnRead: 읽기때 캐시 요소를 복사 할 지 여부
  • copyOnWrite: 캐시 객체 쓸 경우 위한 복사 할지 여부  
    ✔ copyOnRead와 Write는 캐쉬로 받아온 객체에 수정이 일어나는 경우 사용한다.  
    ✔ 캐시된 객체에 수정이 일어나면 참조호출로 인해 그 뒤에 호출되는 모든 객체가 수정 영향이 중첩되어 발생하므로 주의 필요
  • logging: 로깅 사용 여부
  • overflowToOffHeap: Off-heap 메모리 사용을 설정을 사용 할 수 있으며 JAVA의 GC에 영향을 주지 않는다. 엔터프라이즈 버전에서만 사용가능 하며, maxEntriesLocalHeap 설정을 최소한 100 요소까지 권정하며 OffHeap를 사용하는 경우 성능이 저하될수 있음.
  • maxMemoryOffHeap: Off-heap 메모리 사용의 최대 량을 설정
  • maxBytesLocalHeap: 최대 로컬 힙메모리 사용량 설정, 1kb, 1mb, 1gb 해당 옵션을 사용할 경우 maxEntriesLocalHeap 설정은 사용 할 수 없음.
  • maxBytesLocalOffHeap: 로컬 VM의 최대 offHeap 사용량을 설정 
  • maxBytesLocalDisk: maxBytesLocalHeap에 설정된 캐시 사용 이후에 대한 디스크 스토리지의 한계를 설정

3. JAVA에 적용하기

이제 캐시를 사용 할 준비는 끝났다. 서비스단에 적용시켜 사용하면 된다.

@Service
public class CacheServiceTest {
	// DAO자료는 본인꺼로 테스트해보면 된다. 
	@Resource
	DBtestMP dbtestMP;
	
	@Cacheable(value="test")
	public List<Map> getUserByCrypto(){
		return dbtestMP.getUserByCrypto();
	}
	// Date()는 DB없이 바로 사용가능하다.
	@Cacheable(value="test")
	public String getDate() {
		return new Date().toString();
	}
	
	@CacheEvict(value="test")
	public String getDate2() {
		return new Date().toString();
	}
	
	@CachePut(value="test")
	public String getDate3() {
		return new Date().toString();
	}
	
}

3.1 @Cacheable

@Cacheable 어노테이션은 value와 key 두가지 파라미터를 갖는다.
value에는 config에서 설정하였던 name을 매칭시켜주면 되고, key는 해당 캐시 메모리에 키 값이된다.
따라서 같은 name과 key값을 가지고 있는 Cache자료는 데이터가 공유되며, 파라미터를 이용해 다양한 조회조건을 캐시에 담을 수 있다.
key값을 설정하는 방법은 다음과 같다.

@Cacheable(value="test", key= "#id")
	public String getDate(String id) {
		return new Date().toString();
}

다음과 같이 매서드에 변수값을 넘겨서 key값으로 설정할 수 있다.
내가 작성한 맨 위에 소스에서는 KeyGenerator에서 커스텀해서 method.getName()을 가지고 생성 되게 하였다. key의 자료형은 어떤것이든 상관없으며 필수값도 아니라서, 따로 설정안하여도된다.

이렇게 설정된 getDate를 불러와보면 timeToLiveSeconds="10" 설정으로 인해 10초동안은 시간이 안바뀌는걸 알 수 있다.

3.2 @CachePut

데이터의 부분적인 업데이트가 필요할 때 데이터를 변경하는 메소드에 @CachePut을 사용해서 key 값에 해당하는 캐시 데이터를 업데이트 해준다.

3.3 @CacheEvict

캐시 데이터를 지우는 기능을 한다. 해당 name과 key가 일치하는것을 찾아 캐시를 날린다.
이때 파라미터가 한개 추가할 수 있는데 'allEntries = true'를 설정하면 어떤 key값이 오던 해당 name에 해당하는 캐시 값은 다 지워진다.

	@CacheEvict(value="test" key=#id) // 캐시설정이 test이고, key가 id인 자료만 삭제
	public String getDate2(String id) {
		return new Date().toString();
	}
    
    @CacheEvict(value="test" key=#id allEntries = true) // key 값 무시하고 test 다 삭제 
	public String getDate3() {
		return new Date().toString();
	}

4. 주의사항

캐시는 프록시기반으로 동작하는데 프록시 클래스는 모든 요청과 응답의 인터셉터에 의해 생성되어지기 때문에 같은 클래스 내부(internal) 콜에 의해서는 캐시기능이 적용되지 않는다.
여기서 더 자세히 확인 가능하다

  @Named("aService")
  public class AService {

      @Cacheable("employeeData")
      public List<EmployeeData> getEmployeeData(Date date){
      ..println("Cache is not being used");
      ...
      }

      public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
          List<EmployeeData> employeeData = getEmployeeData(date);
          // 이렇게 내부에서 실행하면 안돼요
      }

  }



참고자료들___
(참고) Redis 에 @Cacheable, @CachePut, @CacheEvict 적용해보기
(참고) ehcache Configuration
(참고) Ehcache 옵션 정리
(참고) SpringBoot의 기본 Cache 사용하기
(참고) ehcache를 사용할 때 주의할점 (proxy...)

profile
하이

0개의 댓글