Caffeine Cache, 어렵지 않게 사용하기 1

2022. 4. 4. 22:32Spring

반응형

Caffeine Cache에 대해 알아보고 사용법에 대해 알아보는 것이 해당 포스팅의 목표입니다.

 

이 전 포스팅에서는 Spring Cache에 대해 다뤘습니다.

Cache를 사용하기 위해서는 CacheManager가 필요한데요.

이번 포스팅에서는 로컬 캐시 중 성능에 유리한 Caffeine Cache를 다뤄보겠습니다.

 

본 편은 Caffeine Cache를 다루는 "Caffeine Cache, 어렵지 않게 사용하기 2" 로 이어집니다.

 

 

 

-------------------    INDEX     -------------------

Caffeine Cache?

Caffeine versus ..

📌 Caffeine VS EhCache

📌 Benchmarks

Caffeine 기능

📌 Population Strategy

📌 Eviction

📌 Notification of Removal

📌 Compute

📌 Statistics

Configuration

📌 yml

📌 @configuration

📌 Creating Parameters

----------------------------------------------

 

 

Caffeine Cache?

Caffeine is a high performance, near optimal caching library.

- Caffeine Github

 

 

Caffeine Github를 확인해보면, "고성능의 최적의 캐싱 라이브러리" 라는 문구로 Caffeine을 설명합니다.

 

고성능, 최적의 캐싱이라는 말에 뒷받침하여 다양한 캐시들과의 성능 차이 또한 제시하는데요.

흔히 사용하는 Ehcache부터 Guava, ConcurrentHashMap까지의 다양한 캐시들과 비교한 내용을 담았습니다.

같이 살펴보도록 하겠습니다.

 

 

 

Caffeine versus ..

📌  Caffeine VS EhCache

먼저 Ehcache는 multi-level 캐시, distributed 캐시, 캐시 리스너 등과 같은 많은 기능을 지원합니다.

Caffeine은 Ehcache보다 캐시의 성능이 높으며, 실제로 더 우수한 캐시 제거 전략을 사용합니다.

실제로 Window TinyLfu 퇴출 정책을 사용하는데, 거의 최적의 적중률을 제공합니다.

 

 

 

📌  Benchmarks

Caffeine은 벤치마크로 성능을 증명하는데요.

캐시의 용량 제한이 없고, 완전히 채워지며 항상 일정한 값을 계산하는 조건 하에 진행됩니다.

 

1. Compute

2. Read (100%)

 

3. Read (75%) / Write (25%)

2. Write(100%)

 

위의 그래프를 보면 알 수 있듯이,

데이터 처리량Throughput 대비 Ops/sOperations per seconds, 초당 작업에서 굉장히 높은 처리 속도를 확인할 수 있습니다. 

 

 

 

 

Caffeine 기능

Caffeine이 제공하는 기능들을 정리했습니다.

번역하다 시간이 호로록 지나가버리네요 .. 🥲

실제 사용은 다음 포스팅에서 확인하고, 어떤 기능들이 있는지 자세히 확인해보려 합니다.

 

대부분의 기능은 Caffeine Wiki에 자세히 설명되어 있으며,

해당 페이지를 참고해서 정리한 내용입니다.

 

 

📌  Population Strategy

캐시 추가 전략

 

Caffeine Cache는 아래의 세가지 타입의 캐시로 제작하여 사용할 수 있습니다.

해당 내용은 Github 링크를 참조 했으며, 더 자세한 내용은 링크를 확인해주세요.

 

 

1. Manual

Cache<K, V> cache = Caffeine.newBuilder().build()

 

엔트리를 자동 로드하는 캐시 생성합니다.

 

 

 

2. Loading (Synchronously)

LoadingCache<K, V> cache = Caffeine.newBuilder().build(CacheLoader<> loader)

 

동기 방식으로 loader를 통해 캐시 생성합니다.

 

 

3. Asynchronous Loding

AsyncLoadingCache<K, V> cache = Caffeine.newBuilder().buildAsync( ... );

 

비동기 방식으로 loader를 통해 캐시 생성합니다.

 

 

 

📌  Eviction 

Caffeine Cache는 아래의 세가지 타입으로 캐시를 Evict하는 설정을 할 수 있습니다.

해당 내용은 Github 링크를 참조 했으며, 더 자세한 내용은 링크를 확인해주세요.

 

위에서 다뤘다시피, Caffeine은 Window TinyLfu 을 사용합니다.

히트율이 높고 메모리 설치 공간이 적기 때문인데요.

해당 링크를 보면 Windwo TinyLfu 가 얼마나 효율적인지 확인할 수 있습니다.

 

 

 

1. Size-based

 

Caffeine.newBuilder().maximumSize(long)

 

크기 기준으로 캐시를 제거하는 방식은 개발자가 설정한 특정 값을 기준으로,

entries의 크기가 그 값을 넘을 때 entries의 일부분을 제거합니다.

 

그렇다면, 어떤 값을 제거할까요?

위에서 언급했던 Window TinyLfu를 적용하여 가장 최근에 사용되지 않았거나, 자주 사용되어지지 않은 것을 제거합니다.

 

 

 

2. Time-based

 

Caffeine.newBuilder().expireAfterAccess(long)
Caffeine.newBuilder().expireAfterWrite(long[, TimeUnit])

Caffeine.newBuilder().expireAfter(Expiry)

 

✔ expireAfterAccess:

(캐시 생성 이후) 해당 값이 가장 최근에 대체되거나 마지막으로 읽은 후 

특정 기간이 지나면 각 항목이 캐시에서 자동으로 제거되도록 지정합니다.

 

✔ expireAfterWrite:

캐시 생성 후 또는 가장 최근에 바뀐 후

특정 기간이 지나면 각 항목이 캐시에서 자동으로 제거되도록 지정합니다.

 

expireAfter: 

캐시가 생성되거나 마지막으로 업데이트된 후

지정된 시간 간격으로 캐시를 새로 고침합니다.

 

 

 

3. Reference-based

Caffeine.newBuilder().weakKeys().weakValues()
Caffeine.newBuilder() .softValues()

 

✔ Caffeine.weakKeys(), Caffeine.weakValues()

Week References를 사용하여 키를 저장합니다.

Week References는 키에 대한 다른 강력한 참조(Strong References)가 없는 경우 가비지 수집할 수 있습니다.

가비지 수집은 identity에만 의존하므로 전체 캐시가 동등성(.equals()) 대신 identity 동일성(==)을 사용하여 키를 비교합니다.

 


✔ Caffeine.softValues()

Soft References를 사용하여 값을 저장합니다.

Soft References 오브젝트는 메모리 수요에 따라 least-recently-used 방식으로 가비지가 수집됩니다.

소프트 레퍼런스를 사용하면 퍼포먼스가 영향을 받기 때문에 일반적으로 예측 가능한 최대 캐시 크기를 사용하는 것이 좋습니다. softValues()를 사용하면 Week References와 마찬가지로 값이 equals 대신 identity(==) equality를 사용하여 비교됩니다.

 

 

 

📌  Notification of Removal

제거되는 캐시 엔트리에 대해 리스너를 달아 추가적인 작업을 할 수 있습니다.

 

Caffeine.newBuilder()
    .evictionListener((K, V, RemovalCause) -> { /* ... */ })
    .removalListener((K, V, RemovalCause) -> { /* ... */ })
    .build();

 

두 리스너의 차이는 캐시 관련 용어를 이해하면 적절히 사용할 수 있습니다. 

 

Eviction : 캐시 정책Policy에 의한 삭제 

Invalidation : Caller에 의한 수동 삭제

Removal : invalidation과 eviction, 두 가지를 모두 포함

 

사용 예시를 볼까요?

 

Cache<Key, Graph> graphs = Caffeine.newBuilder()
    .evictionListener((Key key, Graph graph, RemovalCause cause) ->
        System.out.printf("Key %s was evicted (%s)%n", key, cause))
    .removalListener((Key key, Graph graph, RemovalCause cause) ->
        System.out.printf("Key %s was removed (%s)%n", key, cause))
    .build();

 

위와 같이 Key 타입을 갖는 key와 Graph 타입을 갖는 value를 이용해 로그를 남기는 예시를 확인할 수 있습니다.

 

 

 

📌  Compute

외부 데이터(리소스)에 접근하며 캐시 쓸 수 있습니다.

graphs.asMap().compute(key, (k, v) -> { /* ... */ });

 

불러온 캐시 데이터를 Map으로 변경한 후 키를 차례로 조작할 수 있습니다.

 

graphs.asMap().compute(key, (k, v) -> {
  Graph graph = createExpensiveGraph(key);
  ... // update a secondary store
  return graph;
});

map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))

 

 

📌  Statistics

캐시 액세스 통계 정보를 제공합니다.

통계 정보를 사용하려면 Caffeine.recordStats()를 설정해주면 됩니다.

 

Cache.stats() 메소드가 CacheStats를 반환해주는데요.

CacheStats는 아래와 같은 내용을 가집니다.

 

public final class CacheStats {

    // ... 
    private final long hitCount;
    private final long missCount;
    private final long loadSuccessCount;
    private final long loadFailureCount;
    private final long totalLoadTime;
    private final long evictionCount;
    private final long evictionWeight;
    // ... 
    public double hitRate() {/* ... */}
    public long evictionCount() {/* ... */}
    public double averageLoadPenalty() {/* ... */}
    // ... 
}

 

 

 

Configuration

 🔗 링크 : dependency 

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
  <version>3.0.6</version>
</dependency>

 

 

Caffeine은 yml, @Configuration 의 두가지 방법으로 설정을 할 수 있습니다.

 

📌  yml

장점 : 단순합니다.

단점 : 캐시가 여러개일 경우 expiration time이나 maximum number 등의 캐시 설정을 개별적으로 할 수 없기 때문에 설정에 제한이 있습니다.

 

spring:
  cache:
    caffeine:
      spec: maximumSize=500,expireAfterWrite=5s
    type: caffeine
    cache-names:
      - users
      - books

 

 

 

📌  @configuration

캐시가 여러개일 경우 expiration time이나 maximum number 등의 캐시 설정을 개별적으로 할 수 있어, 더 디테일한 작업이 가능합니다.

 

@Configuration
@EnableCaching
public class CaffeineCacheConfig { /* ... */ }

 

해당 방법으로 자세한 설정을 해보도록 할게요.

 

 

✔️  Simple Setting

@EnableCaching
@Configuration
public class CacheConfig {

    @Bean
    public Caffeine caffeineConfig() {
        return Caffeine
                .newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(60, TimeUnit.MINUTES);
    }

    @Bean
    public CacheManager cacheManager(Caffeine caffeine) {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
}

 

이 때 설정할 수 있는 값을 알아보도록 하겠습니다.

 

 

📌  Creating Parameters

 

initialCapacity: 내부 해시 테이블의 최소한의 크기를 설정합니다.

✔ maximumSize: 캐시에 포함할 수 있는 최대 엔트리 수를 지정합니다.

✔ maximumWeight: 캐시에 포함할 수 있는 엔트리의 최대 무게를 지정합니다.

✔ expireAfterAccess: 캐시가 생성된 후, 해당 값이 가장 최근에 대체되거나 마지막으로 읽은 후 특정 기간이 경과하면 캐시에서 자동으로 제거되도록 지정합니다.

✔ expireAfterWrite: 항목이 생성된 후 또는 해당 값을 가장 최근에 바뀐 후 특정 기간이 지나면 각 항목이 캐시에서 자동으로 제거되도록 지정합니다.

✔ refreshAfterWrite: 캐시가 생성되거나 마지막으로 업데이트된 후 지정된 시간 간격으로 캐시를 새로 고칩니다.

✔ weakKeys: 키를 weak reference로 지정합니다. (GC에서 회수됨)

✔ weakValues: Value를 weak reference로 지정합니다. (GC에서 회수됨)

✔ softValues: Value를 soft reference로 지정합니다. (메모리가 가득 찼을 때 GC에서 회수됨)

✔ recordStats: 캐시에 대한 Statics를 적용합니다.

 

⚠️ expireAfterWrite 와 expireAfterAccess 가 함께 지정된 경우, expireAfterWrite가 우선순위로 적용됩니다.

⚠️ maximumSize와 maximumWeight는 함께 지정될 수 없습니다.

 

 

 

 

 

 

Spring Caffeine Cache의 설정과 관련된 내용을 다뤘습니다.

다음 포스팅은 Caffeine Cache의 실제 사용법을 좀 더 디테일하게 다뤄보겠습니다.

 

오타나 잘못된 내용을 댓글로 남겨주세요!

감사합니다 ☺️ 

 

 

 

 

 

 

📌 Spring Cache Series

 

✔ Spring Cache, 제대로 사용하기

✔ Caffeine Cache, 제대로 사용하기 1

✔ Caffeine Cache, 제대로 사용하기 2

반응형