feat(ranking): cold-start 스냅샷 생성을 위임한다
This commit is contained in:
@@ -20,6 +20,7 @@ class CreatorRankingQueryService(
|
||||
private val snapshotPort: CreatorRankingSnapshotPort,
|
||||
private val blockPort: CreatorRankingBlockPort,
|
||||
private val aggregationPort: CreatorRankingAggregationPort,
|
||||
private val snapshotJobService: CreatorRankingSnapshotJobService,
|
||||
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() },
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
@@ -36,6 +37,9 @@ class CreatorRankingQueryService(
|
||||
if (latestItems.isEmpty()) {
|
||||
if (snapshotPort.isSnapshotTableEmpty()) {
|
||||
val fallbackItems = aggregateColdStartFallback().toRankedItems()
|
||||
if (fallbackItems.isNotEmpty()) {
|
||||
delegateColdStartSnapshotRefresh()
|
||||
}
|
||||
val blockedCreatorIds = findBlockedCreatorIds(viewerMemberId = viewerMemberId, items = fallbackItems)
|
||||
return@runCatching QueryLogResult(
|
||||
result = CreatorRankingResult(
|
||||
@@ -127,6 +131,18 @@ class CreatorRankingQueryService(
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
private fun delegateColdStartSnapshotRefresh() {
|
||||
runCatching {
|
||||
snapshotJobService.ensureLastCompletedWeekSnapshotForColdStart()
|
||||
}.onFailure { ex ->
|
||||
log.warn(
|
||||
"event=creator_ranking_query_cold_start_snapshot_refresh_failure error={}",
|
||||
ex.message,
|
||||
ex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<CreatorRankingSnapshotRecord>.toRankedItems(): List<CreatorRankingItem> {
|
||||
return groupBy { it.finalScore }
|
||||
.toSortedMap(compareByDescending { it })
|
||||
|
||||
@@ -1,31 +1,49 @@
|
||||
package kr.co.vividnext.sodalive.v2.ranking.application
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicy
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingUtcRange
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobPort
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobRecord
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobStatus
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotJobTrigger
|
||||
import org.redisson.api.RedissonClient
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.TransactionDefinition
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class CreatorRankingSnapshotJobService(
|
||||
private val refreshService: CreatorRankingSnapshotRefreshService,
|
||||
private val jobPort: CreatorRankingSnapshotJobPort,
|
||||
private val redissonClient: RedissonClient,
|
||||
transactionManager: PlatformTransactionManager,
|
||||
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
private val periodPolicy = CreatorRankingPeriodPolicy()
|
||||
private val transactionTemplate = TransactionTemplate(transactionManager).also { template ->
|
||||
template.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun refreshLastCompletedWeekByScheduledJob() {
|
||||
val now = nowProvider()
|
||||
val period = periodPolicy.resolveLastCompletedWeek(now)
|
||||
val utcRange = periodPolicy.toUtcRange(period)
|
||||
withLastCompletedWeekPeriodLock { now, utcRange ->
|
||||
transactionTemplate.executeWithoutResult {
|
||||
refreshLastCompletedWeekByScheduledJob(now, utcRange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshLastCompletedWeekByScheduledJob(
|
||||
now: ZonedDateTime,
|
||||
utcRange: CreatorRankingUtcRange
|
||||
) {
|
||||
val job = jobPort.save(
|
||||
CreatorRankingSnapshotJobRecord(
|
||||
aggregationStartAtUtc = utcRange.startInclusiveUtc,
|
||||
@@ -89,6 +107,32 @@ class CreatorRankingSnapshotJobService(
|
||||
jobPort.markPending(jobId)
|
||||
}
|
||||
|
||||
fun ensureLastCompletedWeekSnapshotForColdStart() {
|
||||
withLastCompletedWeekPeriodLock { now, _ ->
|
||||
transactionTemplate.executeWithoutResult {
|
||||
refreshService.refreshLastCompletedWeek(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun withLastCompletedWeekPeriodLock(action: (ZonedDateTime, CreatorRankingUtcRange) -> Unit) {
|
||||
val now = nowProvider()
|
||||
val period = periodPolicy.resolveLastCompletedWeek(now)
|
||||
val utcRange = periodPolicy.toUtcRange(period)
|
||||
val lockName = "lock:creator-ranking-snapshot-refresh:${utcRange.startInclusiveUtc}:${utcRange.endExclusiveUtc}"
|
||||
val lock = redissonClient.getLock(lockName)
|
||||
|
||||
try {
|
||||
if (lock.tryLock(0, -1, TimeUnit.SECONDS)) {
|
||||
action(now, utcRange)
|
||||
}
|
||||
} finally {
|
||||
if (lock.isHeldByCurrentThread) {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun logJobStatusChanged(
|
||||
job: CreatorRankingSnapshotJobRecord,
|
||||
status: CreatorRankingSnapshotJobStatus,
|
||||
|
||||
Reference in New Issue
Block a user