From e697867bb0ba9be0eb009a4e5dbde0578a887118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dean=5B=EB=B0=B1=EB=B3=91=EB=82=A8=5D?= Date: Mon, 9 Feb 2026 20:34:33 +0900 Subject: [PATCH] Split the function --- .../libs/generator-dataset-for-training.jar | Bin 19804941 -> 19806638 bytes .../batch/ExportGeoJsonTasklet.java.backup | 152 ++++++++++++++++++ .../batch/LaunchChildJobsTasklet.java | 59 +++++-- .../config/AsyncJobLauncherConfig.java | 44 +++++ 4 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/ExportGeoJsonTasklet.java.backup create mode 100644 kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/config/AsyncJobLauncherConfig.java diff --git a/kamco-make-dataset-generation/build/libs/generator-dataset-for-training.jar b/kamco-make-dataset-generation/build/libs/generator-dataset-for-training.jar index 310e59127f7cdf9837808ba7192e7d6b4369cb3d..6320c43d792dfc9a79c6b5cc58f9cd34448bcb3e 100644 GIT binary patch delta 8980 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 Gqx(PJ-9f4V diff --git a/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/ExportGeoJsonTasklet.java.backup b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/ExportGeoJsonTasklet.java.backup new file mode 100644 index 0000000..2931dfe --- /dev/null +++ b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/ExportGeoJsonTasklet.java.backup @@ -0,0 +1,152 @@ +package com.kamco.cd.geojsonscheduler.batch; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.kamco.cd.geojsonscheduler.dto.TrainingDataReviewJobDto.AnalCntInfo; +import com.kamco.cd.geojsonscheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList; +import com.kamco.cd.geojsonscheduler.dto.TrainingDataReviewJobDto.CompleteLabelData; +import com.kamco.cd.geojsonscheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature; +import com.kamco.cd.geojsonscheduler.dto.TrainingDataReviewJobDto.FeatureCollection; +import com.kamco.cd.geojsonscheduler.repository.TrainingDataReviewJobRepository; +import com.kamco.cd.geojsonscheduler.service.DockerRunnerService; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class ExportGeoJsonTasklet implements Tasklet { + + private final TrainingDataReviewJobRepository repository; + private final DockerRunnerService dockerRunnerService; + + @Value("${training-data.geojson-dir}") + private String trainingDataDir; + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) { + log.info("========================================"); + log.info("배치 작업 시작"); + log.info("========================================"); + + // 1. StepContext를 통해 바로 가져오기 (가장 추천) + String jobName = chunkContext.getStepContext().getJobName(); + log.info("Job Name: {}", jobName); + + // 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기 + log.info("진행중인 회차 목록 조회 중..."); + List analList = repository.findAnalCntInfoList(); + log.info("진행중인 회차 수: {}", analList.size()); + + int processedAnalCount = 0; + for (AnalCntInfo info : analList) { + log.info("----------------------------------------"); + log.info("회차 처리 중: AnalUid={}, ResultUid={}", info.getAnalUid(), info.getResultUid()); + log.info("전체 건수: {}, 파일 건수: {}", info.getAllCnt(), info.getFileCnt()); + + if (Objects.equals(info.getAllCnt(), info.getFileCnt())) { + log.info("모든 파일이 이미 처리됨. 건너뜀."); + continue; + } + + //추론 ID + String resultUid = info.getResultUid(); + log.info("ResultUid: {}", resultUid); + + //insert 하기 jobname, resultUid , 시작시간 + // 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기 + log.info("검수 완료된 도엽 목록 조회 중... (AnalUid={})", info.getAnalUid()); + List analMapList = repository.findCompletedAnalMapSheetList(info.getAnalUid()); + log.info("검수 완료된 도엽 수: {}", analMapList.size()); + + //TODO 도엽이 4개이상 존재할때 만 RUN 하기 + if (analMapList.isEmpty()) { + log.warn("검수 완료된 도엽이 없음. 건너뜀."); + continue; + } + + //insert 하기 jobname, resultUid , 시작시간 + boolean anyProcessed = false; + int processedMapSheetCount = 0; + int totalGeoJsonFiles = 0; + + for (AnalMapSheetList mapSheet : analMapList) { + log.info(" 도엽 처리 중: MapSheetNum={}", mapSheet.getMapSheetNum()); + + //도엽별 geom 데이터 가지고 와서 geojson 만들기 + List completeList = + repository.findCompletedYesterdayLabelingList( + info.getAnalUid(), mapSheet.getMapSheetNum()); + log.info(" 완료된 라벨링 데이터 수: {}", completeList.size()); + + if (!completeList.isEmpty()) { + List geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList(); + log.info(" GeoUID 목록 생성 완료: {} 건", geoUids.size()); + + List features = completeList.stream().map(GeoJsonFeature::from).toList(); + log.info(" GeoJSON Feature 변환 완료: {} 개", features.size()); + + FeatureCollection collection = new FeatureCollection(features); + String filename = mapSheet.buildFilename(resultUid); + log.info(" GeoJSON 파일명: {}", filename); + + // 형식 /kamco-nfs/dataset/request/uuid/filename + Path outputPath = Paths.get(trainingDataDir + File.separator + "request" + File.separator + resultUid, filename); + log.info(" 출력 경로: {}", outputPath); + + try { + Files.createDirectories(outputPath.getParent()); + log.info(" 디렉토리 생성 완료: {}", outputPath.getParent()); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + objectMapper.writeValue(outputPath.toFile(), collection); + log.info(" GeoJSON 파일 저장 완료: {}", outputPath); + + repository.updateLearnDataGeomFileCreateYn(geoUids); + log.info(" DB 업데이트 완료: {} 건", geoUids.size()); + + anyProcessed = true; + processedMapSheetCount++; + totalGeoJsonFiles++; + } catch (IOException e) { + log.error(" GeoJSON 파일 생성 실패: {}", e.getMessage(), e); + } + } + } + + log.info("회차 처리 완료: ResultUid={}", resultUid); + log.info(" 처리된 도엽 수: {}", processedMapSheetCount); + log.info(" 생성된 GeoJSON 파일 수: {}", totalGeoJsonFiles); + + if (anyProcessed) { + log.info("Docker 컨테이너 실행 중... (ResultUid={})", resultUid); + dockerRunnerService.run(resultUid); + log.info("Docker 컨테이너 실행 완료 (ResultUid={})", resultUid); + processedAnalCount++; + } else { + log.warn("처리된 도엽이 없어 Docker 실행 건너뜀 (ResultUid={})", resultUid); + } + } + + log.info("========================================"); + log.info("배치 작업 완료"); + log.info("처리된 회차 수: {}", processedAnalCount); + log.info("========================================"); + + return RepeatStatus.FINISHED; + } +} diff --git a/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/LaunchChildJobsTasklet.java b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/LaunchChildJobsTasklet.java index d99e482..d29e2ef 100644 --- a/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/LaunchChildJobsTasklet.java +++ b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/batch/LaunchChildJobsTasklet.java @@ -6,7 +6,9 @@ import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.StepContribution; @@ -59,8 +61,9 @@ public class LaunchChildJobsTasklet implements Tasklet { /** 분석 회차 정보 조회를 위한 Repository */ private final TrainingDataReviewJobRepository repository; - /** Child Job을 실행하기 위한 JobLauncher */ - private final JobLauncher jobLauncher; + /** Child Job을 실행하기 위한 비동기 JobLauncher (트랜잭션 충돌 방지) */ + @Qualifier("asyncJobLauncher") + private final JobLauncher asyncJobLauncher; /** 실행할 Child Job (processAnalCntInfoJob) */ @Qualifier("processAnalCntInfoJob") @@ -141,18 +144,56 @@ public class LaunchChildJobsTasklet implements Tasklet { log.info(" - JobParameters: analUid={}, resultUid={}", info.getAnalUid(), info.getResultUid()); - // Child Job 실행 (동기 방식) + // Child Job 실행 (비동기 방식 - 트랜잭션 충돌 방지) + // asyncJobLauncher를 사용하여 별도 쓰레드에서 실행 // 내부적으로 makeGeoJsonStep → dockerRunStep → zipResponseStep 순차 실행 long startTime = System.currentTimeMillis(); - jobLauncher.run(processAnalCntInfoJob, jobParameters); + JobExecution jobExecution = asyncJobLauncher.run(processAnalCntInfoJob, jobParameters); + + // Child Job 완료 대기 (비동기 실행이므로 완료를 폴링) + log.info("[Child Job 대기] 실행 완료 대기 중... (JobExecutionId={})", + jobExecution.getId()); + while (jobExecution.isRunning()) { + try { + Thread.sleep(1000); // 1초마다 상태 확인 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Child Job 대기 중 인터럽트 발생", e); + } + } + long duration = System.currentTimeMillis() - startTime; + BatchStatus status = jobExecution.getStatus(); - log.info("[Child Job 완료] ✓ 정상 종료"); - log.info(" - AnalUid: {}", info.getAnalUid()); - log.info(" - ResultUid: {}", info.getResultUid()); - log.info(" - 실행 시간: {} ms ({} 초)", duration, duration / 1000); + if (status == BatchStatus.COMPLETED) { + log.info("[Child Job 완료] ✓ 정상 종료"); + log.info(" - AnalUid: {}", info.getAnalUid()); + log.info(" - ResultUid: {}", info.getResultUid()); + log.info(" - 실행 시간: {} ms ({} 초)", duration, duration / 1000); + log.info(" - 최종 상태: {}", status); + processedCount++; + } else { + // Child Job 실패 + log.error("[Child Job 실패] ✗ 비정상 종료"); + log.error(" - AnalUid: {}", info.getAnalUid()); + log.error(" - ResultUid: {}", info.getResultUid()); + log.error(" - 실행 시간: {} ms ({} 초)", duration, duration / 1000); + log.error(" - 최종 상태: {}", status); + log.error(" - Exit 상태: {}", jobExecution.getExitStatus()); - processedCount++; + // 실패 예외 정보 로깅 + if (!jobExecution.getAllFailureExceptions().isEmpty()) { + log.error(" - 실패 예외:"); + for (Throwable t : jobExecution.getAllFailureExceptions()) { + log.error(" * {}: {}", t.getClass().getSimpleName(), t.getMessage()); + } + } + + failedCount++; + // 실패해도 다음 회차 계속 처리 + log.info("[계속 진행] 다음 회차 처리를 계속합니다."); + continue; // 다음 for 루프로 + } } catch (Exception e) { // Child Job 실행 실패 시 (Step 실패 또는 예외 발생) diff --git a/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/config/AsyncJobLauncherConfig.java b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/config/AsyncJobLauncherConfig.java new file mode 100644 index 0000000..8a46e45 --- /dev/null +++ b/kamco-make-dataset-generation/src/main/java/com/kamco/cd/geojsonscheduler/config/AsyncJobLauncherConfig.java @@ -0,0 +1,44 @@ +package com.kamco.cd.geojsonscheduler.config; + +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; + +/** + * 비동기 JobLauncher 설정 + * + *

Parent Job 내에서 Child Job을 실행할 때 트랜잭션 충돌을 방지하기 위해 비동기 JobLauncher를 생성합니다. + * + *

문제: Parent Job의 Step이 트랜잭션 내에서 실행되는데, 그 안에서 동기 JobLauncher로 Child Job을 + * 실행하면 "Existing transaction detected in JobRepository" 에러 발생 + * + *

해결: 비동기 TaskExecutor를 사용하는 별도의 JobLauncher를 생성하여 트랜잭션 분리 + * + * @author KAMCO Development Team + * @since 1.0.0 + */ +@Configuration +public class AsyncJobLauncherConfig { + + /** + * 비동기 JobLauncher 생성 + * + *

SimpleAsyncTaskExecutor를 사용하여 Child Job을 별도 쓰레드에서 실행합니다. 이렇게 하면 Parent Job의 + * 트랜잭션과 분리되어 트랜잭션 충돌이 발생하지 않습니다. + * + * @param jobRepository JobRepository + * @return 비동기 JobLauncher + * @throws Exception JobLauncher 초기화 실패 시 + */ + @Bean(name = "asyncJobLauncher") + public JobLauncher asyncJobLauncher(JobRepository jobRepository) throws Exception { + TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher(); + jobLauncher.setJobRepository(jobRepository); + jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor()); // 비동기 실행 + jobLauncher.afterPropertiesSet(); + return jobLauncher; + } +}