Compare commits

..

2 커밋

10개의 변경된 파일170개의 추가작업 그리고 32개의 파일을 삭제

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@ -50,9 +52,15 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "COMPANY_COMPLIANCE_IMPORT_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
@Override
protected int getChunkSize() {
@ -90,12 +98,26 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Override
protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder
.start(companyComplianceImportRangeStep()) // 1단계 실행
// .next(companyComplianceHistoryValueChangeManageStep()) // 2단계 실행 (2단계 실패 실행 )
.next(companyComplianceLastExecutionUpdateStep()) // 3단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트
.start(companyComplianceImportRangeStep())
.next(companyComplianceEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(companyComplianceEmptyResponseDecider()).on("*").to(companyComplianceLastExecutionUpdateStep())
.end()
.build();
}
@Bean
public JobExecutionDecider companyComplianceEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[CompanyComplianceImportRangeJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[CompanyComplianceImportRangeJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
}
@Override
protected ItemReader<CompanyComplianceDto> createReader() {
return companyComplianceDataRangeReader;
@ -193,11 +215,11 @@ public class CompanyComplianceImportRangeJobConfig extends BaseMultiStepJobConfi
@Bean
public Tasklet companyComplianceLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@ -49,9 +51,15 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "COMPLIANCE_IMPORT_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
@Override
protected int getChunkSize() {
@ -90,12 +98,26 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Override
protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder
.start(complianceImportRangeStep()) // 1단계 실행
// .next(complianceHistoryValueChangeManageStep()) // 2단계 실행 (2단계 실패 실행 )
.next(complianceLastExecutionUpdateStep()) // 3단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트
.start(complianceImportRangeStep())
.next(complianceEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(complianceEmptyResponseDecider()).on("*").to(complianceLastExecutionUpdateStep())
.end()
.build();
}
@Bean
public JobExecutionDecider complianceEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[ComplianceImportRangeJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[ComplianceImportRangeJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
}
@Override
protected ItemReader<ComplianceDto> createReader() {
return complianceDataRangeReader;
@ -195,11 +217,11 @@ public class ComplianceImportRangeJobConfig extends BaseMultiStepJobConfig<Compl
@Bean
public Tasklet complianceLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@ -45,9 +47,15 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "EVENT_IMPORT_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
@Override
protected int getChunkSize() {
@ -87,10 +95,25 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder
.start(eventImportStep())
.next(eventLastExecutionUpdateStep())
.next(eventEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(eventEmptyResponseDecider()).on("*").to(eventLastExecutionUpdateStep())
.end()
.build();
}
@Bean
public JobExecutionDecider eventEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[EventImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[EventImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
}
@Bean
@StepScope
public EventDataReader eventDataReader(
@ -139,11 +162,11 @@ public class EventImportJobConfig extends BaseMultiStepJobConfig<EventDetailDto,
@Bean
public Tasklet eventLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -15,6 +15,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@ -47,9 +49,15 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "PSC_IMPORT_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
public PscInspectionJobConfig(
JobRepository jobRepository,
@ -87,10 +95,25 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder
.start(PSCDetailImportStep())
.next(pscLastExecutionUpdateStep())
.next(pscEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(pscEmptyResponseDecider()).on("*").to(pscLastExecutionUpdateStep())
.end()
.build();
}
@Bean
public JobExecutionDecider pscEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[PSCDetailImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[PSCDetailImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
}
@Bean
@StepScope
public PscApiReader pscApiReader(
@ -145,11 +168,11 @@ public class PscInspectionJobConfig extends BaseMultiStepJobConfig<PscInspection
@Bean
public Tasklet pscLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -13,6 +13,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.Tasklet;
@ -45,9 +47,15 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "RISK_IMPORT_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
@Override
@ -87,11 +95,26 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Override
protected Job createJobFlow(JobBuilder jobBuilder) {
return jobBuilder
.start(riskRangeImportStep()) // 1단계: API 데이터 적재
.next(riskLastExecutionUpdateStep()) // 2단계: 모두 완료 , BATCH_LAST_EXECUTION 마지막 성공일자 업데이트
.start(riskRangeImportStep())
.next(riskEmptyResponseDecider())
.on("EMPTY_RESPONSE").end()
.from(riskEmptyResponseDecider()).on("*").to(riskLastExecutionUpdateStep())
.end()
.build();
}
@Bean
public JobExecutionDecider riskEmptyResponseDecider() {
return (jobExecution, stepExecution) -> {
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[RiskRangeImportJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[RiskRangeImportJob] Decider: NORMAL - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
}
@Override
protected ItemReader<RiskDto> createReader() {
return riskDataRangeReader;
@ -137,11 +160,11 @@ public class RiskImportRangeJobConfig extends BaseMultiStepJobConfig<RiskDto, Ri
@Bean
public Tasklet riskLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -28,6 +28,9 @@ public class ShipDetailSyncJobConfig {
@Value("${app.batch.target-schema.name}")
private String targetSchema;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
// API 정의 (배치 로그 관리용)
protected String getApiKey() {
return "SHIP_DETAIL_SYNC_API";
@ -36,8 +39,8 @@ public class ShipDetailSyncJobConfig {
// 마지막 실행 일자 업데이트 SQL
protected String getBatchUpdateSql() {
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, getApiKey()
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey()
);
}
@ -139,11 +142,11 @@ public class ShipDetailSyncJobConfig {
@Bean
public Tasklet shipDetailSyncLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 테이블 동기화 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -69,9 +69,15 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
@Value("${app.batch.ship-detail-update.max-retry-count:3}")
private int maxRetryCount;
@Value("${app.batch.last-execution-buffer-hours:24}")
private int lastExecutionBufferHours;
protected String getApiKey() {return "SHIP_DETAIL_UPDATE_API";}
protected String getBatchUpdateSql() {
return String.format("UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW(), UPDATED_AT = NOW() WHERE API_KEY = '%s'", targetSchema, getApiKey());}
return String.format(
"UPDATE %s.BATCH_LAST_EXECUTION SET LAST_SUCCESS_DATE = NOW() - INTERVAL '%d HOURS', UPDATED_AT = NOW() WHERE API_KEY = '%s'",
targetSchema, lastExecutionBufferHours, getApiKey());
}
public ShipDetailUpdateJobConfig(
@ -114,6 +120,7 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
.start(ShipDetailUpdateStep())
.next(retryModeDecider())
.on("RETRY").end()
.from(retryModeDecider()).on("EMPTY_RESPONSE").end()
.from(retryModeDecider()).on("NORMAL").to(shipDetailLastExecutionUpdateStep())
.end()
.build();
@ -131,6 +138,12 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
log.info("[ShipDetailUpdateJob] Decider: RETRY 모드 - LAST_EXECUTION 업데이트 스킵");
return new FlowExecutionStatus("RETRY");
}
if (stepExecution != null && stepExecution.getReadCount() == 0) {
log.info("[ShipDetailUpdateJob] Decider: EMPTY_RESPONSE - 응답 데이터 0건으로 LAST_EXECUTION 업데이트 스킵 (다음 실행 시 동일 범위 재조회)");
return new FlowExecutionStatus("EMPTY_RESPONSE");
}
log.info("[ShipDetailUpdateJob] Decider: NORMAL 모드 - LAST_EXECUTION 업데이트 진행");
return new FlowExecutionStatus("NORMAL");
};
@ -213,11 +226,11 @@ public class ShipDetailUpdateJobConfig extends BaseMultiStepJobConfig<ShipDetail
@Bean
public Tasklet shipDetailLastExecutionUpdateTasklet() {
return (contribution, chunkContext) -> {
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작");
log.info(">>>>> 모든 스텝 성공: BATCH_LAST_EXECUTION 업데이트 시작 (버퍼: {}시간)", lastExecutionBufferHours);
jdbcTemplate.execute(getBatchUpdateSql());
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료");
log.info(">>>>> BATCH_LAST_EXECUTION 업데이트 완료 (LAST_SUCCESS_DATE = NOW() - {}시간)", lastExecutionBufferHours);
return RepeatStatus.FINISHED;
};
}

파일 보기

@ -110,6 +110,9 @@ app:
enabled: true
cron: "0 0 * * * ?" # Every hour
# LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응
last-execution-buffer-hours: 24
# ShipDetailUpdate 배치 설정 (dev - 기존과 동일하게 20건 유지)
ship-detail-update:
batch-size: 20 # dev에서는 문제 없으므로 기존 20건 유지

파일 보기

@ -112,6 +112,9 @@ app:
enabled: true
cron: "0 0 * * * ?" # Every hour
# LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응
last-execution-buffer-hours: 24
# ShipDetailUpdate 배치 설정 (prod 튜닝)
ship-detail-update:
batch-size: 10 # API 요청 당 IMO 건수 (프록시 타임아웃 방지)

파일 보기

@ -165,6 +165,9 @@ app:
enabled: true
cron: "0 0 * * * ?" # Every hour
# LAST_EXECUTION 버퍼 시간 (시간 단위) - 외부 DB 동기화 지연 대응
last-execution-buffer-hours: 24
# ShipDetailUpdate 배치 설정
ship-detail-update:
batch-size: 10 # API 요청 당 IMO 건수