feat(i18n): 번역 작업을 그룹 단위로 처리한다
This commit is contained in:
@@ -33,22 +33,26 @@ interface TranslationJobRepository : JpaRepository<TranslationJob, Long> {
|
||||
@Param("sourceHash") sourceHash: String
|
||||
): TranslationJob?
|
||||
|
||||
fun findFirstByStatusAndNextRetryAtLessThanEqualOrderByCreatedAtAsc(
|
||||
status: TranslationJobStatus,
|
||||
nextRetryAt: LocalDateTime
|
||||
): TranslationJob?
|
||||
|
||||
@Query(
|
||||
value = """
|
||||
select id
|
||||
from translation_job
|
||||
where status = 'PENDING'
|
||||
and next_retry_at <= :now
|
||||
order by created_at asc
|
||||
limit 1
|
||||
select j.id
|
||||
from translation_job j
|
||||
join (
|
||||
select resource_type, resource_id, target_language
|
||||
from translation_job
|
||||
where status = 'PENDING'
|
||||
and next_retry_at <= :now
|
||||
order by created_at asc
|
||||
limit 1
|
||||
) g on j.resource_type = g.resource_type
|
||||
and j.resource_id = g.resource_id
|
||||
and j.target_language = g.target_language
|
||||
where j.status = 'PENDING'
|
||||
and j.next_retry_at <= :now
|
||||
order by j.created_at asc
|
||||
for update skip locked
|
||||
""",
|
||||
nativeQuery = true
|
||||
)
|
||||
fun findNextPendingJobIdForUpdate(@Param("now") now: LocalDateTime): Long?
|
||||
fun findNextPendingGroupJobIdsForUpdate(@Param("now") now: LocalDateTime): List<Long>
|
||||
}
|
||||
|
||||
@@ -20,33 +20,52 @@ class TranslationJobWorker(
|
||||
|
||||
@Scheduled(fixedDelayString = "\${sodalive.translation-job.fixed-delay-ms:600000}")
|
||||
fun runPendingJobs() {
|
||||
repeat(MAX_JOBS_PER_TICK) {
|
||||
if (!processNextJob()) return
|
||||
repeat(MAX_GROUPS_PER_TICK) {
|
||||
if (!processNextGroup()) return
|
||||
}
|
||||
}
|
||||
|
||||
fun processNextJob(): Boolean {
|
||||
val job = claimNextJob() ?: return false
|
||||
fun processNextGroup(): Boolean {
|
||||
val jobs = claimNextGroup()
|
||||
if (jobs.isEmpty()) return false
|
||||
|
||||
val firstJob = jobs.first()
|
||||
val succeededJobs = mutableListOf<TranslationJob>()
|
||||
val failedJobs = mutableListOf<Pair<TranslationJob, Exception>>()
|
||||
jobs.forEach { job ->
|
||||
try {
|
||||
ensureMemory(job)
|
||||
succeededJobs.add(job)
|
||||
} catch (ex: Exception) {
|
||||
failedJobs.add(job to ex)
|
||||
}
|
||||
}
|
||||
|
||||
if (failedJobs.isNotEmpty()) {
|
||||
succeededJobs.forEach { completeJob(it.id!!) }
|
||||
failedJobs.forEach { (job, ex) -> failJob(job.id!!, ex) }
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
ensureMemory(job)
|
||||
materializer.materialize(job.resourceType, job.resourceId, job.targetLanguage)
|
||||
completeJob(job.id!!)
|
||||
materializer.materialize(firstJob.resourceType, firstJob.resourceId, firstJob.targetLanguage)
|
||||
succeededJobs.forEach { completeJob(it.id!!) }
|
||||
} catch (ex: Exception) {
|
||||
failJob(job.id!!, ex)
|
||||
succeededJobs.forEach { failJob(it.id!!, ex) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun claimNextJob(): TranslationJob? {
|
||||
private fun claimNextGroup(): List<TranslationJob> {
|
||||
return transactionTemplate.execute {
|
||||
val jobId = translationJobRepository.findNextPendingJobIdForUpdate(LocalDateTime.now())
|
||||
?: return@execute null
|
||||
val job = translationJobRepository.findById(jobId).orElse(null)
|
||||
?: return@execute null
|
||||
job.status = TranslationJobStatus.RUNNING
|
||||
translationJobRepository.save(job)
|
||||
job
|
||||
}
|
||||
val jobIds = translationJobRepository.findNextPendingGroupJobIdsForUpdate(LocalDateTime.now())
|
||||
jobIds.mapNotNull { jobId ->
|
||||
val job = translationJobRepository.findById(jobId).orElse(null) ?: return@mapNotNull null
|
||||
job.status = TranslationJobStatus.RUNNING
|
||||
translationJobRepository.save(job)
|
||||
job
|
||||
}
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
private fun ensureMemory(job: TranslationJob) {
|
||||
@@ -129,7 +148,7 @@ class TranslationJobWorker(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_JOBS_PER_TICK = 20
|
||||
private const val MAX_GROUPS_PER_TICK = 5
|
||||
private const val MAX_ERROR_LENGTH = 1000
|
||||
private const val MAX_RETRY_COUNT = 3
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user