cache 101 μ리μ¦λ web application μ κ°λ°νλ©° λ§μ£Όνλ cache μ λν΄ νμν μ§μκ³Ό λꡬλ€μ μ¬μ©λ²μ νμ΅νλ μ리μ¦μ λλ€.
μ μμλλ‘ κΈμ μ½μΌμλ©΄ νμ΅μ λ λ§μ λμμ΄ λ©λλ€.
μ€λμ Spring μμ μ 곡νλ Cache μ λν΄μ μ΄μΌκΈ° ν΄λ³Ό κ²μ΄λ€
Spring μμλ Cache μ λνμ¬ Spring Transaction κ³Ό λ§μ°¬κ°μ§λ‘ λμ μΆμνλ₯Ό μ 곡νλ€
@Transactional // spring transaction support
public Todo create() {}
@Cacheable // spring cache support
public Todo create() {}
Spring μμλ μ΄λ₯Ό Cache Abstraction μ΄λΌκ³ λΆλ₯΄λλ°, μ΄λ² μκ°μλ κ·Έ κ°λ κ³Ό case-study λ₯Ό ν΅ν΄ μ¬μ© λ°©λ²κΉμ§ μμλ³Ό μμ μ΄λ€.
λͺ©μ°¨
- Cache μ λν κ°λ΅ μ 리
- Spring Cache Abstraction μ΄λ?
- cache κ΄λ ¨λ ν΅μ¬ ν΄λμ€ μ€λͺ
- cache manager
- Spring Boot Starter μ ν¬ν¨λ κΈ°λ³Έ CacheManager
- cache κ΄λ ¨λ ν΅μ¬ ν΄λμ€ μ€λͺ
- Spring Cache μ ν΅μ¬, Cache μ΄λ
Έν
μ΄μ
- Cacheable
- CachePut
- CacheEvict
- Caching
- Case Study. Todo λ₯Ό λ§λ€μ΄λ³΄λ©° λ°°μ°λ Spring Cache
- step 1. μ ν리μΌμ΄μ μΈν λ° sample code ꡬν (cache configuration)
- step 2.
@Cacheable
μ ν΅ν cache μ‘°ν - step 3.
@CacheEvict
λ₯Ό μ΄μ©ν cache μ΄κΈ°ν - step 4.
@CachePut
μ μ΄μ©ν cache μ λ°μ΄νΈ - step 5.
@Caching
μ μ΄μ©ν λ³΅ν© cache κ΄λ¦¬
- Cache λ μ μ°κΈ°
- CachePut κ³Ό Type
- invalidation λλ½κ³Ό stale data
- cache expiration
Cache μ λν κ°λ΅ μ€λͺ
μ§λ Cache μ λν κ±°μ λͺ¨λ κ² μμ μ°λ¦¬λ μΊμμ λν κ°λ μ , μ΄λ‘ μ λ΄μ©λ€μ μ΄ν΄νμλ€.
μ§λ μκ° μ΄μΌκΈ°νλ μΊμμ λν΄μ μμ£Ό κ°λ΅νκ² μμ½ν΄λ³΄μ
- μΊμλ web application μ μλ΅/μ²λ¦¬ μ±λ₯ ν₯μμ μν΄ μ¬μ©λλ€
- μΊμλ μ΄μ μ μ°μ°λ κ°μ μ¬μ°μ° νμ§ μλλ‘ λ―Έλ¦¬ μ μ₯νλ€
- κ³Όκ±°μ μ‘°νν λ°μ΄ν°κ° λ³νμ§ μμλ€λ©΄, μ΄λ₯Ό λΉ λ₯΄κ² μ κ·Όν μ μλλ‘ μ μ₯νλ€
- λ°μ΄ν°λ₯Ό νΉμ 곡κ°μ μ μ₯νλ κ²μ
μΊμ±νλ€!
λΌκ³ νννλ€
- μΊμ μ μ₯μμ λ°μ΄ν°λ₯Ό μ‘°ννμμ λλ
- κ°μ΄ μ‘΄μ¬νλ©΄ cache hit
- μ‘΄μ¬νμ§ μμΌλ©΄ cache miss
- μΊμκ° κ½ μ°¨λ©΄ λ°©μΆ, eviction μ μνν΄μ€μΌ νλ€
- λ§λ£ μκ°μ μν
expiration
- μ μ₯μμ lack of memory μ μν
replacement
- λ§λ£ μκ°μ μν
λ§μ½ κΈ°μ΅μ΄ λμ§ μκ±°λ μ²μ 보λ μ¬λλ€μ΄λΌλ©΄ μμ κΈμ λ€μ λ³΄κ³ μλ μ’λ€
Spring μ Cache Abstraction μ΄λ?
2012 λ Spring framework 3.1 λ²μ μ΄ λ¦΄λ¦¬μ¦ λκ³ 4.1 λ²μ μ¦μμ Spring Integration μ νμ λͺ¨λλ‘ Cache Abstraction μ΄λΌλ λͺ¨λμ΄ ν¨κ» ν¬ν¨λμ΄ λ¦΄λ¦¬μ¦ λμλ€,
JPA μ λ§μ°¬κ°μ§λ‘ μΊμ±λ μμ JCache (JSR-107) μ΄λΌκ³ λΆλ¦¬λ μΊμ±μ λν νμ€μ΄ μ‘΄μ¬νμλλ°, 2012λ λ μΆμν Spring 3.1 λ²μ κ³Ό 4.1 λ²μ μ΄νλΆν° μ΄λ₯Ό 곡μμ μΌλ‘ μ§μ λ° κ°λ ₯ν νμ₯ κΈ°λ₯μ μ 곡νλ€
Spring μμλ JCache μμ μ¬μ©ν μ μλ κΈ°λ₯λ€ μΈμλ λμ± λ€μν κΈ°λ₯μ μ 곡νλ€.
λνμ μΌλ‘λ λ¨μΌ μΊμλ§ μ§μνλ JCache μ λ¬λ¦¬ CacheResolver
λ₯Ό ν΅ν΄ cache μ name μ μ§μ ν μ μμ΄ multi cache ꡬμ±μ΄ κ°λ₯νλ€
μ΄μ μμ¬λ μ¬κΈ°κΉμ§λ§ μμλ³΄κ³ , μ€μ λ‘ Spring Cache Abstraction λ₯Ό μ§ν±νλ ꡬμ±μμλ€μ λν΄μ κ°λ΅ν μμ보μ
Spring Cache Abstraction μ ν΅μ¬ μμλ€
Spring Cache Abstraction μμλ ν¬κ² 3κ°μ§ ν΄λμ€κ° μ‘΄μ¬νλ€
- CacheManager
- Cache
- CacheResolver
μ΄ 3κ°μ ν΅μ¬ ν΄λμ€λ€κ³Ό μ¬λ¬κ°μ§ CacheOperation λ€μ ν΅ν΄ Spring μ AOP λ‘ μΊμ±μ μ§μνλ€
1. CacheManager
CacheManager λ μ΄λ¦μμλΆν° μ μ μλ― Spring cache abstraction μ κ°μ₯ ν΅μ¬μ μΈ ν΄λμ€μ΄λ€.
Redis, Caffeine νΉμ Ehcache μ κ°μ μΊμ ꡬν체μ μΈμ€ν΄μ€λ₯Ό μμ±/κ΄λ¦¬νλ μν μ μννλ€.
Spring Cache λ₯Ό μ¬μ©νλ €λ©΄ νμμ μΌλ‘ CacheManager
μΈμ€ν΄μ€λ₯Ό λ±λ‘ν΄μ€μΌ νλ€.
μ΄ CacheManager interface μ DIP λλΆμ μ°λ¦¬κ° μ¬λ¬κ°μ§ cache μ ꡬν체λ€μ μνλ μμ μ νΈλ¦¬νκ² λ³κ²½μ΄ κ°λ₯ν΄μ‘λ€
2. CacheResolver μ Cache
CacheResolver
λ name μ κΈ°λ°μΌλ‘ Cache λ₯Ό μ°Ύμ μ μκ² ν΄μ€λ€.
μμ λ§ν JCache μ λ€λ₯Έ μ μΈ name μ ν΅ν΄ multi cache κ° κ°λ₯νλλ‘ νλ μν μ μννλ€
CacheResolver λ₯Ό ν΅ν΄ Cache λΌλ κ°μ²΄λ₯Ό μ°Ύμμ€λ©΄, μ°λ¦¬λ ν΄λΉ κ°μ²΄μκ² Put, Evict λ μ‘°ν λͺ λ Ήμ μννλ€.
Spring Boot μ Cache Abstraction
κΈ°λ³Έμ μΌλ‘ Spring Cache Abstraction μ ꡬνμ΄ μλ μΆμμΌλ‘ μ¦, Implementation μ λ±λ‘ν΄μ€μΌ νλ€
νμ§λ§ spring-boot-starter-cache
λ₯Ό gradle μμ‘΄μΌλ‘ μΆκ°νλ©΄ autoconfiguration μ μν΄ κΈ°λ³Έ ꡬνμ²΄κ° λ±λ‘λλ€.
μ΄ ν΄λμ€λ spring-boot-autoconfigure
μ μ‘΄μ¬νλ SimpleCacheConfiguration
ν΄λμ€μ΄λ€
ν΄λΉ ν΄λμ€λ₯Ό 보면 CacheManager Bean μ΄ μλ€λ©΄ κΈ°λ³ΈμΌλ‘ ConcurrentCacheManager
λ₯Ό μ¬μ©νλ€.
ConcurrentCacheManager
λ΄λΆμ μΌλ‘λ μΌλ°μ μΈ HashMap μμ segmented locking μ μΆκ°νμ¬ Thread-Safe λ₯Ό 보μ₯νλ ConcurrentHashMap μ μ΄μ©νλ€.
CacheManager λ±λ‘νκΈ°
CacheManager Bean μ Redis λ Caffeine κ³Ό κ°μ λ€λ₯Έ ꡬν체λ₯Ό μ΄μ©νλ κ² μμ κ°λ₯νλ€.
μ°λ¦¬κ° μ§μ Bean μΌλ‘ RedisCacheManager
λ CaffeineCacheManager
λ₯Ό λ±λ‘ν΄μ£Όκ±°λ Configuration property λ₯Ό μ΄μ©νλ©΄ λλ€.
spring cache μ ConfigurationProperties
ν΄λμ€λ₯Ό 보면 "spring.cache"
λΌλ μ€μ κ°μ κΈ°λ°μΌλ‘ λμνλλ°, λ΄λΆμ μΌλ‘λ Redis property, Caffeine property λ±μ μ§μνλ―λ‘ μμΈν μμ±κ³Ό configuration μ Spring docs-Configuring the Cache Storage λ₯Ό νμΈνμ
Spring Cache μ ν΅μ¬, Cache μ΄λ Έν μ΄μ
Spring Cache μ λ€λ₯Έ Spring family μ λμΌνκ² μ΄λ Έν μ΄μ μ ν΅ν μ μΈμ μΌλ‘ μΊμλ₯Ό κ΄λ¦¬ν μ μκ² ν΄μ€λ€.
λ€μ νμμ μ΄λ Έν μ΄μ λ§ μ΄ν΄ν΄λ Spring Cache λ₯Ό κ°λ³κ² μ΄μ©νλλ° λ¬΄λ¦¬κ° μμ κ²μ΄λ€
Spring Cache Abstraction μμλ 5κ°μ§ μ΄λ Έν μ΄μ μ μ 곡νλ€.
- @Cacheable -> Cache μ κ°μ μ°κΈ°/μ½κΈ°
- @CachePut -> Cache μ κ°μ κ°±μ
- @CacheEvict -> Cache λ₯Ό μ΄κΈ°ν
- @Caching -> cache μ°μ°μ νλλ‘ λ§λ€μ΄μ£ΌκΈ°
- @CacheConfig -> cache μ€μ 곡μ νκΈ°
μλμ λμ¬ case-study μμ μμΈν μμ보λλ‘ νκ³ μ§κΈμ κ°λ λ§ νλμ© ν΅μ¬λ§ μμ보μ
@Cacheable
λ©μλμ νΈμΆ κ²°κ³Ό, μ¦ return value λ₯Ό cache μ μ μ₯νκ³ μ΄ν λμΌν μμ²μ΄λΌκ³ νλ¨λ κ²½μ° μ€μ λ©μλλ₯Ό νΈμΆνμ§ μκ³ cache μ μ‘΄μ¬νλ κ°μ λ°ννλ€
μ΄λ€ λ°μ΄ν°λ₯Ό μΊμ±νλ€ λΌκ³ νμ λ @Cacheable
λ§ λͺ
μνλ©΄ λλ€.
@CachePut
λ©μλμ νΈμΆ κ²°κ³Ό, μ¦ return value λ₯Ό κ°μ λ‘ μΊμμ μ μ₯/κ°±μ νλ€
λ³΄ν΅ caching λ λ°μ΄ν°λ₯Ό update ν λ μ¬μ©νλ€
@CacheEvict
cache μ μ‘΄μ¬νλ λ°μ΄ν°κ° stale λμ΄ invalidate μν¬ λ, μ¦ μΊμλ₯Ό μ΄κΈ°νν λ μ¬μ©νλ€
cache λ μ ν©μ±μ΄ μ€μνλ€. μ΄λ€ λ°μ΄ν°λ₯Ό @Cacheable
λ‘ μΊμμ μ μ¬νλλ°, κ·Έ λ°μ΄ν°κ° μ
λ°μ΄νΈ λμλ€.
Put μ ν΅ν΄ νλνλ update νκΈ°λ νλ€κ³ ν μλ μμ λλ Evict λ₯Ό ν΅ν΄μ μΊμ±λ λ°μ΄ν°λ₯Ό λ λ €λ²λ¦¬λ κ²λ μ’μ λ°©λ²μ΄λ€.
@Caching
μ¬λ¬ μΊμ μ°μ°λ€μ νλλ‘ λ¬Άμ λ μ¬μ©νλ€
μ΄λ€ λ©μλ μμμλ cache λ λ°μ΄ν°κ° update λλ©° λμμ λ€λ₯Έ cache μλ evict κ° λμ΄μΌ ν λ @Caching
μ μ¬μ©νλ©΄ μ’λ€
Case Study. Todo λ₯Ό λ§λ€μ΄λ³΄λ©° λ°°μ°λ Spring Cache
μ΄μ μ€μ λ‘ κ°λ¨ν Todo λ₯Ό λ±λ‘νκ³ μ‘°ν, μμ ν μ μλ μλΉμ€μ Spring Cache λ₯Ό μΆκ°ν΄λ³΄μ
λ΄λΆμ μΌλ‘λ 볡μ‘ν μ μμ§λ§ Spring Cache κ° μ λ§ μΆμνλ₯Ό μ ν΄λμκΈ° λλ¬Έμ, μ½κ² μ¬μ©ν μ μλ€
λ€μ 4κ°μ§ step μ ν΅ν΄μ cache λ₯Ό κ²½νν΄λ³΄κ³ μΆκ°μ μΌλ‘ μΊμλ₯Ό μ¬μ©ν λ λ§μ£Όν μ μλ λ¬Έμ λ ν¨κ» μμλ³Ό κ²μ΄λ€
- step 1. μ ν리μΌμ΄μ μΈν λ° sample code ꡬν
- step 2. @Cacheable μ ν΅ν cache μ‘°ν
- step 3. @CacheEvict λ₯Ό μ΄μ©ν cache μ΄κΈ°ν
- step 4. @CachePut μ μ΄μ©ν cache μ λ°μ΄νΈ
- step 5. @Caching μ μ΄μ©ν λ³΅ν© μΊμ κ΄λ¦¬
μμ μ½λμ ν μ€νΈ νκ²½μ μν μμΈν μΈν λ° μΈλΆ ꡬν λ‘μ§λ€μ https://github.com/my-research/spring-cache μμ νμΈν μ μλ€.
step 1. Todo μ ν리μΌμ΄μ μΈν λ° μ½λ ꡬν
μ°λ¦¬κ° λ§λ€μ΄λ³Ό TODO application μ λ€μ 4κ°μ§μ API λ€μ μ 곡νκ³ μλ€.
API λ€μ μ±κ²©μ λ°λΌμ command μ query λ‘ λΆλ₯ν μ μλ€.
- TODO λ₯Ό μμ±νλ€ (command)
- TODO μ μνλ₯Ό λ³κ²½νλ€ (command)
- TODO μμΈλ₯Ό μ‘°ννλ€ (query)
- user κ° μμ ν λͺ¨λ TODO λ₯Ό μ‘°ννλ€ (query)
λΉ λ₯΄κ² μ 4κ°μ API λ₯Ό ꡬνν κ²μΈλ° μ¬μ€ ν΅μ¬μ todo λ₯Ό ꡬννλ κ²μ΄ μλλ―λ‘ ν΅μ¬ λ‘μ§λ§ 보μ¬μ€ κ²μ΄λ€.
μμΈν μ½λλ€μ μμ μ΄μΌκΈ° νλ git repository μμ νμΈν μ μλ€.
λ¨Όμ todo μ μνλ₯Ό λ³κ²½νλ command service λ₯Ό ꡬνν΄λ³΄μ
// TODO λ₯Ό μμ±νλ€
fun create(userId: Long, name: String): Todo {
val todo = Todo(
userId = userId,
name = name,
)
return repository.save(todo)
}
// TODO μ μνλ₯Ό λ³κ²½νλ€
fun transit(todoId: Long, status: String): Todo {
val todo = repository.findById(todoId).orElseThrow()
todo.transitTo(TodoStatus.valueOf(status))
return repository.save(todo)
}
κ·Έλ¦¬κ³ todo μ μνλ₯Ό μ‘°ννλ query service λ₯Ό ꡬνν΄λ³΄μ.
μ¬κΈ°μ λμ¬κ²¨λ΄μΌ ν μ μ repository μ μ‘°ννλ λ‘μ§μ μΊμλ₯Ό μ μ©ν ν κ·Ήμ μΈ μ±λ₯ ν₯μμ 체κ°νκΈ° μν΄ μλμ μΌλ‘ Thread sleep μ 쀬λ€λ μ μ΄λ€
// userId μ ν΄λΉνλ λͺ¨λ TODO λ₯Ό μ‘°ννλ€
fun findAllBy(userId: Long): List<Todo> {
SleepUtils.sleep()
return repository.findAllByUserId(userId).toList()
}
// TODO id λ₯Ό ν΅ν΄ μμΈλ₯Ό μ‘°ννλ€
fun findBy(id: Long): Todo {
SleepUtils.sleep()
return repository.findById(id).orElseThrow()
}
μ΄μ μμ λ‘μ§λ€μ http λ₯Ό ν΅νμ¬ νΈμΆν μ μλλ‘ κ°λ¨ν Controller λ§ κ΅¬νν΄μ£Όλ©΄ μ€μ΅ μ€λΉκ° λλλ€
step 2. @Cacheable μ μ΄μ©ν cache μ‘°ν
μ ν리μΌμ΄μ μ΄ μ€λΉλμμΌλ todo λ₯Ό νλ μμ±νκ³ userId λ‘ μ‘°νν΄λ³΄μ!
μ°λ¦¬λ μμμ thread sleep μ ν΅ν΄ latency λ₯Ό μλμ μΌλ‘ λ°μμμΌ°λ€. (μ½ 3μ΄)
ν΄λΉ λ©μλμ μΊμλ₯Ό μ μ©νμ¬ μ±λ₯ ν₯μμ μμΌλ³΄μ. μ°λ¦¬κ° λ°°μ λ @Cacheable
μ λͺ
μν΄μ£Όλ©΄ λλ€
@Cacheable(cacheNames = ["todosByUserId"])
fun findAllBy(userId: Long): List<Todo> {
// λ‘μ§ μλ΅
}
@Cacheable
λ₯Ό μ¬μ©ν λλ cacheName μ μ§μ ν΄μ€μΌ νλλ°, μ΄ cache name μ μ€μν μν μ μννλ€.
κ·ΈλΌ μμ κ·Έλ¦Όκ³Ό κ°μ΄ spring cache κ° λ΄λΆμ μΌλ‘ μΊμλ₯Ό cache name
(globally unique) -> cache key
(locally unique) μμλ‘ κ΅¬λΆνμ¬ μ μ₯νκ³ μ‘°ννλ€.
κ²°κ΅ μμ λ©μλκ° νΈμΆλλ©΄ todosByUserId
λΌλ cache μ userId(μλ₯Ό λ€μ΄ 1004) μ key λ₯Ό κ°μ§ Todo(id: 1, name:"λ°₯λ¨ΉκΈ°") λΌλ value κ° μ μ₯λλ€
κ²°κ³Ό
cache λ₯Ό μ μ©ν ν API call μ μ±λ₯μ΄ ν₯μνλ κ²μ λ무λλ λΉμ°νλ€
- 첫λ²μ¨° μμ² & μλ΅
- cache miss λ°μ 3s μμ
- μ°μ°μ κ²°κ³Όλ₯Ό cache μ μ μ₯
- λλ²μ§Έ μμ² & μλ΅
- cache hit λ°μ (n)ms μμ
step 3. @CacheEvict λ₯Ό μ΄μ©ν cache μ΄κΈ°ν
μ¬κΈ°μ λ§μ½ todo λ₯Ό λ μΆκ°νκ³ userId λ‘ λͺ¨λ todo λ₯Ό μ‘°ννλ©΄ μ΄λ»κ² λ κΉ?
μ°λ¦¬κ° μμ step μμ cache miss μ μν΄ todo μ 보λ€μ μΊμμ μ μ¬νμμΌλ μ΄ν λͺ¨λ API call μ cache hit κ° λ°μνμ¬ backing store μΈ database μ μ κ·Όμ νμ§ μμ κ²μ΄λ€.
κ²°κ΅ μ€μ λ‘λ todo κ° μΆκ°λμμ§λ§ cache μ μν΄ μ΅μ μ λ°μ΄ν°λ₯Ό μ 곡λ°μ§ λͺ»νκ² λλ μν©μ΄ λ°μνλ€.
μ΄λ, cache μ μ‘΄μ¬νλ κ³Όκ±°μ λ°μ΄ν°λ₯Ό stale data
λΌκ³ νκ³ cache λ₯Ό μ΅μ μΌλ‘ κ°±μ νκΈ° μν΄ cache invalidate
κ° λ°μν΄μΌ νλ€.
cache invalidate λ μ¬λ¬κ°μ§ λ°©λ²μ΄ μ‘΄μ¬νλλ°, κ°μ₯ μ¬μ΄ λ°©λ²μΌλ‘λ cache μ κ°μ μ§μλ²λ € cache miss λ₯Ό μ λνκ³ db μ λ€μ μ‘°ννλλ‘ νλ λ°©λ²μ΄λ€.
μ΄ λ°©λ²μ μ¬μ©νλ €λ©΄ @CacheEvict
λ₯Ό μ¬μ©νλ©΄ λλ€
@CacheEvict(cacheNames = ["todosByUserId"], key = "#userId")
fun create(userId: Long, name: String): Todo {
val todo = Todo(userId, name)
return repository.save(todo)
}
μ΄λ
Έν
μ΄μ
μ΄ λΆμ΄μλ λ©μλκ° νΈμΆλλ©΄ cacheName κ³Ό key μ mapping λ value λ€μ λͺ¨λ λͺ¨λ evict(λ°©μΆ, μ κ±°)νλ λͺ
λ Ήμ μννλ€.
@CacheEvict
κ·ΈλΌ μμ μ΄μΌκΈ°νλκ² μ²λΌ todo κ° μλ‘κ² μΆκ°λλ©΄ userId μ ν΄λΉλλ λͺ¨λ todo cache λ₯Ό μ§μλ²λ¦¬κΈ° λλ¬Έμ μΆν μ‘°νλ₯Ό μννλ client κ° stale data λ₯Ό λ°μ§ μκ² λλ€
step 4. @CachePut μ μ΄μ©ν cache update
userId λ‘ μ‘°νν λ cache λ₯Ό μ μ©νλλ°, μ΄λ²μλ todo detail μ μ‘°ννλ api μλ cache λ₯Ό μ μ©ν΄λ³΄μ
μμ λ§μ°¬κ°μ§λ‘ @Cacheable
μ μ΄μ©νλ©΄ λλ€. μ΄λ²μλ todoById λΌλ cache name μ μ§μ ν΄λ³΄μ.
@Cacheable("todoById")
fun findBy(id: Long): Todo {
SleepUtils.sleep()
return repository.findById(id).orElseThrow()
}
μ΄λ° μν©μμ λ§μ½ todo λ₯Ό update νλ©΄ cache λ μ΄λ»κ² λ κΉ?
κ·ΈλΌ μμ μ΄μΌκΈ°νλ λ°©μμΌλ‘ @CacheEvict(cacheNames = ["todoById"], key = "#userId")
λ₯Ό ν΅ν΄ update λ todo κ° μ
λ°μ΄νΈλ λλ§λ€ Eviction μ μννμ¬ cache λ₯Ό μ΅μ νμν¬ μ μμ κ²μ΄λ€.
νμ§λ§ cache evict λ λ§ κ·Έλλ‘ μΊμμμ λ°©μΆμμΌλ²λ¦¬λ μ°μ°μ΄κΈ° λλ¬Έμ νμ cache miss κ° λ°μνκ² λλ€.
κ·Έλμ μμ£Ό update λλ€λ©΄ cache evict & cache miss κ° μ¦μμ§λ―λ‘ μΊμλ₯Ό μ΄μ©ν μ±λ₯ ν₯μμ κΈ°λνκΈ°κ° μ΄λ ΅λ€.
μ΄λ° μν©μμλ @CachePut
μ ν΅ν΄ cache μ μ μ₯λ κ°μ update μμΌλ²λ¦¬λ λ°©λ²μ μ¬μ©νλ©΄ μ’μ ν΄κ²°μ±
μ΄ λ μ μλ€.
@CachePut(cacheNames = ["todoById"], key = "#todoId")
fun transit(todoId: Long, status: String): Todo {
val todo = repository.findById(todoId).orElseThrow()
todo.transitTo(TodoStatus.valueOf(status))
return repository.save(todo)
}
ν΄λΉ μ΄λ κ² @CachePut
μ μ¬μ©νκ² λλ€λ©΄, ν΄λΉ μ΄λ
Έν
μ΄μ
μ΄ λΆμ λ©μλμ return κ°μ cache μ μ§μ update νκ² λλ―λ‘ cache miss κ° λ°μν μΌμ΄ μλ€.
step 5. @Caching μ μ΄μ©ν λ³΅ν© μΊμ κ΄λ¦¬
μ§κΈ μ°λ¦¬λ cache λ₯Ό μ΄μ©ν΄μ λ€μκ³Ό κ°μ κ²μμ νλ€
- userId μ ν΄λΉνλ λͺ¨λ todo λ₯Ό μ‘°νν μ μλ€
- todo λ₯Ό μμ±/μΆκ°ν μ μλ€.
- todo κ° μΆκ°λλ©΄ userId μ μ°κ²°λ μΊμλ₯Ό evict νμ¬ stale μ λ°©μ§νμλ€.
- todo μ μνλ₯Ό λ³κ²½ν μ μλ€
- todoId μ μ°κ²°λ μΊμλ₯Ό update νμ¬ stale μ λ°©μ§νμλ€
νμ§λ§ ν κ°μ§ λ¬Έμ κ° μλ€. λ€μ νλ‘μ°λ₯Ό λ΄λ³΄μ
- todo μμ±
- userId λ‘ todo μ 체 μ‘°ν -> cache μ μ¬
- νΉμ todo μ λ°μ΄νΈ -> userId cache μλ λ°μ x
- userId λ‘ todo μ 체 μ‘°ν -> stale λ°μ΄ν° λ°ν
μ΄ μν©μ 보면 todo update μ°μ°μμ 2κ°μ cache λ₯Ό invalidation ν΄μ£Όμ΄μΌ νλ€λ κ²μ μλ―Ένλ€.
- todoById μΊμλ₯Ό update
- todosByUserId μΊμλ₯Ό evict
2λ²μ΄ update κ° μλ evict μΈ μ΄μ λ λ°νκ° λλ¬Έμ΄λ€.
@CachePut
μ λ©μλ νΈμΆ λ°νκ°μ cache μ update νλ€κ³ νλλ°, transit λ©μλμ λ°νκ°μ Todo
ν΄λμ€μΈ λ°λ©΄ todosByUserId cache μ μ μ₯λ value λ List<Todo>
μ΄λ―λ‘ λ°ν νμ
μ΄ λ€λ₯΄λ―λ‘ μ¬μ©ν μ μλ€.
λν param μΌλ‘ userId λ₯Ό λ°μ μ μμΌλ―λ‘ νΉμ key μ ν΄λΉνλ cache λ₯Ό μ§μΈ μ μλ€.
@CacheEvict
μ μμ± μ€μ allEntries
λΌλ μμ±μ΄ μλλ°, ν΄λΉ μμ±μ cache name μ ν΄λΉνλ λͺ¨λ μΊμ μνΈλ¦¬λ₯Ό μ§μλ²λ¦΄ μ μλ€.
μ΄λ κ² 2κ°μ cache μ΄λ
Έν
μ΄μ
μ μ¬μ©ν λμλ @Caching
μ΄λΌλ μ΄λ
Έν
μ΄μ
μ ν΅ν΄ μ¬λ¬κ°μ μΊμ μ°μ°μ νλλ‘ λ¬Άμ μ μλ€.
@Caching(
put = [CachePut(cacheNames = ["todoById"], key = "#todoId")],
evict = [CacheEvict(cacheNames = ["todosByUserId"], allEntries = true)]
)
fun transit(todoId: Long, status: String): Todo {
val todo = repository.findById(todoId).orElseThrow()
todo.transitTo(TodoStatus.valueOf(status))
return repository.save(todo)
}
Spring Cache λ μ μ°κΈ°
1. CachePut μ μ¬μ©νλ©° μ£Όμν μ
@CachePut
μ°μ°μ method μ λ°ν κ°μ΄ cache μ update λλ€.
μ΄ νΉμ± λλ¬Έμ λ°μν μ μλ λ¬Έμ κ° νλ μλ€.
cache update κ° runtime μ λ°μνκΈ° λλ¬Έμ λ©μλμ λ°ν κ°μ΄ κΈ°μ‘΄μ cache μ μ μ₯λ κ°μ²΄μ type κ³Ό λ§μ§ μλλ€λ©΄ @Cacheable
κ° λͺ
μλ λ‘μ§μμ ClassCastException
μ΄ λ°μν μ μλ€.
λ°λ‘ μμ step 5 μμ λ°μν μ μλ λ¬Έμ μ λμΌνλ€
compiler type check λ₯Ό μ΄μ©ν μ μκΈ° λλ¬Έμ νμ νμ μ λν΄μλ μ£Όμλ₯Ό κΈ°μΈμ¬μΌ νλ€
2. λ³΅ν© μΊμ μ°μ°μ μ¬μ©νλ©° μ£Όμν μ
μμ case-study μ step5 λ₯Ό 보면 μλ‘ λ€λ₯Έ μΊμ μ°μ°μ νλλ‘ λ¬Άμλλ°, @CacheEvict
λ μμ κ°μ μν©μμλ μ΅λν μ¬μ©νμ§ μλ κ²μ΄ λ°λμ§νλ€.
@CacheEvict
λ₯Ό μ¬μ©νκ² λλ€λ©΄ μμ°μ€λ½κ² cache key μ ν΄λΉνλ λ°μ΄ν°λ₯Ό μμ λ κ²μ΄κΈ° λλ¬Έμ μμ°μ€λ½κ² cache miss νλ₯ μ΄ μ¬λΌκ°λ€.
κ²°κ΅ μΊμλ‘ μΈν μ±λ₯ ν₯μμ κΈ°λνκΈ° μ΄λ €μμ§λ€.
κ·ΈλΌμλ λΆκ΅¬νκ³ μ°λ¦¬λ ν΄λΉ λ©μλμμ cache key(userId) λ₯Ό λ°μ μ μμΌλ CacheEvict
λ₯Ό μ¬μ©ν μ λ°μ μλ€.
μ΄λ CacheManager
λ₯Ό μ§μ μ¬μ©νλ€λ©΄ evict λμ put μ μνν μ μλ€.
μ¬μ€μ μ°λ¦¬κ° μ¬μ©νλ @Cacheable
μ΄λ @CachePut
κ³Ό κ°μ μ΄λ
Έν
μ΄μ
μ Spring AOP λ₯Ό μν κ²μ΄κ³ κ²°κ΅ λ΄λΆμ μΌλ‘λ CacheManager μκ² μ°μ°μ μμ²νλ κ²μ΄λ€.
λ€μκ³Ό κ°μ΄ CacheManager μκ² μ§μ μ°μ°μ νλ λ°©λ²λ μ‘΄μ¬νλ€.
@Caching(
put = [CachePut(cacheNames = ["todoById"], key = "#todoId")],
)
fun transit(todoId: Long, status: String): Todo {
val todo = repository.findById(todoId).orElseThrow()
todo.transitTo(TodoStatus.valueOf(status))
cacheManager.getCache("todosByUserId")
?.evict(todo.userId)
return repository.save(todo)
}
3. μΊμ λ§λ£ μ μ±
μΊμμλ expiration μ΄λΌλ κ°λ μ΄ μλ€.
νΉμ μκ°μ΄ μ§λλ μΊμκ° μ λ°μ΄νΈλμ§ μλλ€λ©΄ μμ ν΄λΉ μΊμλ₯Ό λ°©μΆμμΌλ²λ¦¬λ κ²μ΄λ€.
κΈ°λ³Έμ μΌλ‘ ConcurrentCacheManager μλ cache expiration μ΄ μμ§λ§ caffeine ꡬν체μλ expiration μ΄ μ‘΄μ¬νλ€.
μλλ Caffeine Cache λ₯Ό μ¬μ©ν λ μΆκ°ν΄μ£Όλ bean config λ₯Ό κ°μ Έμλ΄€λ€
@Configuration
class CacheConfig {
@Bean
fun caffeineCacheManager(caffeine: Caffeine<Any, Any>): CacheManager {
val manager = CaffeineCacheManager()
manager.setCaffeine(caffeine)
return manager
}
@Bean
fun caffeine(): Caffeine<Any, Any>? {
return Caffeine.newBuilder()
.initialCapacity(10)
.expireAfterWrite(Duration.of(100, ChronoUnit.SECONDS))
.recordStats()
}
}
μμ μ½λλ₯Ό 보면 expireAfterWrite
μ΅μ
μ ν΅ν΄ write μ΄ν μΈμ ν΄λΉ cache λ₯Ό expire μν¬μ§ λͺ
μν μ μλ€.
λ§μΉλ©°
μ΄λ κ² μ€λμ Spring Cache Abstraction μ ν΅ν΄μ cache μ λν κ°λ κ³Ό μ€μ κ·Έ ꡬνμ μμ보μλ€.
λ¨μν΄λ³΄μ΄λ todo application μ λ§λ€λλΌλ μ¬λ¬κ°μ§ μΊμ μ ν©μ±μ λν κ³ λ―Ό ν¬μΈνΈκ° μ‘΄μ¬νλ€λ κ²μ μ¬λ¬λΆλ νμΈνμ κ²μ΄λ€.
μ΄μ²λΌ cache λ μννΈμ¨μ΄λ₯Ό λΉ λ₯΄μ§λ§ 볡μ‘νκ² λ§λλ μμμ€ νλλ€.
μμ μμ μ²λΌ μμ£Ό λ³κ²½λλ λ°μ΄ν°μλ μΊμκ° λ§€μ° λΉν¨μ¨μ μ΄λ€. μ€νλ € κ΄λ¦¬ μμλ§ λ리λ κ²μ΄λ€.
μ΄μ²λΌ λ§μ§ μλ μν©μ μΊμλ₯Ό λμ νκ² λλ€λ©΄ μμ€ν μ 볡μ‘μ±μ λͺμ λΉ μ Έλ²λ¦¬κ² λλ μ£Όμνμ.
μλ§λ μ΄ μ리μ¦λ₯Ό μ λΆ μμ£Όνλ€λ©΄ μ΄λμ μΊμλ₯Ό λ°°μΉνκ³ μ΄λ€μμΌλ‘ μΊμ μ ν©μ±μ λ§μΆ°μ€μΌ ν μ§ insight λ₯Ό μ»μ΄κ° μ μμΌλ¦¬λΌ λ―Ώλλ€.
'π¬web application > - System architecture' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
cache 101 - μΊμμ λν κ±°μ λͺ¨λ κ² (2) | 2024.02.01 |
---|---|
Event-Driven-Architecture μ λν Overview μ μ¬λ¬ κ³ λ €μ¬νλ€ (0) | 2022.08.22 |
CQRS ν¨ν΄μ λν μ€ν΄ νκΈ° (5) | 2022.06.11 |
[Event-Driven-Architecture] Message Oriented Middlewareκ³Ό Message Brokerμ μ°¨μ΄ λ° μ리 (0) | 2021.05.19 |
λκΈ