feat(home): 인기 커뮤니티 스냅샷 갱신 경로를 분리한다
This commit is contained in:
@@ -32,7 +32,7 @@ class RecommendationSnapshotFallbackService(
|
||||
executor: Executor? = null,
|
||||
private val homeWaitMillis: Long = HOME_WAIT_MILLIS,
|
||||
transactionManager: PlatformTransactionManager? = null
|
||||
) : AiCharacterSnapshotFallbackPort, CheerCreatorSnapshotFallbackPort {
|
||||
) : AiCharacterSnapshotFallbackPort, CheerCreatorSnapshotFallbackPort, PopularCommunitySnapshotFallbackPort {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
private val ownedExecutor: ExecutorService? = if (executor == null) {
|
||||
Executors.newFixedThreadPool(DEFAULT_WORKER_THREADS)
|
||||
@@ -65,6 +65,19 @@ class RecommendationSnapshotFallbackService(
|
||||
return refreshIfMissing(target(RecommendedSectionType.CHEER_CREATOR, CHEER_CREATOR_LOCK_KEY), offset, limit, nowUtc)
|
||||
}
|
||||
|
||||
override fun refreshPopularCommunityIfMissing(
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
nowUtc: LocalDateTime
|
||||
): List<RecommendationSnapshotRecord> {
|
||||
return refreshIfMissing(
|
||||
target(RecommendedSectionType.POPULAR_COMMUNITY, POPULAR_COMMUNITY_LOCK_KEY),
|
||||
offset,
|
||||
limit,
|
||||
nowUtc
|
||||
)
|
||||
}
|
||||
|
||||
private fun refreshIfMissing(
|
||||
target: FallbackTarget,
|
||||
offset: Long,
|
||||
@@ -186,6 +199,7 @@ class RecommendationSnapshotFallbackService(
|
||||
return when (sectionType) {
|
||||
RecommendedSectionType.AI_CHARACTER -> refreshService.refreshAiCharacterSnapshots(nowUtc)
|
||||
RecommendedSectionType.CHEER_CREATOR -> refreshService.refreshCheerCreatorSnapshots(nowUtc)
|
||||
RecommendedSectionType.POPULAR_COMMUNITY -> refreshService.refreshPopularCommunitySnapshots(nowUtc)
|
||||
else -> error("Unsupported fallback sectionType: $sectionType")
|
||||
}
|
||||
}
|
||||
@@ -207,6 +221,7 @@ class RecommendationSnapshotFallbackService(
|
||||
companion object {
|
||||
const val AI_CHARACTER_LOCK_KEY = "lock:recommendation-snapshot-refresh:AI_CHARACTER"
|
||||
const val CHEER_CREATOR_LOCK_KEY = "lock:recommendation-snapshot-refresh:CHEER_CREATOR"
|
||||
const val POPULAR_COMMUNITY_LOCK_KEY = "lock:recommendation-snapshot-refresh:POPULAR_COMMUNITY"
|
||||
private const val LOCK_WAIT_MILLIS = 300L
|
||||
private const val HOME_WAIT_MILLIS = 1_500L
|
||||
private const val DEFAULT_WORKER_THREADS = 2
|
||||
@@ -220,3 +235,11 @@ interface CheerCreatorSnapshotFallbackPort {
|
||||
nowUtc: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
): List<RecommendationSnapshotRecord>
|
||||
}
|
||||
|
||||
interface PopularCommunitySnapshotFallbackPort {
|
||||
fun refreshPopularCommunityIfMissing(
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
nowUtc: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
): List<RecommendationSnapshotRecord>
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.transaction.support.TransactionSynchronization
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -38,18 +37,12 @@ open class RecommendationSnapshotRefreshService(
|
||||
@Transactional
|
||||
fun refreshDailySnapshots(now: LocalDateTime) {
|
||||
val startedAt = System.currentTimeMillis()
|
||||
val snapshotAt = now
|
||||
.atZone(UTC_ZONE)
|
||||
.withZoneSameInstant(KST_ZONE)
|
||||
.toLocalDate()
|
||||
.minusDays(1)
|
||||
.atTime(23, 59, 59)
|
||||
val windowStart = snapshotAt.toLocalDate().minusDays(6).atStartOfDay()
|
||||
val snapshotAt = windowPolicy.previousKstDayUtcWindow(now).snapshotAt
|
||||
|
||||
runCatching {
|
||||
val aiCharacterCount = refreshAiCharacterSnapshotsWithSectionLock(now)
|
||||
val cheerCreatorCount = refreshCheerCreatorSnapshotsWithSectionLock(now)
|
||||
val popularCommunityCount = replacePopularCommunitySnapshots(windowStart, snapshotAt)
|
||||
val popularCommunityCount = refreshPopularCommunitySnapshotsWithSectionLock(now)
|
||||
RefreshCounts(aiCharacterCount, cheerCreatorCount, popularCommunityCount)
|
||||
}.onSuccess { counts ->
|
||||
afterCommit {
|
||||
@@ -119,6 +112,30 @@ open class RecommendationSnapshotRefreshService(
|
||||
return snapshots.size
|
||||
}
|
||||
|
||||
@Transactional
|
||||
open fun refreshPopularCommunitySnapshots(nowUtc: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)): Int {
|
||||
val startedAt = System.currentTimeMillis()
|
||||
val window = windowPolicy.previousKstSevenDayUtcWindow(nowUtc)
|
||||
val snapshots = queryPort.findPopularCommunitySnapshots(
|
||||
window.startUtc,
|
||||
window.endExclusiveUtc,
|
||||
POPULAR_COMMUNITY_SNAPSHOT_LIMIT
|
||||
)
|
||||
snapshotPort.replaceSnapshots(RecommendedSectionType.POPULAR_COMMUNITY, window.snapshotAt, snapshots)
|
||||
afterCommit {
|
||||
log.info(
|
||||
"event=popular_community_recommendation_snapshot_refresh_success " +
|
||||
"snapshotAt={} windowStartUtc={} windowEndExclusiveUtc={} savedCount={} elapsedMs={}",
|
||||
window.snapshotAt,
|
||||
window.startUtc,
|
||||
window.endExclusiveUtc,
|
||||
snapshots.size,
|
||||
System.currentTimeMillis() - startedAt
|
||||
)
|
||||
}
|
||||
return snapshots.size
|
||||
}
|
||||
|
||||
private fun refreshAiCharacterSnapshotsWithSectionLock(nowUtc: LocalDateTime): Int {
|
||||
val client = redissonClient ?: return refreshAiCharacterSnapshots(nowUtc)
|
||||
val lock = client.getLock(RecommendationSnapshotFallbackService.AI_CHARACTER_LOCK_KEY)
|
||||
@@ -151,6 +168,22 @@ open class RecommendationSnapshotRefreshService(
|
||||
return refreshCheerCreatorSnapshots(nowUtc)
|
||||
}
|
||||
|
||||
private fun refreshPopularCommunitySnapshotsWithSectionLock(nowUtc: LocalDateTime): Int {
|
||||
val client = redissonClient ?: return refreshPopularCommunitySnapshots(nowUtc)
|
||||
val lock = client.getLock(RecommendationSnapshotFallbackService.POPULAR_COMMUNITY_LOCK_KEY)
|
||||
|
||||
if (!lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
|
||||
log.info(
|
||||
"event=popular_community_recommendation_snapshot_refresh_lock_missed lockKey={}",
|
||||
RecommendationSnapshotFallbackService.POPULAR_COMMUNITY_LOCK_KEY
|
||||
)
|
||||
return 0
|
||||
}
|
||||
|
||||
unlockAfterTransaction(lock)
|
||||
return refreshPopularCommunitySnapshots(nowUtc)
|
||||
}
|
||||
|
||||
private fun unlockAfterTransaction(lock: org.redisson.api.RLock) {
|
||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
if (lock.isHeldByCurrentThread) {
|
||||
@@ -170,12 +203,6 @@ open class RecommendationSnapshotRefreshService(
|
||||
)
|
||||
}
|
||||
|
||||
private fun replacePopularCommunitySnapshots(windowStart: LocalDateTime, snapshotAt: LocalDateTime): Int {
|
||||
val snapshots = queryPort.findPopularCommunitySnapshots(windowStart, snapshotAt, POPULAR_COMMUNITY_SNAPSHOT_LIMIT)
|
||||
snapshotPort.replaceSnapshots(RecommendedSectionType.POPULAR_COMMUNITY, snapshotAt, snapshots)
|
||||
return snapshots.size
|
||||
}
|
||||
|
||||
private data class RefreshCounts(
|
||||
val aiCharacterCount: Int,
|
||||
val cheerCreatorCount: Int,
|
||||
@@ -198,7 +225,5 @@ open class RecommendationSnapshotRefreshService(
|
||||
private const val AI_CHARACTER_SNAPSHOT_LIMIT = 20
|
||||
private const val CHEER_CREATOR_SNAPSHOT_LIMIT = 16
|
||||
private const val POPULAR_COMMUNITY_SNAPSHOT_LIMIT = 20
|
||||
private val UTC_ZONE: ZoneId = ZoneId.of("UTC")
|
||||
private val KST_ZONE: ZoneId = ZoneId.of("Asia/Seoul")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user