๋ฐ์ํ
Caffeine Cache ๋?
๋ก์ปฌ์บ์๋ ํด๋น ๊ธฐ๊ธฐ์์๋ง ์ฌ์ฉ๋๋ ์บ์์ด๋ค. ์๋๊ฐ ๋น ๋ฅด์ง๋ง ๋ถ์ฐ ์์คํ ์ผ ๊ฒฝ์ฐ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๊นจ์ง ์ ์๋ค.
๋น์ฆ๋์ค ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ด๋์ ๋๊น์ง ์ ํฉ์ฑ์ ๋ง์ถฐ์ผํ ์ง, ์ผ๋ง๋ ์๋๊ฐ ์ค์ํ ์ง์ ๋ฐ๋ผ ์บ์๋ฅผ ์ ํํ๋ค.
Caffeine Cache๋ java ์บ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ๋์ ์ฑ๋ฅ์ ๊ฐ์ง๊ณ ์๋ ์บ์์ด๋ค.
Caffeine์ Window TinyLfu eviction ์ ์ฑ ์ ๊ฐ์ง๊ณ ์๋๋ฐ, ์ด๋ ์ต์ ์ ์ ์ค๋ฅ ์ ์ ๊ณตํ๋ค.
Setting
build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("com.github.ben-manes.caffeine:caffeine")
}
Cache Config ์์ฑ
@Configuration
@EnableCaching
class CacheConfig { }
-
CacheConfig ํ์ผ์ ์์ฑํ๊ณ , ์บ์๋ก ์ฌ์ฉํ Bean๋ค์ ์ ์ํด๋๋๋ค.
Cache Enum ์ ์
enum class CacheType (
private val cacheName: String,
private val expireAfterWrite: Long,
private val timeUnit: TimeUnit
) {
USER_INFO_CACHE(
USER_INFO_CACHE_NAME, // ์บ์ ์ด๋ฆ: UserInfo
10,
TimeUnit.SECONDS, // ๋ง๋ฃ ์๊ฐ: 10s
);
fun getCacheName() = cacheName
fun getExpireAfterWrite() = expireAfterWrite
fun getTimeUnit() = timeUnit
companion object {
const val USER_INFO: String = "UserInfo"
}
}
- Enum ํด๋์ค ๋ฐ ๊ฐ ํ๋์ ๋ํด Getter ์์ฑ
Cache Manager Bean ๋ฑ๋ก
@Bean
fun cacheManager(): CacheManager {
val cacheManager = SimpleCacheManager()
val caches = CacheType.values().map {
CaffeineCache(
it.getCacheName(),
Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(it.getExpireAfterWrite(), it.getTimeUnit())
.build()
)
}
cacheManager.setCaches(caches)
return cacheManager
}
- CacheType์ ์ ์๋ ์บ์๋ค์ CaffeineCache ๊ฐ์ฒด๋ก ์์ฑํ์ฌ, CacheManager์ ๋ฑ๋ก
Cache KeyGenerator
๋ณ๋์ keyGenerator๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋ ์๋์ SimpleKeyGenerator ์ฌ์ฉ
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
-
ํ๋ผ๋ฏธํฐ๊ฐ ์๋ค๋ฉด, SimpleKey.EMPTY๊ฐ key๋ก ์ฌ์ฉ
-
ํ๋ผ๋ฏธํฐ๊ฐ 1๊ฐ๋ผ๋ฉด, ํ๋ผ๋ฏธํฐ๋ฅผ key๋ก ์ฌ์ฉ
-
ํ๋ผ๋ฏธํฐ๊ฐ 2๊ฐ ์ด์์ด๋ผ๋ฉด, SimpleKey ๊ฐ์ฒด๋ก ์ฌ์ฉ
CustomKeyGenerator
@Bean("UserInfoCacheKeyGenerator")
fun userInfoCacheKeyGenerator(): KeyGenerator {
return KeyGenerator { _, method, params ->
if (params.size > 2) {
"${method.name}:${params[2]}"
} else {
"${method.name}:${UUID.randomUUID()}"
}
}
}
-
๋ฉ์๋๋ช ๊ณผ ํน์ parameter๋ฅผ ์ฌ์ฉํ์ฌ Key ์์ฑ
CacheConfig.kt ์ ์ฒด
@Configuration
@EnableCaching
class CacheConfig {
@Bean
fun cacheManager(): CacheManager {
val cacheManager = SimpleCacheManager()
val caches = CacheType.values().map {
CaffeineCache(
it.getCacheName(),
Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(it.getExpireAfterWrite(), it.getTimeUnit())
.build()
)
}
cacheManager.setCaches(caches)
return cacheManager
}
@Bean("UserInfoCacheKeyGenerator")
fun userInfoCacheKeyGenerator(): KeyGenerator {
return KeyGenerator { _, method, params ->
if (params.size > 2) {
"${method.name}:${params[2]}"
} else {
"${method.name}:${UUID.randomUUID()}"
}
}
}
enum class CacheType (
private val cacheName: String,
private val expireAfterWrite: Long,
private val timeUnit: TimeUnit
) {
USER_INFO_CACHE(
USER_INFO_CACHE_NAME, // ์บ์ ์ด๋ฆ: UserInfo
10,
TimeUnit.SECONDS, // ๋ง๋ฃ ์๊ฐ: 10s
);
fun getCacheName() = cacheName
fun getExpireAfterWrite() = expireAfterWrite
fun getTimeUnit() = timeUnit
companion object {
const val USER_INFO: String = "UserInfo"
}
}
}
Cache ์ฌ์ฉ
@Cacheable(cacheNames = [CacheType.USER_INFO], keyGenerator = "UserInfoCacheKeyGenerator")
fun getUserInfo(userId: String) {...}
-
defaultKeyGenerator ์ฌ์ฉ ์, ์๋ต ๊ฐ๋ฅ
TestCode
class UserInfoCacheTests {
@SpykBean
lateinit var cacheManager: CacheManager
lateinit var userInfoCache: Cache<Any, Any>
@BeforeEach
fun beforeEach() {
userInfoCache = (cacheManager.getCache(CacheConfig.CacheType.USER_INFO) as CaffeineCache).nativeCache
}
@Test
fun `USER_INFO ์บ์๊ฐ ๋์ํ๋ค`() {
getUserInfo("requestId", "userId")
getUserInfo("requestId", "userId")
assertEquals(1, userInfoCache.asMap().keys.size)
eventCache.asMap().map {
assertEquals("getUserInfo|userId", it.key)
assertNotNull(it.value)
}
assertEquals(1, userInfoCache.stats().hitCount())
assertEquals(1, userInfoCache.stats().missCount())
}
}
๋ฐ์ํ