반응형

Ehcache의 주요 특징

  1. 효율적인 캐싱: 메모리와 디스크를 사용한 하이브리드 캐싱을 지원하여, 대용량 데이터의 캐싱도 효율적으로 처리할 수 있습니다.
    1. 메모리 캐시: 자주 사용되는 데이터를 메모리에 저장하여 빠른 응답 시간을 제공합니다. 메모리 캐시는 가장 빠른 캐싱 옵션입니다.
    2. 디스크 캐시: 메모리 캐시의 데이터를 디스크에 저장하여, 메모리 용량 초과 시에도 데이터를 유지할 수 있습니다. 디스크 캐시는 메모리보다 느리지만 더 많은 데이터를 저장할 수 있습니다.
  2. 분산 캐싱: 클러스터링과 분산 캐싱을 지원하여, 여러 애플리케이션 인스턴스 간의 캐시 일관성을 유지할 수 있습니다.
    1. Ehcache는 여러 노드 간에 캐시 데이터를 공유하고 동기화할 수 있는 클러스터링 기능을 제공합니다. 이를 통해 분산 환경에서 데이터 일관성을 유지할 수 있습니다.
    2. Terracotta와 통합하여 확장 가능한 분산 캐시 클러스터를 구축할 수 있습니다.
    3. Ehcache + Terracotta의 장점:
      1. 분산 캐시 처리: 여러 서버에서 동일한 캐시 데이터를 공유하고 동기화할 수 있습니다.
      2. 확장성: 여러 서버에 걸쳐 캐시를 분산 처리하므로, 대규모 애플리케이션에서도 효율적으로 캐시를 관리할 수 있습니다.
      3. 고가용성: Terracotta는 분산 환경에서 장애 복구 기능을 제공하여 데이터의 고가용성을 보장합니다.
      4. 데이터 일관성: 동기화 또는 비동기화 모드를 통해 캐시 데이터의 일관성을 유지할 수 있습니다.
  3. 플러그인 방식: Spring Framework, Hibernate, JPA 등과 쉽게 통합되어, 개발자가 캐싱을 손쉽게 구현할 수 있습니다.
  4. 캐시 구성: 다양한 캐시 정책(예: LRU, LFU, FIFO)을 통해 캐시의 만료, 용량 제어, 데이터 일관성 관리가 가능합니다.
    1. TTL(Time To Live): 캐시 항목의 유효 시간을 설정하여, 지정된 시간이 지나면 캐시에서 자동으로 제거됩니다.
    2. TTI(Time To Idle): 캐시 항목이 사용되지 않은 시간 기준으로 만료되는 설정입니다. 일정 시간 동안 접근되지 않은 캐시 항목은 제거됩니다.
    3. LRU(Least Recently Used): 최근에 사용되지 않은 항목을 먼저 제거하여 캐시의 크기를 관리하는 정책입니다.
    4. LFU(Least Frequently Used): 사용 빈도가 적은 항목을 먼저 제거하여 캐시의 크기를 관리합니다.
  5. 기본 및 확장 기능: 기본적인 캐시 기능 외에도, 캐시 이벤트 청취자, 트랜잭션 캐시, 캐시 복제 등 다양한 기능을 제공합니다.
  6. JCache 표준 지원 & Spring과의 통합:
    1. Ehcache는 Spring 프레임워크와의 통합을 지원하며, Spring의 캐시 추상화를 통해 쉽게 설정하고 사용할 수 있습니다.
    2. 애노테이션 기반 캐시 관리(@Cacheable, @CacheEvict 등)를 지원하여 개발 생산성을 높일 수 있습니다.
  • 3.1 캐시 일관성 전략
    1. Write-through 캐시
      • 개념: 애플리케이션이 데이터베이스에 데이터를 쓰는 동시에 캐시에 데이터를 갱신하는 방식입니다. 쓰기 작업이 캐시와 데이터베이스에 동시 반영됩니다.
      • 장점: 데이터베이스와 캐시 간의 일관성이 보장됩니다.
      • 단점: 쓰기 작업의 지연 시간이 증가할 수 있습니다.
    2. Write-behind 캐시
      • 개념: 애플리케이션이 데이터를 캐시에 먼저 쓰고, 비동기적으로 데이터베이스에 반영하는 방식입니다. 캐시에 데이터를 저장한 후, 일정 시간 후에 데이터베이스에 기록됩니다.
      • 장점: 쓰기 작업의 성능이 향상됩니다.
      • 단점: 캐시와 데이터베이스 간의 일관성 문제가 발생할 수 있으며, 데이터 유실 가능성도 있습니다.
    3. Cache-aside 패턴 / 읽을때
      • 개념: 애플리케이션이 먼저 캐시에서 데이터를 조회하고, 캐시된 데이터가 없으면 데이터베이스에서 데이터를 읽은 후 캐시에 저장하는 방식입니다.
      • 장점: 읽기 작업 성능이 매우 높으며, 캐시된 데이터는 자주 읽히는 데이터로 효율적으로 관리됩니다.
      • 단점: 캐시와 데이터베이스 간 일관성을 보장하지 않으므로, 캐시 무효화 전략이 필요합니다.
    4. TTL(Time To Live) 기반 캐시
      • 개념: 캐시에 저장된 데이터에 **유효 기간(Time To Live, TTL)**을 설정하여, 데이터가 일정 시간이 지나면 자동으로 무효화됩니다.
      • 장점: 오래된 데이터가 자동으로 갱신되므로 일관성을 유지할 수 있습니다.
      • 단점: TTL을 적절하게 설정하지 않으면, 캐시 히트율이 떨어지거나 불필요한 갱신이 발생할 수 있습니다.
    5. 캐시 무효화(Cache Invalidation)
      • 개념: 데이터베이스에서 데이터가 변경되면 해당 데이터를 캐시에서 무효화하여, 다음 읽기 작업 시 새로운 데이터를 캐시에 다시 로드하는 방식입니다.
      • 장점: 데이터베이스와 캐시 간의 일관성을 보장합니다.
      • 단점: 데이터베이스와 캐시 간 동기화 지연으로 인해 일시적인 일관성 문제가 발생할 수 있습니다.

    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'javax.cache:cache-api'
    implementation 'org.ehcache:ehcache:3.6.2'

application.properties

# 캐시 구성 파일 위치 설정
spring.cache.jcache.config=classpath:ehcache.xml
spring.cache.type=ehcache

ehcache.xml(자바로도 가능)

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

    <!-- 기본 캐시 설정 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            overflowToDisk="false"/>

    <!-- 사용자 정의 캐시 설정 -->
    <cache name="exampleCache"
           maxEntriesLocalHeap="1000"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           overflowToDisk="true"
           diskSpoolBufferSizeMB="20"
           maxEntriesLocalDisk="10000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

캐시 구성 세부 사항

  • 캐시 만료 정책
    • timeToIdleSeconds: 캐시에 저장된 데이터가 얼마나 오랫동안 사용되지 않았을 때 만료될지 설정합니다.
    • timeToLiveSeconds: 캐시에 저장된 데이터가 얼마나 오랫동안 존재할 수 있는지 설정합니다.
    • eternal: 데이터가 영원히 만료되지 않도록 설정합니다. true일 경우 만료 시간 설정이 무시됩니다.
  • 캐시 용량 관리
    • maxEntriesLocalHeap: 캐시에서 허용할 최대 엔트리 수를 설정합니다.
    • overflowToDisk: 메모리 캐시가 가득 차면 디스크로 데이터를 내보낼지 설정합니다.
    • maxEntriesLocalDisk: 디스크 캐시에 허용할 최대 엔트리 수를 설정합니다.
  • 데이터 유지 정책
    • memoryStoreEvictionPolicy: 메모리 캐시에서 데이터를 제거할 정책을 설정합니다. LRU (Least Recently Used), LFU (Least Frequently Used), FIFO (First In, First Out) 등이 있습니다.

 

위 의존성/설정을 추가하고 프로젝트에 @EnableCaching 어노테이션을 추가한다.

spring-boot를 사용하면 @EnableCaching 어노테이션만으로도 ConcurrentMapCacheManager를 기본으로 등록해주기 때문에 따로 등록할 것은 없지만 커스텀을 할 경우 아래와 같이 빈을 등록하여 override 해야 한다.

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
          new ConcurrentMapCache("directory"), 
          new ConcurrentMapCache("addresses")));
        return cacheManager;
    }
}

참고) 캐시 매니저 종류

  • ConcurrentMapCacheManager: Java의 ConcurrentHashMap을 사용해 구현한 캐시를 사용하는 캐시 매니저
  • SimpleCacheManager: 기본적으로 제공하는 캐시가 없어 사용할 캐시를 직접 등록하여 사용하기 위한 캐시 매니저
  • EhCacheCacheManager: 자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저
  • CompositeCacheManager: 1개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저
  • CaffeineCacheManager: Java 8로 Guava 캐시를 재작성한 Caffeine 캐시를 사용하는 캐시 매니저
  • JCacheCacheManager: JSR-107 기반의 캐시를 사용하는 캐시 매니저

 

ConcurrentMapCache 생성자를 통해서 캐시 이름과 null값을 캐싱할지 여부, 사용할 concurrentMap을 지정할 수 있다. 참고로 ConcurrentMapCache는 만료시간에 의한 자동 만료 기능이 없기 때문에, 수동으로 체크 혹은 @Scheduled를 활용하여 구현해야 한다.

참고로 캐시 흐름을 로그로 보고 싶다면 별도 이벤트 리스너를 구현해야한다.

@Slf4j
public class CacheEventConfig implements CacheEventListener<Object, Object> {

  @Override
  public void onEvent(CacheEvent<?, ?> event) {
    log.info(">>>[Caching event] working {} :: {}", event.getType(), event.getKey());
  }
}

캐시 조회/저장 @Cacheable

캐시 해야 하는 데이터가 return 되는 메서드 위에 설정한다.

@Cacheable("addresses")
public String getAddress(Customer customer) {...}

getAddress가 호출될 때 실제 로직이 시행되기 전 input parameter(key = customer)에 해당하는 캐시를 먼저 확인하고, 없으면 실제 로직을 실행하고 캐싱을 한다. 위 예시는 Customer클래스가 key 값이므로(별도의 key값을 설정하지 않았으므로) hashcode와 equals 메서드를 오버라이드 해 어떤 데이터일 때 같다고 판별할지 명백히 해야 한다. 아래 별도의 링크를 통해 키 판별에 대해 확인하자.

@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}

이렇게 여러 개의 캐시를 줄 수도 있다. 이때 둘 중 하나라도 값이 있으면 실제 로직을 실행하지 않는다.

@Cacheable(value = "bestSeller", key = "#book.bookNo")
public Book getBestSeller(Book book, User user, Date dateTime) {...}

Key값의 지정에는 SpEL이 사용된다. 그렇기 때문에 만약 파라미터가 객체라면 위와 같이 하위 속성에 접근할 수 있다.

@Cacheable(value = "bestSeller", key = "#book.bookNo", condition = "#user.type == 'ADMIN'")
public Book getBestSeller(Book book, User user, Date dateTime) {...}

만약 파라미터 값이 특정 조건인 경우에만 캐시를 적용하기를 원한다면 condition을 이용하면 된다.

 

캐시 삭제 @CacheEvict

@CacheEvict(value = "bestSeller")
public void clearBestSeller() {...}

@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}

기본적으로 메서드의 키에 해당하는 캐시만 제거한다. 위 예시는 이름이 같은 캐시만 제거할 것이다.

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}

여러 이름의 캐시를 지울 때는 위와 같이 복수개 설정이 가능하다.

@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

key와 상관없이 모든 객체를 지우려면 allEntries = true로 지정하면 된다.

 

캐시 수정 @CachePut

캐시 값이 바뀌었다면 바로 수정해주어야 한다. @CachePut은 @Cacheable과 유사하게 실행 결과를 캐시에 저장하지만, 캐시의 저장 유무와 상관없이 항상 메서드의 로직을 실행한다는 점에서 다르다.

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

 

@CacheConfig

하나의 캐시에 대해 일괄 관리를 하고자 하면 아래와 같이 클래스 레벨에서 처리할 수도 있다. 이때 이름을 중복으로 적을 필요 없이 클래스 선언으로 해결된다.

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

    @Cacheable
    public String getAddress(Customer customer) {...}
}

 

조건절

위에서 잠깐 나왔지만 spEL을 활용하여 condition / unless의 조건절을 줄 수 있다.

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

 

캐시 key 설정에 대한 내용

https://jistol.github.io/spring/2017/02/09/springboot-cache-key/

 

(Spring Cache) @Cacheable key값 정하기

 

jistol.github.io

https://sunghs.tistory.com/132

 

[SPRING] 스프링 캐시 사용하기

spring에서 cache 관련 된 기능을 지원한다. 기존 cache 처리라고 하면, Redis, memcached 등의 추가적인 memoryDB를 이용하거나, application 레벨에서 사용 가능한 EhCache 등이 많이 쓰이는데, 이 중 applicati..

sunghs.tistory.com

 

728x90
반응형

+ Recent posts