diff --git a/label/label-to-review/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/label/label-to-review/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 4d27b0f..8b68d23 100644 Binary files a/label/label-to-review/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/label/label-to-review/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/label/label-to-review/.gradle/file-system.probe b/label/label-to-review/.gradle/file-system.probe index e860189..e5ffdf6 100644 Binary files a/label/label-to-review/.gradle/file-system.probe and b/label/label-to-review/.gradle/file-system.probe differ diff --git a/label/label-to-review/build/classes/java/main/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.class b/label/label-to-review/build/classes/java/main/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.class index 0ba19d4..dd6f141 100644 Binary files a/label/label-to-review/build/classes/java/main/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.class and b/label/label-to-review/build/classes/java/main/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.class differ diff --git a/label/label-to-review/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.java b/label/label-to-review/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.java index 82663b1..cbbd270 100644 --- a/label/label-to-review/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.java +++ b/label/label-to-review/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataLabelJobService.java @@ -33,7 +33,6 @@ public class TrainingDataLabelJobService { } public void runTask() { - // 프록시를 통해 호출해야 @Transactional이 적용됨 applicationContext .getBean(TrainingDataLabelJobService.class) .assignReviewerYesterdayLabelComplete(null); @@ -42,87 +41,136 @@ public class TrainingDataLabelJobService { @Transactional public void assignReviewerYesterdayLabelComplete(LocalDate baseDate) { + long jobStart = System.currentTimeMillis(); + log.info("[JOB-START] TrainingDataLabelJob start profile={}", profile); + try { - log.info("[Step 1-1] 라벨링 완료된 데이터 목록 조회한다."); - log.info("=== baseDate : {}", baseDate); - log.info("=== baseDate 있으면 해당 일자, 없으면 어제일자로 조회"); + + log.info("[Step 1-1] 라벨링 완료된 데이터 조회 시작 baseDate={}", baseDate); + List tasks = trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned(baseDate); - log.info("[Step 1-2] 목록 객체 건수 count : {}", tasks == null ? 0 : tasks.size()); - if (tasks.isEmpty()) { - log.info("[Step 1-3] 조회된 것 없어 return"); + int totalTasks = tasks == null ? 0 : tasks.size(); + + log.info("[Step 1-2] 조회된 작업 건수={}", totalTasks); + + if (tasks == null || tasks.isEmpty()) { + log.info("[Step 1-3] 조회된 데이터 없음 → 종료"); return; } - // 회차별로 그룹핑 - log.info("[Step 2-1] 회차별로 그룹핑 시작"); + log.info("[Step 2-1] 회차별 그룹핑 시작"); + Map> taskByRound = tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid)); - // 회차별 분배 - log.info("[Step 3-1] 회차별로 분배 시작"); + log.info("[Step 2-2] 회차 수={}", taskByRound.size()); + + int successRound = 0; + int skipRound = 0; + for (Map.Entry> entry : taskByRound.entrySet()) { + + long roundStart = System.currentTimeMillis(); + Long analUid = entry.getKey(); List analTasks = entry.getValue(); - // pending 계산 - log.info("[Step 3-2] 수행하는 회차 analUid: {}", analUid); - log.info("해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음"); + log.info( + "[ROUND-START] analUid={} tasks={}", analUid, analTasks == null ? 0 : analTasks.size()); + + log.info("[Step 3-2] 해당 회차 검수자 pending 조회"); + List pendings = trainingDataLabelJobCoreService.findInspectorPendingByRound(analUid); - log.info("검수자 수: {}", pendings == null ? 0 : pendings.size()); - if (pendings.isEmpty()) { - log.info("[Step 3-3] 할당된 검수자가 없으면 return"); + int reviewerCount = pendings == null ? 0 : pendings.size(); + + log.info("[Step 3-3] 검수자 수={}", reviewerCount); + + if (pendings == null || pendings.isEmpty()) { + log.warn("[ROUND-SKIP] 검수자 없음 analUid={}", analUid); + skipRound++; continue; } - log.info("[Step 4-1] 검수자 사번 List 생성"); List reviewerIds = pendings.stream().map(InspectorPendingDto::getInspectorUid).toList(); - // Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현 - log.info("[Step 4-2] 검수자 테이블 lock 걸리지 않게 처리"); + log.info("[Step 4-1] 검수자 목록={}", reviewerIds); + + log.info("[Step 4-2] 검수자 row lock 수행"); + trainingDataLabelJobCoreService.lockInspectors(analUid, reviewerIds); - // 균등 분배 - log.info("[Step 5-1] 검수자에게 라벨 작업 균등분배 시작"); - Map> assignMap = distributeByLeastPending(analTasks, reviewerIds); - log.info("[Step 5-2] 검수자에게 라벨 작업 균등분배 완료"); + log.info("[Step 5-1] 라벨 작업 균등 분배 시작"); + + Map> assignMap = distributeByLeastPending(analTasks, reviewerIds); + + log.info("[Step 5-2] 라벨 작업 균등 분배 완료"); + + assignMap.forEach( + (reviewerId, assignedTasks) -> + log.info( + "[Step 5-3] reviewerId={} assignedCount={}", + reviewerId, + assignedTasks == null ? 0 : assignedTasks.size())); + + log.info("[Step 5-4] reviewer batch update 시작"); - // reviewer별 batch update - log.info("[Step 5-3] 검수자별 할당 데이터를 batch update 시작"); assignMap.forEach( (reviewerId, assignedTasks) -> { - if (assignedTasks.isEmpty()) { - log.info("[Step 5-4] 할당된 데이터 없으면 return"); + if (assignedTasks == null || assignedTasks.isEmpty()) { + log.debug("[Step 5-5] reviewer={} 할당 없음", reviewerId); return; } List assignmentUids = assignedTasks.stream().map(Tasks::getAssignmentUid).toList(); - log.info("[Step 6-1] 할당 작업에 검수자 아이디 update"); - log.info("==== 검수자 사번: {}", reviewerId); - log.info("==== 할당 갯수: {}", assignmentUids == null ? 0 : assignmentUids.size()); + + log.info( + "[Step 6-1] reviewer assignment update reviewerId={}, count={}", + reviewerId, + assignmentUids.size()); + trainingDataLabelJobCoreService.assignReviewerBatch(assignmentUids, reviewerId); - log.info("[Step 7-1] geom 테이블에 검수 상태 update"); List geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList(); + + log.info("[Step 7-1] geom 상태 업데이트 geomCount={}", geomUids.size()); + trainingDataLabelJobCoreService.updateGeomUidTestState(geomUids); }); + + successRound++; + + log.info( + "[ROUND-END] analUid={} elapsed={}ms", + analUid, + System.currentTimeMillis() - roundStart); } + + log.info( + "[JOB-SUMMARY] rounds={}, successRounds={}, skipRounds={}, elapsed={}ms", + taskByRound.size(), + successRound, + skipRound, + System.currentTimeMillis() - jobStart); + } catch (Exception e) { - log.error("배치 처리 중 예외", e); + + log.error("[JOB-ERROR] 배치 처리 중 예외 발생", e); + throw e; } } private Map> distributeByLeastPending( List tasks, List reviewerIds) { + Map> result = new LinkedHashMap<>(); - // 순서 유지 중요 (ASC 정렬된 상태) for (String reviewerId : reviewerIds) { result.put(reviewerId, new ArrayList<>()); } @@ -130,7 +178,9 @@ public class TrainingDataLabelJobService { int reviewerCount = reviewerIds.size(); for (int i = 0; i < tasks.size(); i++) { + String reviewerId = reviewerIds.get(i % reviewerCount); + result.get(reviewerId).add(tasks.get(i)); }