Kotlin&Springboot 환경에 CaffeineCache 적용

Naeyoung.Kim·2023년 2월 23일
1

CaffeineCache

구글의 Guava’s cache 와 ConcurrentLinkedHashMap 설계 경험을 바탕으로 개선한 캐시 라이브러리

공식 GitHub - Caffeine cache

캐시 제거 정책으로 Window TinyLfu 알고리즘을 사용하여 최적의 적중률로 캐시가 제거된다고 한다.

Why not EhCache?

Ehcache 에 비해 지원하는 기능은 적지만 상대적으로 가볍고 성능이 좋다.
(공식 GitHub 에도 High performance를 강조하고 있다.)

  • Read (75%) / Write (25%) 성능 측정 벤치마크 자료 (출처)

프로젝트 특성상 로컬 캐시에 많은 기능이 필요하지 않았기 때문에, 상대적으로 가벼운 CaffeineCache가 더 이점이 있을것으로 판단하여 적용하게 되었다.

Kotlin+Springboot 환경에 적용하기

spring cache & caffeine cache 의존성 추가

dependencies {
	/** cache **/
	implementation("org.springframework.boot:spring-boot-starter-cache")
 	implementation("com.github.ben-manes.caffeine:caffeine")
}

@EnableCaching 설정

@EnableCaching
class Application

fun main(args: Array<String>) {
	val application = SpringApplication(Application::class.java)
 	application.run(*args)
}

CacheType enum 추가

enum class CacheType(val cacheName: String, val expireAfterWrite: Long, val maximumSize: Long) {
	ELEMENTS(
		"cacheElements", // 캐시 이름
		60 * 60, // 캐시가 write 된 시점으로부터 만료 시간(초 단위)
		10 // 최대 사이즈
	)
}

CacheManager @Bean 설정

@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
	}
}

Cache 사용

@Service
class ElementCachedService internal constructor(
	private val elementRepository: ElementRepository
) {
	@Cacheable("cacheElements")
	fun findElements(): List<String> {
		return elementRepository.findAll()
	}

	@CacheEvict("cacheElements")
	fun removeElements() {}
}

Test (springmockk 사용)

현재 프로젝트에서는 mocking을 위해 Mockk를 사용중인 상태인데, MockKMockBean 기능을 제공하지 않아서 springmockk의존성을 추가해야만 했다.

springmockk 의존성 추가

testImplementation("com.ninja-squad:springmockk:3.1.1")

MockTest

@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를 적극 사용하게 되지 않을까 싶다.


Refferences

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

0개의 댓글