Java

[Java] 로컬 캐시 3대장 완벽 비교: Caffeine vs Ehcache vs Guava

hyuk0309 2026. 1. 31. 20:35

 

애플리케이션의 성능을 높이는 가장 확실한 방법 중 하나는 **로컬 캐시(Local Cache)**를 사용하는 것입니다. Redis 같은 분산 캐시보다 훨씬 빠르고 간편한 Java 로컬 캐시의 대표 주자 3종을 분석해 보았습니다.

 

1. Caffeine Cache: 현시점 최강의 성능

Caffeine은 Java 8 이상을 기반으로 한 고성능 최신 캐시 라이브러리입니다. Spring Boot 2.0부터 기본 캐시로 사용될 만큼 성능과 효율성을 인정받았습니다.

  • 동작 원리: W-TinyLFU(Window TinyLFU) 알고리즘을 사용합니다. 데이터의 '최근성(LRU)'과 '사용 빈도(LFU)'를 모두 고려하면서도, 아주 적은 메모리 오버헤드로 높은 적중률을 유지합니다.
  • 장점:
    • 압도적인 읽기/쓰기 속도 (Lock 경합 최소화).
    • 매우 높은 캐시 적중률(Hit Rate).
    • 다양한 만료 정책(시간, 크기, 참조 기반) 제공.
  • 단점: * 오직 Java Heap 메모리만 사용하며, 분산 환경 동기화 기능은 내장되어 있지 않음.

2. Ehcache: 엔터프라이즈급 확장성

오랫동안 Java 표준처럼 자리 잡은 라이브러리입니다. 단순 메모리 저장을 넘어 다양한 저장 계층을 제공하는 것이 특징입니다.

  • 동작 원리: **다층 저장 구조(Tiered Storage)**를 채택합니다. 데이터를 온힙(On-heap)뿐만 아니라 오프힙(Off-heap), 디스크(Disk)에 나누어 저장할 수 있습니다.
  • 장점:
    • Off-heap 지원: JVM GC의 영향권 밖인 메모리 영역을 사용하여 대용량 데이터 저장 시 GC 부하를 줄임.
    • 영속성: 서버 재시작 시에도 디스크에 캐시를 보관하여 복구 가능.
    • 테라바이트급 대용량 데이터 관리에 적합.
  • 단점:
    • Caffeine에 비해 처리량(Throughput)이 상대적으로 낮음.
    • 설정이 복잡함 (주로 XML 사용).

3. Guava Cache: 단순함과 안정성

Google의 공통 라이브러리인 Guava에 포함된 캐시 모듈입니다. Caffeine의 조상 격으로, 구조가 단순하고 견고합니다.

  • 동작 원리: Segmented Hash Table 구조를 사용합니다. 내부적으로 데이터를 세그먼트로 분할하여 락(Lock) 범위를 좁히는 방식을 취하며, 기본적으로 LRU 알고리즘을 따릅니다.
  • 장점:
    • Guava 라이브러리를 이미 사용 중이라면 추가 의존성 없이 즉시 사용 가능.
    • API가 매우 직관적이어서 학습 곡선이 낮음.
  • 단점:
    • Caffeine 출시 이후 성능 및 기능 확장성 면에서 하위 호환 취급을 받음.
    • 유연한 알고리즘(LFU 등) 적용이 어려움.

 

비교 항 Caffeine Ehcache Guava Cache
핵심 알고리즘 W-TinyLFU LRU, LFU, FIFO LRU
주요 특징 고성능, 고적중률 Off-heap, Disk 저장 단순함, 안정성
추천 용도 일반적인 고성능 앱 대용량 데이터, 영속성 필요 가벼운 프로젝트
GC 영향 있음 (Heap 사용) 낮음 (Off-heap 지원) 있음 (Heap 사용)

 

 

Demo : 실제로도 그럴까?

실제로도 그런지 테스트해봤습니다.

 

소스 코드 : https://github.com/hyuk0309/play-ground/tree/main/cache-benchmark

 

play-ground/cache-benchmark at main · hyuk0309/play-ground

Contribute to hyuk0309/play-ground development by creating an account on GitHub.

github.com

결과

Benchmark                                         Mode  Cnt       Score       Error   Units
CacheBenchmark.readCaffeine                      thrpt    3  136023.978 ± 86516.506  ops/ms
CacheBenchmark.readCaffeine:gc.alloc.rate        thrpt    3    2173.259 ±  1934.176  MB/sec
CacheBenchmark.readCaffeine:gc.alloc.rate.norm   thrpt    3      17.303 ±     0.691    B/op
CacheBenchmark.readCaffeine:gc.count             thrpt    3      79.000              counts
CacheBenchmark.readCaffeine:gc.time              thrpt    3      36.000                  ms
CacheBenchmark.readEhcache                       thrpt    3  102497.823 ± 12967.187  ops/ms
CacheBenchmark.readEhcache:gc.alloc.rate         thrpt    3    1495.525 ±  1340.416  MB/sec
CacheBenchmark.readEhcache:gc.alloc.rate.norm    thrpt    3      15.795 ±     0.006    B/op
CacheBenchmark.readEhcache:gc.count              thrpt    3      78.000              counts
CacheBenchmark.readEhcache:gc.time               thrpt    3      37.000                  ms
CacheBenchmark.readGuava                         thrpt    3   17214.489 ±  4449.187  ops/ms
CacheBenchmark.readGuava:gc.alloc.rate           thrpt    3     630.322 ±   529.770  MB/sec
CacheBenchmark.readGuava:gc.alloc.rate.norm      thrpt    3      39.645 ±     0.028    B/op
CacheBenchmark.readGuava:gc.count                thrpt    3      33.000              counts
CacheBenchmark.readGuava:gc.time                 thrpt    3      16.000                  ms
CacheBenchmark.writeCaffeine                     thrpt    3   11718.498 ±  2000.031  ops/ms
CacheBenchmark.writeCaffeine:gc.alloc.rate       thrpt    3    2521.096 ±  2403.587  MB/sec
CacheBenchmark.writeCaffeine:gc.alloc.rate.norm  thrpt    3     232.336 ±    40.088    B/op
CacheBenchmark.writeCaffeine:gc.count            thrpt    3     132.000              counts
CacheBenchmark.writeCaffeine:gc.time             thrpt    3     197.000                  ms
CacheBenchmark.writeEhcache                      thrpt    3    6362.255 ±   376.085  ops/ms
CacheBenchmark.writeEhcache:gc.alloc.rate        thrpt    3    1638.802 ±  1576.640  MB/sec
CacheBenchmark.writeEhcache:gc.alloc.rate.norm   thrpt    3     278.810 ±     0.269    B/op
CacheBenchmark.writeEhcache:gc.count             thrpt    3      85.000              counts
CacheBenchmark.writeEhcache:gc.time              thrpt    3     105.000                  ms
CacheBenchmark.writeGuava                        thrpt    3    4406.399 ±  2111.697  ops/ms
CacheBenchmark.writeGuava:gc.alloc.rate          thrpt    3     560.718 ±   776.203  MB/sec
CacheBenchmark.writeGuava:gc.alloc.rate.norm     thrpt    3     137.625 ±     0.286    B/op
CacheBenchmark.writeGuava:gc.count               thrpt    3      30.000              counts
CacheBenchmark.writeGuava:gc.time                thrpt    3      28.000                  ms

 

 

1. 요약: 최종 순위


1.  🥇 Caffeine: 읽기, 쓰기 모든 면에서 가장 빠름 (압도적 1위)
2.  🥈 Ehcache 3: 읽기 성능은 꽤 준수하지만, 쓰기에서 Caffeine에 밀림
3.  🥉 Guava: 읽기 성능이 충격적으로 낮음 (최하위)

 

2. 상세 분석


A. 읽기 성능 (Read Throughput, 100% Hit)
> 단위: ops/ms (1밀리초당 처리 횟수, 높을수록 좋음)
*   Caffeine: 136,023 ops/ms
*   Ehcache: 102,497 ops/ms
*   Guava: 17,214 ops/ms
해석:
*   Caffeine vs Guava: Caffeine이 Guava보다 약 7.9배 더 빠릅니다. 같은 시간 동안 Guava가 1번 읽을 때 Caffeine은 8번 가까이 읽습니다.
*   Caffeine vs Ehcache: Ehcache도 분전했지만 Caffeine이 약 1.3배 더 빠릅니다.
*   GC 효율성 (gc.alloc.rate.norm):
    *   Caffeine(17.3 B/op)과 Ehcache(15.8 B/op)는 조회 1건당 메모리 할당량이 매우 적습니다.
    *   반면 Guava(39.6 B/op)는 조회 시 2배 이상의 객체(메모리)를 생성하고 있어 GC 부하가 더 큽니다.
B. 쓰기 성능 (Write Throughput, Insert & Eviction)
> 쓰기 작업은 락(Lock)과 데이터 구조 재배열 때문에 읽기보다 훨씬 느린 것이 정상입니다.
*   Caffeine: 11,718 ops/ms
*   Ehcache: 6,362 ops/ms
*   Guava: 4,406 ops/ms
해석:
*   Caffeine의 독주: 쓰기 작업에서도 Caffeine은 Ehcache보다 약 1.8배, Guava보다 약 2.6배 빠릅니다.
*   동시성 처리: Caffeine은 내부적으로 Ring Buffer와 비동기 이벤트 처리를 사용하여, 여러 스레드가 동시에 락을 걸려고 할 때 발생하는 병목(Contention)을 매우 효율적으로 해결합니다. 반면 Guava는 전통적인 Segment Lock 방식을 사용하여 동시 쓰기 시 성능 저하가 큽니다.

 

3. GC(Garbage Collection) 및 메모리 분석


*   gc.alloc.rate.norm (작업 1회당 메모리 할당량, 낮을수록 좋음)
    *   읽기(Read): Ehcache(15.8 B) < Caffeine(17.3 B) << Guava(39.6 B)
        *   Guava는 읽기만 해도 내부적으로 꽤 많은 임시 객체를 만듭니다.
    *   쓰기(Write): Guava(137.6 B) < Caffeine(232.3 B) < Ehcache(278.8 B)
        *   재미있는 점은 쓰기 시 메모리 할당량은 Guava가 가장 적다는 것입니다.
        *   하지만 성능은 가장 느립니다. 이는 Guava의 느린 속도가 "메모리 할당" 때문이 아니라 "CPU 연산이나 락 대기(Lock Contention)" 때문임을 시사합니다.
        *   Caffeine은 더 많은 객체를 생성(Window TinyLFU 알고리즘 등을 위한 메타데이터 관리)하지만, 락을 최소화하는 구조 덕분에 처리량은 훨씬 높습니다.

 

결론: 무엇을 써야 할까?

  1. 성능이 가장 중요하다면? 고민 없이 Caffeine입니다.
  2. 캐시 데이터가 너무 커서 GC가 걱정된다면? Ehcache의 Off-heap 설정을 권장합니다.
  3. 별도 라이브러리 추가 없이 간단한 캐시가 필요하다면? Guava가 좋은 선택입니다.

🔗 참고 문헌 및 출처