구글의 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