Merge pull request 'feat(log-cleanup): 배치 로그 관리 정책 수립 및 정리 배치 작업 개발' (#101) from feature/ISSUE-100-batch-log-cleanup into develop

This commit is contained in:
HYOJIN 2026-03-25 16:10:30 +09:00
커밋 a9620e6883
5개의 변경된 파일262개의 추가작업 그리고 0개의 파일을 삭제

파일 보기

@ -5,6 +5,7 @@
## [Unreleased]
### 추가
- 배치 로그 관리 정책 수립 및 정리 배치 작업 개발 (LogCleanupJob) (#100)
- IMO Meta Table 관리 배치 작업 개발 (All IMO Import + Delete Flag Update) (#80)
- Risk 상세 데이터 수집 배치 프로세스 추가 (RisksByImos API, 파티션 병렬 처리) (#65)
- 배치 모니터링 React SPA 전환 및 10대 기능 강화

파일 보기

@ -0,0 +1,37 @@
package com.snp.batch.global.cleanup;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 배치 로그 정리 설정
*
* 로그 종류별 보존 기간() 설정
*
* 설정 예시:
* app.batch.log-cleanup:
* api-log-retention-days: 30
* batch-meta-retention-days: 90
* failed-record-retention-days: 90
* recollection-history-retention-days: 90
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "app.batch.log-cleanup")
public class LogCleanupConfig {
/** batch_api_log 보존 기간 (일) */
private int apiLogRetentionDays = 30;
/** Spring Batch 메타 테이블 보존 기간 (일) */
private int batchMetaRetentionDays = 90;
/** batch_failed_record (RESOLVED) 보존 기간 (일) */
private int failedRecordRetentionDays = 90;
/** batch_recollection_history 보존 기간 (일) */
private int recollectionHistoryRetentionDays = 90;
}

파일 보기

@ -0,0 +1,69 @@
package com.snp.batch.global.cleanup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
/**
* 배치 로그 정리 Job Config
*
* 스케줄: 매일 02:00 (0 0 2 * * ?)
*
* 동작:
* - 보존 기간이 지난 배치 로그 데이터를 삭제
* - batch_api_log (30일), Spring Batch 메타 (90일),
* batch_failed_record/RESOLVED (90일), batch_recollection_history (90일)
*/
@Slf4j
@Configuration
public class LogCleanupJobConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
private final LogCleanupTasklet logCleanupTasklet;
public LogCleanupJobConfig(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
LogCleanupTasklet logCleanupTasklet) {
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
this.logCleanupTasklet = logCleanupTasklet;
}
@Bean(name = "logCleanupStep")
public Step logCleanupStep() {
return new StepBuilder("logCleanupStep", jobRepository)
.tasklet(logCleanupTasklet, transactionManager)
.build();
}
@Bean(name = "LogCleanupJob")
public Job logCleanupJob() {
log.info("Job 생성: LogCleanupJob");
return new JobBuilder("LogCleanupJob", jobRepository)
.listener(new JobExecutionListener() {
@Override
public void beforeJob(JobExecution jobExecution) {
log.info("[LogCleanupJob] 배치 로그 정리 Job 시작");
}
@Override
public void afterJob(JobExecution jobExecution) {
log.info("[LogCleanupJob] 배치 로그 정리 Job 완료 - 상태: {}",
jobExecution.getStatus());
}
})
.start(logCleanupStep())
.build();
}
}

파일 보기

@ -0,0 +1,148 @@
package com.snp.batch.global.cleanup;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class LogCleanupTasklet implements Tasklet {
private final JdbcTemplate jdbcTemplate;
private final LogCleanupConfig config;
@Value("${app.batch.target-schema.name}")
private String schema;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
log.info("========================================");
log.info("배치 로그 정리 Job 시작");
log.info("========================================");
int totalDeleted = 0;
// 1. batch_api_log 정리
totalDeleted += cleanupApiLog();
// 2. Spring Batch 메타 테이블 정리 (FK 순서)
totalDeleted += cleanupBatchMeta();
// 3. batch_failed_record 정리 (RESOLVED만)
totalDeleted += cleanupFailedRecord();
// 4. batch_recollection_history 정리
totalDeleted += cleanupRecollectionHistory();
log.info("========================================");
log.info("배치 로그 정리 Job 완료 - 총 삭제: {} 건", totalDeleted);
log.info("========================================");
return RepeatStatus.FINISHED;
}
private int cleanupApiLog() {
int days = config.getApiLogRetentionDays();
String sql = String.format(
"DELETE FROM %s.batch_api_log WHERE created_at < NOW() - INTERVAL '%d days'",
schema, days);
int deleted = jdbcTemplate.update(sql);
log.info("[batch_api_log] 보존기간: {}일, 삭제: {}건", days, deleted);
return deleted;
}
private int cleanupBatchMeta() {
int days = config.getBatchMetaRetentionDays();
int totalDeleted = 0;
// FK 의존 순서: step_execution_context step_execution job_execution_context job_execution_params job_execution job_instance(orphan)
// 1. batch_step_execution_context
String sql1 = String.format(
"DELETE FROM %s.batch_step_execution_context WHERE step_execution_id IN (" +
"SELECT se.step_execution_id FROM %s.batch_step_execution se " +
"JOIN %s.batch_job_execution je ON se.job_execution_id = je.job_execution_id " +
"WHERE je.create_time < NOW() - INTERVAL '%d days')",
schema, schema, schema, days);
int deleted = jdbcTemplate.update(sql1);
totalDeleted += deleted;
log.info("[batch_step_execution_context] 삭제: {}건", deleted);
// 2. batch_step_execution
String sql2 = String.format(
"DELETE FROM %s.batch_step_execution WHERE job_execution_id IN (" +
"SELECT job_execution_id FROM %s.batch_job_execution " +
"WHERE create_time < NOW() - INTERVAL '%d days')",
schema, schema, days);
deleted = jdbcTemplate.update(sql2);
totalDeleted += deleted;
log.info("[batch_step_execution] 삭제: {}건", deleted);
// 3. batch_job_execution_context
String sql3 = String.format(
"DELETE FROM %s.batch_job_execution_context WHERE job_execution_id IN (" +
"SELECT job_execution_id FROM %s.batch_job_execution " +
"WHERE create_time < NOW() - INTERVAL '%d days')",
schema, schema, days);
deleted = jdbcTemplate.update(sql3);
totalDeleted += deleted;
log.info("[batch_job_execution_context] 삭제: {}건", deleted);
// 4. batch_job_execution_params
String sql4 = String.format(
"DELETE FROM %s.batch_job_execution_params WHERE job_execution_id IN (" +
"SELECT job_execution_id FROM %s.batch_job_execution " +
"WHERE create_time < NOW() - INTERVAL '%d days')",
schema, schema, days);
deleted = jdbcTemplate.update(sql4);
totalDeleted += deleted;
log.info("[batch_job_execution_params] 삭제: {}건", deleted);
// 5. batch_job_execution
String sql5 = String.format(
"DELETE FROM %s.batch_job_execution WHERE create_time < NOW() - INTERVAL '%d days'",
schema, days);
deleted = jdbcTemplate.update(sql5);
totalDeleted += deleted;
log.info("[batch_job_execution] 삭제: {}건", deleted);
// 6. batch_job_instance (참조 없는 인스턴스만)
String sql6 = String.format(
"DELETE FROM %s.batch_job_instance WHERE job_instance_id NOT IN (" +
"SELECT DISTINCT job_instance_id FROM %s.batch_job_execution)",
schema, schema);
deleted = jdbcTemplate.update(sql6);
totalDeleted += deleted;
log.info("[batch_job_instance] orphan 삭제: {}건", deleted);
log.info("[Spring Batch 메타] 보존기간: {}일, 총 삭제: {}건", days, totalDeleted);
return totalDeleted;
}
private int cleanupFailedRecord() {
int days = config.getFailedRecordRetentionDays();
String sql = String.format(
"DELETE FROM %s.batch_failed_record WHERE status = 'RESOLVED' AND resolved_at < NOW() - INTERVAL '%d days'",
schema, days);
int deleted = jdbcTemplate.update(sql);
log.info("[batch_failed_record] 보존기간: {}일 (RESOLVED만), 삭제: {}건", days, deleted);
return deleted;
}
private int cleanupRecollectionHistory() {
int days = config.getRecollectionHistoryRetentionDays();
String sql = String.format(
"DELETE FROM %s.batch_recollection_history WHERE created_at < NOW() - INTERVAL '%d days'",
schema, days);
int deleted = jdbcTemplate.update(sql);
log.info("[batch_recollection_history] 보존기간: {}일, 삭제: {}건", days, deleted);
return deleted;
}
}

파일 보기

@ -223,6 +223,13 @@ app:
imo-column: imo_no # IMO/LRNO 컬럼명 (PK, NOT NULL)
mmsi-column: mmsi_no # MMSI 컬럼명 (NULLABLE)
# 배치 로그 정리 설정
log-cleanup:
api-log-retention-days: 30 # batch_api_log 보존 기간
batch-meta-retention-days: 90 # Spring Batch 메타 테이블 보존 기간
failed-record-retention-days: 90 # batch_failed_record (RESOLVED) 보존 기간
recollection-history-retention-days: 90 # batch_recollection_history 보존 기간
# 파티션 관리 설정
partition:
# 일별 파티션 테이블 목록 (네이밍: {table}_YYMMDD)