Skip to content

Commit

Permalink
[Feat]: 캐시 갱신 volatile 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
bayy1216 committed Sep 10, 2024
1 parent 5599ee0 commit 9fce915
Showing 1 changed file with 11 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class NovelQueryService(
* 2. `novelIds`를 통해 소설을 조회한다.
*/
fun getNovelModelsByRanking(days: Int, size: Int, page: Int): List<NovelModel.Main> {
val localLockCheckNum = lockCheckNum

val cache: List<NovelIdAndScore>? =
novelRankingCacheStore.getNovelIdRankingByPage(days = days, size = size, page = page)

Expand All @@ -41,13 +43,16 @@ class NovelQueryService(
}
}

val localLockCheckNum = lockCheckNum
// A. 스케줄링 노드의 실패로 인해 캐시가 없는경우(방어로직)
// B. 최근 기간동안 `event`가 하나도 없어서 스코어링 `novel`이 없는 경우
// -> 실시간으로 스코어링을 조회하여 캐시를 저장한다.
// API 서버간의 분산락은 사용하지 않는다 (복잡성을 줄이기 위해)
return cacheUpdateLock.withLock {
if (localLockCheckNum != lockCheckNum) { // 다른 스레드가 먼저 캐시를 업데이트 했을 경우
if (localLockCheckNum != lockCheckNum) {
// 다른 쓰레드에 의해 캐시가 갱신되었다면, 현재쓰레드가 진입한 시점에서 lockCheckNum은 증가하였다.
// 즉, 같지 않다면, 캐시 조회전에 둘다 같은 lockCheckNum이였고, 캐시 갱신이 일어났다.
// 같다면, 캐시가 갱신이 안되었고, 현재 쓰레드가 락을 점유하였다.
// 같은데, 다른 쓰레드가 캐시를 갱신하였다면, 해당 임계구역을 진입하지 못했다.
// Volatile 변수를 사용했기 때문에 CPU 캐시에 저장되지 않고, 메인 메모리에 저장되어 쓰레드간 공유된다.
novelRankingCacheStore.getNovelIdRankingByPage(days = days, size = size, page = page)?.let {
val novelIds = it.map { novelAndScore -> novelAndScore.novelId }
return@withLock readOnly {
Expand All @@ -57,25 +62,26 @@ class NovelQueryService(
}
}

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
return@withLock emptyList()
}

novelRankingCacheStore.saveNovelIdAndScoresAll(days, scoresByOrder) // 캐시 저장
lockCheckNum += 1 // 캐시 업데이트 했으므로 lockCheckNum 증가
val novelIds = scoresByOrder.map { it.novelId }
return@withLock readOnly { // 소설 DB 조회, // TX BLOCK
val novels = novelReader.findNovelsByIdsIn(novelIds)
return@readOnly novels.map { NovelModel.Main.from(it)() }
}
}

}

companion object {
private val cacheUpdateLock = ReentrantLock()
@Volatile // CPU 캐시 불일치 방지, JIT 최적화 방지
private var lockCheckNum = 0
private val log = org.slf4j.LoggerFactory.getLogger(this::class.java)
}
Expand Down

0 comments on commit 9fce915

Please sign in to comment.