feat(cache): 인기 캐릭터 조회에 윈도우 기반 동적 캐시 키 적용

- ChatCharacterService.getPopularCharacters()에 @Cacheable 추가
- 키: popular-chat-character:<windowStartEpoch>:<limit>
- 윈도우(매일 20:00 UTC) 전환 시 자동으로 신규 키 사용 → 전일 순위 캐시와 분리 보장

Why: 동일 윈도우 내 반복 요청의 DB 부하를 줄이고, 경계 전환 시 자연스러운 캐시 갱신을 보장.
This commit is contained in:
Klaus 2025-09-14 17:43:53 +09:00
parent 4adc3e127c
commit 0574f4f629
2 changed files with 15 additions and 0 deletions

View File

@ -19,6 +19,7 @@ import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepositor
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository
import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@ -42,6 +43,10 @@ class ChatCharacterService(
* Spring Cache(@Cacheable) + 동적 + 고정 TTL(24h) 사용 * Spring Cache(@Cacheable) + 동적 + 고정 TTL(24h) 사용
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Cacheable(
cacheNames = ["popularCharacters_24h"],
key = "T(kr.co.vividnext.sodalive.chat.character.service.RankingWindowCalculator).now('popular-chat-character').cacheKey"
)
fun getPopularCharacters(limit: Long = 20): List<Character> { fun getPopularCharacters(limit: Long = 20): List<Character> {
val window = RankingWindowCalculator.now("popular-chat-character") val window = RankingWindowCalculator.now("popular-chat-character")
val topIds = popularCharacterQuery.findPopularCharacterIds(window.windowStart, window.nextBoundary, limit) val topIds = popularCharacterQuery.findPopularCharacterIds(window.windowStart, window.nextBoundary, limit)

View File

@ -123,6 +123,16 @@ class RedisConfig(
) )
) )
// 24시간 TTL 캐시: 인기 캐릭터 집계용
cacheConfigMap["popularCharacters_24h"] = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
GenericJackson2JsonRedisSerializer()
)
)
return RedisCacheManager.builder(redisConnectionFactory) return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig) .cacheDefaults(defaultCacheConfig)
.withInitialCacheConfigurations(cacheConfigMap) .withInitialCacheConfigurations(cacheConfigMap)