구글의 Guava’s cache 와 ConcurrentLinkedHashMap 설계 경험을 바탕으로 개선한 캐시 라이브러리
공식 GitHub - Caffeine cache
캐시 제거 정책으로 Window TinyLfu 알고리즘을 사용하여 최적의 적중률로 캐시가 제거된다고 한다.
Ehcache 에 비해 지원하는 기능은 적지만 상대적으로 가볍고 성능이 좋다.
(공식 GitHub 에도 High performance를 강조하고 있다.)

프로젝트 특성상 로컬 캐시에 많은 기능이 필요하지 않았기 때문에, 상대적으로 가벼운 CaffeineCache가 더 이점이 있을것으로 판단하여 적용하게 되었다.
dependencies {
/** cache **/
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("com.github.ben-manes.caffeine:caffeine")
}
@EnableCaching
class Application
fun main(args: Array<String>) {
val application = SpringApplication(Application::class.java)
application.run(*args)
}
enum class CacheType(val cacheName: String, val expireAfterWrite: Long, val maximumSize: Long) {
ELEMENTS(
"cacheElements", // 캐시 이름
60 * 60, // 캐시가 write 된 시점으로부터 만료 시간(초 단위)
10 // 최대 사이즈
)
}
@Configuration
class CacheConfig {
@Bean
fun cacheManager(): CacheManager {
val cacheManager = SimpleCacheManager()
val caches = CacheType.values().map {
CaffeineCache(
it.cacheName,
Caffeine.newBuilder()
.expireAfterWrite(it.expireAfterWrite, TimeUnit.SECONDS)
.maximumSize(it.maximumSize)
.build()
)
}
cacheManager.setCaches(caches)
return cacheManager
}
}
@Service
class ElementCachedService internal constructor(
private val elementRepository: ElementRepository
) {
@Cacheable("cacheElements")
fun findElements(): List<String> {
return elementRepository.findAll()
}
@CacheEvict("cacheElements")
fun removeElements() {}
}
현재 프로젝트에서는 mocking을 위해 Mockk를 사용중인 상태인데, MockK는 MockBean 기능을 제공하지 않아서 springmockk의존성을 추가해야만 했다.
testImplementation("com.ninja-squad:springmockk:3.1.1")
@SpringBootTest
@ExtendWith(MockKExtension::class)
class ElementCachedServiceMockTest @Autowired constructor(
private val cachedService: ElementCachedService
) {
@MockkBean
private lateinit var elementRepository: ElementRepository
@BeforeEach
fun setUp() {
every { elementRepository.findAll() } returns listOf("test1", "test2", "test3")
}
@Test
fun findElementsTest() {
cachedService.findElements()
cachedService.findElements()
verify(exactly = 1) { elementRepository.findAll() }
}
@AfterEach
fun tearDown() {
cachedService.removeElements()
}
}
테스트 결과 캐시 기능이 정상적으로 적용되는 것을 확인했다.
처음에는 Kotlin + Springboot + Ehcache적용 사례가 꽤 많아서 자료를 찾기 용이했고, 업무에서 작업하는 프로젝트도 대부분 Ehcache를 사용하고 있었기 때문에 CaffeineCache 사용을 고민했다. 하지만 기존 프로젝트에서도 Ehcache의 기능을 온전히 활용하지 않고 있다는 점, (개발자피셜 자료이긴 하지만) 성능상으로도 Ehcache에 비해 훨씬 높은 수치를 보여준다는 점에서 사용하지 않을 이유가 없었다.
물론 Kotlin 에 적용한 자료는 없어서 약간의 실험(?)이 필요했지만, 그리 어렵지 않게(심지어 더 간결하게) 적용이 가능했기 때문에, 앞으로도 CaffeineCache를 적극 사용하게 되지 않을까 싶다.
https://github.com/ben-manes/caffeine/wiki
https://wave1994.tistory.com/182
https://erjuer.tistory.com/127
https://gngsn.tistory.com/157
https://oingdaddy.tistory.com/385
https://github.com/Ninja-Squad/springmockk
https://findmypiece.tistory.com/346