From 76c8fa85595a33c73cd454d8c90df18e14c08160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=ED=99=8D=EC=84=9D?= <78216059+bayy1216@users.noreply.github.com> Date: Sun, 18 Aug 2024 01:07:22 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]:=20=EC=BA=90=EC=8B=9C=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../novel/application/NovelQueryService.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/reditus/novelcia/domain/novel/application/NovelQueryService.kt b/src/main/kotlin/com/reditus/novelcia/domain/novel/application/NovelQueryService.kt index 3f92a0e..e4020fc 100644 --- a/src/main/kotlin/com/reditus/novelcia/domain/novel/application/NovelQueryService.kt +++ b/src/main/kotlin/com/reditus/novelcia/domain/novel/application/NovelQueryService.kt @@ -34,21 +34,33 @@ class NovelQueryService( val cache: List? = novelRankingCacheStore.getNovelIdRankingByPage(days = days, size = size, page = page) - if(cache != null){ // 캐시가 존재하면 캐시를 반환 early return + if (cache != null) { // 캐시가 존재하면 캐시를 반환 early return return readOnly { val novels = novelReader.findNovelsByIdsIn(cache.map { it.novelId }) return@readOnly novels.map { NovelModel.Main.from(it)() } } } - // 스케줄링 노드의 실패로 인해 캐시가 없는경우(방어로직) - // 혹은 최근 기간동안 event가 하나도 없어서 스코어링 novel 이 없는 경우 + val localLockCheckNum = lockCheckNum + // A. 스케줄링 노드의 실패로 인해 캐시가 없는경우(방어로직) + // B. 최근 기간동안 `event`가 하나도 없어서 스코어링 `novel`이 없는 경우 // -> 실시간으로 스코어링을 조회하여 캐시를 저장한다. // API 서버간의 분산락은 사용하지 않는다 (복잡성을 줄이기 위해) return cacheUpdateLock.withLock { - log.warn("NovelScoringUseCase trigger by cache miss (days: $days)") + if (localLockCheckNum != lockCheckNum) { // 다른 스레드가 먼저 캐시를 업데이트 했을 경우 + novelRankingCacheStore.getNovelIdRankingByPage(days = days, size = size, page = page)?.let { + val novelIds = it.map { novelAndScore -> novelAndScore.novelId } + return@withLock readOnly { + val novels = novelReader.findNovelsByIdsIn(novelIds) + return@readOnly novels.map { NovelModel.Main.from(it)() } + } + } + } + + lockCheckNum += 1 // lock 내부이므로 AtomicInt 필요하지 않음 + log.warn("NovelScoringUseCase trigger by cache miss (days: $days, lockCheckNum: $lockCheckNum)") val scoresByOrder = novelScoringUseCase(days = days) // TX BLOCK - if(scoresByOrder.isEmpty()){ // 스코어링 결과가 없으면 early return + if (scoresByOrder.isEmpty()) { // 스코어링 결과가 없으면 early return return@withLock emptyList() } @@ -62,8 +74,9 @@ class NovelQueryService( } - companion object{ + companion object { private val cacheUpdateLock = ReentrantLock() + private var lockCheckNum = 0 private val log = org.slf4j.LoggerFactory.getLogger(this::class.java) }