EHCache 개념 및 설정

들어가며

 

Cache를 사용하면 자주 사용되는 리소스가 존재 할시 리소스를 얻은 후 캐시 저장소에 만료시간과 함께 저장하고 사용자가 조회를 요청할 때 마다 만료시간 이전까지는 캐시 저장소에 있는 리소스를 사용함으로써 조회 성능을 대폭 향상 시킬 수 있다.

 

일반적으로 동일한 리소스 대해 빈번한 SELECT로 발생되는 DBMS 과부하를 줄이고자 사용한다.

 

추가로 해당 포스팅은 Spring Boot 2.3.0.RELEASE 기준이다.

 

Spring Cache

 

스프링은 캐시 추상화 (Cache Abstraction)을 통해 편리한 캐싱 기능을 지원하고 있다.

 

Spring Cache Abstraction ? 사용자는 캐시 구현에 대해 신경 쓸 필요없이 퍼블릭 인터페이스로 쉽게 캐싱 기능을 사용할 수 있는 것을 말한다.
더 자세히 말하면 캐싱이 필요한 비즈니스 로직에서 EhCache, redis 등 캐싱 인프라스트럭쳐에 의존하지않고 추상화된 퍼블릭 인터페이스로  캐싱을 할 수 있다. 이는 만약 EhCache로 사용중이다가 Redis로 변경되더라도 비즈니스로직에는 영향을 주지 않는다는 장점을 말한다.

 

Spring Legacy에서는 SimpleCacheManager를 통해 빈을 CacheManager bean을 생성하여,

Spring Boot에서는 spring-boot-starter-cache 아티팩트를 추가하여 캐싱 기능을 사용할 수 있다.

 

별도의 3rd-party 모듈이 없다면 Local Memory에 저장되는 ConcurrentMap 기반 ConcurrentMapCacheManager가 Bean으로 자동 등록된다. 3rd-party 모듈인 EHCache, Redis 등의 의존성을 추가하게되면 EHCacheCacheManager 또는 RedisCacheManager등을 Bean으로 등록되어 사용할 수 있다. 

 

 

 

EHCache

 

EHCache로 캐시 3rd-party를 구성할 경우 필요한 설정에 대해 알아보자.

 

Maven pom.xml

 

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
   <groupId>net.sf.ehcache</groupId>
   <artifactId>ehcache</artifactId>
   <version>2.10.6</version>
</dependency>

 

 

EhCacheConfig.java

 

@Configuration
@EnableCaching
public class EhCacheConfig {

	@Bean
	public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
		EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
		ehCacheManagerFactoryBean.setConfigLocation(
        		new ClassPathResource("ehcache-config.xml"));
		ehCacheManagerFactoryBean.setShared(true);
		return ehCacheManagerFactoryBean;
	}

	@Bean
	public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
		EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
		ehCacheCacheManager.setCacheManager(ehCacheManagerFactoryBean.getObject());
		return ehCacheCacheManager;
	}
}

 

코드 설명
EhCacheManagerFactoryBean CacacheManager의 적절한 관리 및 인스턴스를 제공하는데 필요하며 EhCache 설정 리소스를 구성한다. 
setConfigLocation 지정된 경로를 통해 EhCache 설정 리소스를 로드한다. (미지정시 루트의 ehcache.xml파일을 찾음).
setShared CacheManager 싱글톤 여부 (default= false).
@EnableCaching Annotation을 사용하여 캐싱 기능을 이용하겠다고 선언.

 


ehcache-config.xml

 

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         maxBytesLocalHeap="300M">

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

    <sizeOfPolicy maxDepth="100000"/>

    <!-- 필수로 설정해야하며 <cache> 들의 기본 설정 값 -->
    <defaultCache
            timeToLiveSeconds="180"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>

    <cache name="guelLocalCache"
           timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LRU">
    </cache>
</ehcache>

 

<defaultCache>라는 태그를 살펴 보면 말 그대로 기본 캐시 설정 값이지만 <cache>들이 <defaultCache>의 설정 값을 상속하나? 라는 오해의 여지가 있을 수 있다.

실제로는 cacheManager.addCache(String cacheName)와 같이 자바코드로 작성하여 런타임에 cache를 동적으로 생성할 때 설정되는 값으로 이해하면 된다.

 

https://stackoverflow.com/questions/10931942/do-caches-in-ehcache-xml-inherit-from-defaultcache

 

Do caches in ehcache.xml inherit from defaultCache?

If I have the following configuration: What will be the ...

stackoverflow.com

 

ehcache 속성 설명
maxEntriesLocalHeap Heap 캐시 메모리 pool size 설정, GC대상이 됨.
maxMemoryOffHeap 캐시 메모리 pool size설정, EHCache가 관리하며 GC대상에서 제외.
maxEntriesLocalDisk Disk pool size 설정.

 

diskStore 속성 설명
path 스풀링 공간을 설정한다, 모든 캐시가 Memory store만을 사용하면 diskStore를 설정 할 필요가 없으나 여러개의 CacheManager를 사용한다면 여러 개의 diskStore 경로를 사용하는것이 효율적이다

 

sizeOfPolicy 속성 설명
maxDepth 캐시에 저장될 객체 레퍼런스의 최대 값.
maxDepthExceededBehavior continue(default) - 객체 그래프를 sizing시 최대 깊이를 초과 할 때 경고 log를 준 후 계속 진행된다. abort - sizing을 중지하고 부분적으로 계산 된 크기를 반환.

 

cache 속성 설명 default required
name 캐시명. 필수 true
eternal true일 경우 timeout 관련 설정이 무시, element가 캐시에서 삭제되지 않음. false true
timeToldleSeconds Element가 지정한 시간 동안 사용(조회)되지 않으면 캐시에서 제거된다. 이 값이 0인 경우 조회 관련 만료 시간을 지정하지 않는다. 0 false
timeToLiveSeconds Element가 존재하는 시간. 이 시간이 지나면 캐시에서 제거된다. 이 시간이 0이면 만료 시간을 지정하지 않는다. 0 false
diskPersistent VM이 재 가동할 때 디스크 저장소에 캐싱된 객체를 저장할지의 여부를 지정한다. false false
diskExpiryThreadIntervalSeconds Disk Expiry 쓰레드의 수행 시간 간격을 초 단위로 지정한다. 120 false
memoryStoreEvictionPolicy 객체의 개수가 maxElementsInMemory에 도달했을 때, 메모리에서 객체를 어떻게 제거할 지에 대한 정책을 지정한다. LRU, FIFO, LFU 지정 가능. LRU false

 


실제 작동을 하는지 테스트해보자.

 

SomeDataRepository.java

 

@Repository
public class SomeDataRepository {

	public String getSomeData() {
		executeSlowQuery();
		return "I'm some data";
	}

	private void executeSlowQuery() { //2초 걸리는 query를 수행한다고 가정
		try {
			Thread.sleep(2000L);
		} catch (Exception e) {}
	}
}

 


SomeDataService.java

 

@Service
@RequiredArgsConstructor
public class SomeDataService {
	private final SomeDataRepository someDataRepository;

	@Cacheable(cacheNames = "testCache") //Annotation을 사용하여 캐싱
	public String getSomeData() {
		return someDataRepository.getSomeData();
	}
}

 


EHCacheTest.java

 

@Slf4j
@SpringBootTest
public class EHCacheTest {

	@Autowired
	private SomeDataService someDataService;
	
	@Test
	public void EHCache_조회_테스트() {
		
		IntStream.range(0, 5)
				 .forEach(index -> {
					 Profiler profiler = new Profiler("EHCache-cacheable-test");
					 profiler.start(String.format("%d번째 호출 수행시간", index + 1));
					 
					 someDataService.getSomeData();
					 
					 profiler.stop().print();
				 });
	}
}

 


테스트 결과

 

 

첫번째 호출시 캐시저장소에 리소스가 저장되어 있지 않아 2초 비용이 드는 쿼리를 수행 했지만

이후의 호출은 캐시저장소에서 리소스를 꺼내 쿼리를 수행 할 필요가 없어 조회 시간이 대폭 감소한것을 볼 수 있다.

 

 

 

@Cacheable 이외에도 @CacheEvict, @CachePut, @Caching, @CacheConfig가 존재한다.

 

@Cacheable

 

캐시 저장소에 리소스가 이미 존재시 저장되어있는 리소스를 반환한다.

만약 리소스가 존재하지 않는다면 메서드 내 로직을 수행 후 반환되는 리소스를 캐시 저장소에 저장 및 외부 호출자에게 응답한다.

 

@Cacheable 속성 설명 Default
value, cacheName ehcache.xml 또는 ehcach config에 구성된 cache name. {}
key 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값, (상수 권장). ""
cacheManager 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) . ""
condition SpEL 표현식을 작성하여 참일 경우에만 캐싱이 적용됨, (or, and 등 조건식, 논리연산 가능).
EX) @Cacheable(key = "testKey", condition="#caching") public Object getSome(boolean caching)
""
unless condition과 반대로 참일 경우에만 캐싱이 적용되지 않는다. ""
sync 캐시 구현체가 Thread safe 하지 않는 경우, 캐시에 동기화를 걸 수 있는 속성이다. false

 

 

 

@CacheEvict

 

메서드가 호출 될 때 저장된 캐시를 삭제해주는 어노테이션이다.

 

@CacheEvict 속성 설명 Default
value, cacheName ehcache.xml 또는 ehcach config에 구성된 cache name. {}
key 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값, (상수 권장). ""
cacheManager 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) . ""
condition SpEL 표현식을 작성하여 참일 경우에만 캐싱이 적용됨, (or, and 등 조건식, 논리연산 가능).
EX) @CacheEvict(key = "testKey", condition="#caching") public Object removeSome(boolean caching)
""
allEntries 선언된 메서드로 캐싱된 캐시 리소스를 모두 삭제한다. false
beforeInvocation true - 메서드 수행 이전 캐시 리소스 삭제, false - 메서드 수행 후 캐시 리소스 삭제가 된다. false

 

 

 

@CachePut

 

저장된 캐시를 갱신할 때 사용되며 메서드의 몸체가 매번 수행된다.

 

@CachePut 속성 설명 Default
value, cacheName ehcache.xml 또는 ehcach config에 구성된 cache name. {}
key 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값, (상수 권장). ""
cacheManager 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) . ""
condition SpEL 표현식을 작성하여 참일 경우에만 캐싱이 적용됨, (or, and 등 조건식, 논리연산 가능).
EX) @Cacheable(key = "testKey", condition="#caching") public Object modifySome(boolean caching)
""
unless condition과 반대로 참일 경우에만 캐싱이 적용되지 않는다. ""

 

 

 

@Caching

 

하나의 메서드를 호출 할 때 Cacheable, CacheEvict 등 여러개의 캐싱 동작을 수행해야 할 때 사용된다.

 

@Caching 속성 설명 Default
cacheable[] 적용 될 @Cacheable array를 등록한다.  {}
evict[] 적용 될 @CacheEvict array를 등록한다.  {}
put[] 적용 될 @Cacheput array를 등록한다.  {}

 

 

 

@CacheConfig

 

클래스 단위로 캐시 설정을 동일하게 하고 싶을 때 사용한다.

 

@CacheConfig 속성 설명 Default
cacheNames 캐시명. {}
cacheManager 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) . ""