From 9b4dba156886cf282c9e45417d926a5db547f6d5 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Wed, 25 Mar 2026 16:10:44 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(maintenance):=20=EB=B0=B0=EC=B9=98=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EA=B4=80=EB=A6=AC=20=EC=A0=95=EB=A6=AC=20?= =?UTF-8?q?Job=20=EA=B5=AC=ED=98=84=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BatchLogCleanupJobConfig: Tasklet 기반 로그 정리 Job - BatchLogCleanupRepository: FK 순서 고려 6단계 삭제 (step_context → step_execution → job_context → job_params → job_execution → job_instance) - application.yml: log-retention-days: 90 설정 - UI 스케줄에서 batchLogCleanupJob 등록하여 사용 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../maintenance/BatchLogCleanupJobConfig.java | 63 +++++++++++++ .../BatchLogCleanupRepository.java | 93 +++++++++++++++++++ src/main/resources/application.yml | 1 + 3 files changed, 157 insertions(+) create mode 100644 src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupJobConfig.java create mode 100644 src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupRepository.java diff --git a/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupJobConfig.java b/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupJobConfig.java new file mode 100644 index 0000000..1c80eeb --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupJobConfig.java @@ -0,0 +1,63 @@ +package com.snp.batch.jobs.maintenance; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.PlatformTransactionManager; + +import java.time.LocalDateTime; + +/** + * 배치 로그 정리 Job + * 보관 기간이 지난 Spring Batch 메타데이터를 삭제 + * + * 실행 주기: 매주 일요일 00:00 (Quartz 스케줄) + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +public class BatchLogCleanupJobConfig { + + private final JobRepository jobRepository; + private final PlatformTransactionManager transactionManager; + private final BatchLogCleanupRepository cleanupRepository; + + @Value("${app.batch.log-retention-days:90}") + private int retentionDays; + + @Bean + public Job batchLogCleanupJob() { + return new JobBuilder("batchLogCleanupJob", jobRepository) + .start(batchLogCleanupStep()) + .build(); + } + + @Bean + public Step batchLogCleanupStep() { + return new StepBuilder("batchLogCleanupStep", jobRepository) + .tasklet(batchLogCleanupTasklet(), transactionManager) + .build(); + } + + @Bean + public Tasklet batchLogCleanupTasklet() { + return (contribution, chunkContext) -> { + LocalDateTime before = LocalDateTime.now().minusDays(retentionDays); + log.info("[LogCleanup] 배치 로그 정리 시작 (보관 기간: {}일, 기준일시: {})", retentionDays, before); + + int totalDeleted = cleanupRepository.deleteOldLogs(before); + + log.info("[LogCleanup] 배치 로그 정리 완료 (총 {}건 삭제)", totalDeleted); + return RepeatStatus.FINISHED; + }; + } +} diff --git a/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupRepository.java b/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupRepository.java new file mode 100644 index 0000000..24ce333 --- /dev/null +++ b/src/main/java/com/snp/batch/jobs/maintenance/BatchLogCleanupRepository.java @@ -0,0 +1,93 @@ +package com.snp.batch.jobs.maintenance; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; + +/** + * 배치 로그 삭제 Repository + * FK 제약 조건 순서를 고려하여 삭제 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class BatchLogCleanupRepository { + + private final JdbcTemplate jdbcTemplate; + + /** + * 보관 기간이 지난 배치 로그를 삭제 + * 삭제 순서: step_context → step_execution → job_context → job_params → job_execution → job_instance(고아) + * + * @param before 이 시점 이전의 로그를 삭제 + * @return 삭제된 총 행 수 + */ + public int deleteOldLogs(LocalDateTime before) { + int total = 0; + + // 1. batch_step_execution_context (step_execution FK) + int stepCtx = jdbcTemplate.update(""" + DELETE FROM batch_step_execution_context + WHERE STEP_EXECUTION_ID IN ( + SELECT se.STEP_EXECUTION_ID + FROM batch_step_execution se + INNER JOIN batch_job_execution je ON se.JOB_EXECUTION_ID = je.JOB_EXECUTION_ID + WHERE je.START_TIME < ? + ) + """, before); + log.info("[LogCleanup] batch_step_execution_context: {}건 삭제", stepCtx); + total += stepCtx; + + // 2. batch_step_execution + int stepExec = jdbcTemplate.update(""" + DELETE FROM batch_step_execution + WHERE JOB_EXECUTION_ID IN ( + SELECT JOB_EXECUTION_ID FROM batch_job_execution WHERE START_TIME < ? + ) + """, before); + log.info("[LogCleanup] batch_step_execution: {}건 삭제", stepExec); + total += stepExec; + + // 3. batch_job_execution_context + int jobCtx = jdbcTemplate.update(""" + DELETE FROM batch_job_execution_context + WHERE JOB_EXECUTION_ID IN ( + SELECT JOB_EXECUTION_ID FROM batch_job_execution WHERE START_TIME < ? + ) + """, before); + log.info("[LogCleanup] batch_job_execution_context: {}건 삭제", jobCtx); + total += jobCtx; + + // 4. batch_job_execution_params + int jobParams = jdbcTemplate.update(""" + DELETE FROM batch_job_execution_params + WHERE JOB_EXECUTION_ID IN ( + SELECT JOB_EXECUTION_ID FROM batch_job_execution WHERE START_TIME < ? + ) + """, before); + log.info("[LogCleanup] batch_job_execution_params: {}건 삭제", jobParams); + total += jobParams; + + // 5. batch_job_execution + int jobExec = jdbcTemplate.update(""" + DELETE FROM batch_job_execution WHERE START_TIME < ? + """, before); + log.info("[LogCleanup] batch_job_execution: {}건 삭제", jobExec); + total += jobExec; + + // 6. batch_job_instance (고아 인스턴스) + int jobInst = jdbcTemplate.update(""" + DELETE FROM batch_job_instance + WHERE JOB_INSTANCE_ID NOT IN ( + SELECT JOB_INSTANCE_ID FROM batch_job_execution + ) + """); + log.info("[LogCleanup] batch_job_instance (고아): {}건 삭제", jobInst); + total += jobInst; + + return total; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 734cc4c..3e19c09 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -104,6 +104,7 @@ app: batch: chunk-size: 10000 sub-chunk-size: 5000 # Writer Sub-Chunk 분할 크기 + log-retention-days: 90 # 배치 로그 보관 기간 (일) source-schema: name: std_snp_data tables: -- 2.45.2 From 2713f8e1713f7db4e2649d06fccef4721ea26b75 Mon Sep 17 00:00:00 2001 From: HYOJIN Date: Wed, 25 Mar 2026 16:11:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE-NOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 4046544..e8e96da 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,9 @@ ## [Unreleased] +### 추가 +- 배치 로그 관리 정리 Job 구현: 90일 보관 기간 기준 자동 삭제 (#16) + ## [2026-03-25] ### 추가 -- 2.45.2