반응형
본 내용은 온라인 강의 사이트 인프런의 정수원 님의 강의 내용이 포함되어 있습니다.
스프링 배치 - Spring Boot 기반으로 개발하는 Spring Batch
Intro
스프링 배치의 도메인은 크게 두 가지로 나눌 수 있다.
- 배치를 특정 단계 또는 흐름에 따라 처리하고 구성하는 역할을 하는 도메인
- Job, Step, Flow, Tasklet, …
- 배치의 단계마다 실행 정보나 상태 정보를 데이터베이스에 저장하기 위한 메타데이터 도메인
- JobInstance, JobExecution, StepExecution, …
StepExecution
StepExecution은 Step에 대한 한 번의 시도를 의미하는 객체로 Step 실행 중에 발생한 정보들을 저장하고 있다. StepExecution에는 Step의 시작시간, 종료시간, 상태(시작, 완료, 실패 등), commit count, rollback count 등의 속성을 가지고 있다.
- StepExecution은 Step이 매번 시도될 때마다 생성되며 Job에 구성된 Step 별로 생성된다.
- Job이 재시작하더라도 이미 성공적으로 완료된 Step은 재실행되지 않고 실패한 Step만 실행된다.
- 이전 단계 Step이 실패하여 현재 Step이 실행되지 않은 경우 StepExecution을 생성하지 않는다. Step이 실제로 시작되었을 때만 StepExecution을 생성한다.
- 예를 들어, step1과 step2가 있을 때, step1의 실행 도중 실패했다면, step2의 StepExecution은 생성되지 않는다.
JobExecution과의 관계
- Step의 StepExecution이 모두 정상적으로 완료되어야 JobExecution이 정상적으로 완료된다.
- Step의 StepExecution 중 하나라도 실패하면 JobExecution은 실패한다.
batch_step_execution 테이블과의 매핑
- JobExecution과 StepExecution은 1:M 관계
- 하나의 Job에 여러 개의 Step으로 구성했을 경우 각 StepExecution은 하나의 JobExecution을 부모로 가진다.
StepExecution의 실행 흐름
2개의 Step을 가지는 Batch Job이 있다고 가정한다.
- Job이 실행하면 JobExecution이 생성되고 각 Step을 순차적으로 실행한다.
- 2021년 1월 1일에 실행된 Job은 2개의 Step을 모두 COMPLETED하여 Job은 최종적으로 실행에 성공한다.
- 해당 Job은 성공적으로 끝났기 때문에 JobInstance를 재실행할 수 없으며, JobExecution을 재생성할 수도 없다.
- Job이 실행하면 JobExecution이 생성되고 각 Step을 순차적으로 실행한다.
- 2021년 1월 2일에 실행된 Job은 Step2에서 FAILED하여 Job은 최종적으로 실행에 실패한다.
- 해당 Job은 실패했기 때문에 JobInstance를 재실행할 수 있으며, 재실행할 경우 새로운 JobExecution 생성된다.
- Step1은 성공이므로 Job을 재실행하더라도 실행하지 않으며, Step2가 재실행되면 새로운 StepExecution이 생성된다.
StepExecution 구조
- jobExecution : JobExecution 객체 저장
- stepName : Step의 이름
- BatchStatus : 실행 상태를 나타내는 Enum 클래스(COMPLETED, STARTING, STARTED, STOPPING, STOPPED, FAILED, ABANDONED, UNKNOWN)
- readCount : 성공적으로 read한 아이템 수
- writeCount : 성공적으로 write한 아이템 수
- commitCount : 실행 중에 커밋된 트랜잭션 수
- rollbackCount : 실행 중에 롤백된 횟수
- readSkipCount : read에 실패하여 스킵된 횟수
- writeSkipCount : write에 실패하여 스킵된 횟수
- filterCount : ItemProcessor에 의해 필터링된 아이템 수
- startTime : Job을 실행할 때의 시스템 시간
- endTime : 성공 여부와 상관없이 실행이 종료된 시간
- lastUpdated : JobExecution이 마지막 저장될 때의 시스템 시간
- ExecutionContext : 실행하는 동안 유지해야 하는 데이터를 담고 있음
- ExitStatus : 실행 결과를 나타내는 클래스로 종료코드를 포함(UNKNOWN, EXECUTING, COMPLETED, NOOP, FAILED, STOPPED)
- failureExecptions : Job 실행 중에 발생한 예외 리스트
Job과 Step의 전체적인 실행 흐름
- Job이 실행되면 JobInstance가 생성된 후 JobExecution이 생성된다.
- Step이 실행되면 StepExecution이 생성된다.
- 각 Step을 실행하고 모든 StepExecution이 COMPLETED이면 JobExecution도 COMPLETED로 해당 Batch Job의 실행을 성공적으로 마친다.
- Step을 실행하는 도중에 실패하면 해당 Step의 StepExecution의 상태는 FAILED이며, JobExecution도 FAILED가 된다.
- JobInstance의 마지막 JobExecution이 FAILED면 재실행 가능하며, 재실행할 경우 JobExecution을 생성한다.
- 다시 각 Step을 실행하고 StepExecution을 생성한다. 모든 StepExecution이 COMPLETED이면 JobExecution도 COMPLETED가 된다.
StepExecution 실행 및 분석
성공 케이스
StepExecutionConfiguration
먼저 간단한 Batch Job을 작성한다.
@Configuration
@RequiredArgsConstructor
public class StepExecutionConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(step1())
.next(step2())
.next(step3())
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
System.out.println("step1 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
System.out.println("step2 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
System.out.println("step3 has executed");
return RepeatStatus.FINISHED;
})
.build();
}
}
실행 결과
2023-07-04 00:57:27.154 INFO 30264 --- [ main] c.e.springbatch.SpringBatchApplication : Started SpringBatchApplication in 2.622 seconds (JVM running for 3.701)
2023-07-04 00:57:27.155 INFO 30264 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [name=user1]
2023-07-04 00:57:27.322 INFO 30264 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{name=user1}]
2023-07-04 00:57:27.390 INFO 30264 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
step1 has executed
2023-07-04 00:57:27.433 INFO 30264 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 42ms
2023-07-04 00:57:27.475 INFO 30264 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step2 has executed
2023-07-04 00:57:27.507 INFO 30264 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 32ms
2023-07-04 00:57:27.554 INFO 30264 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
step3 has executed
2023-07-04 00:57:27.597 INFO 30264 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 43ms
2023-07-04 00:57:27.623 INFO 30264 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{name=user1}] and the following status: [COMPLETED] in 269ms
BATCH_SETP_EXECUTION
- JobExecutionId를 1로 가진 Step 3개가 실행되며, 모든 Step이 COMPLETED로 성공적으로 끝마친다.
BATCH_JOB_EXECUTION
- 모든 Step이 COMPLETED이기 때문에 JobExectuion도 COMPLETED이다.
실패 케이스
StepExecutionConfiguration
실패 케이스를 분석하기 위해 Step2에서 예외가 발생하도록 코드를 수정한다.
@Configuration
@RequiredArgsConstructor
public class StepExecutionConfiguration {
...
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
throw new RuntimeException("Step2 has failed"); // 예외 발생
// System.out.println("step2 has executed"); // 주석 처리
// return RepeatStatus.FINISHED; // 주석 처리
})
.build();
}
...
}
실행
이전에 실행된 Job과 Step 테이블을 초기화한 뒤 재실행한다.
2023-07-04 01:06:14.197 INFO 19532 --- [ main] c.e.springbatch.SpringBatchApplication : Started SpringBatchApplication in 2.532 seconds (JVM running for 3.624)
2023-07-04 01:06:14.199 INFO 19532 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [name=user1]
2023-07-04 01:06:14.370 INFO 19532 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{name=user1}]
2023-07-04 01:06:14.433 INFO 19532 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
step1 has executed
2023-07-04 01:06:14.475 INFO 19532 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 41ms
2023-07-04 01:06:14.517 INFO 19532 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
2023-07-04 01:06:14.543 ERROR 19532 --- [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step step2 in job job
java.lang.RuntimeException: Step2 has failed
at com.example.springbatch.StepExecutionConfiguration.lambda$step2$1(StepExecutionConfiguration.java:42) ~[classes/:na]
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:407) ~[spring-batch-core-4.3.8.jar:4.3.8]
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:331) ~[spring-batch-core-4.3.8.jar:4.3.8]
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) ~[spring-tx-5.3.27.jar:5.3.27]
...
2023-07-04 01:06:14.549 INFO 19532 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 32ms
2023-07-04 01:06:14.578 INFO 19532 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{name=user1}] and the following status: [FAILED] in 180ms
- Step2에서 예외가 발생하며, Step3를 실행하지 않고 Job이 종료된다.
BATCH_SETP_EXECUTION
- Step2에서 실패하여 Step3에 대한 정보는 생성되지 않는다.
BATCH_JOB_EXECUTION
- Step에 FAILED가 있기 때문에 JobExecution도 FAILED다.
Job 재실행
Job이 실패한 경우 동일한 JobParameter을 가지더라도 재실행이 가능하다. 따라서 예외 발생 코드를 다시 성공하도록 수정하여 실행한다.
@Configuration
@RequiredArgsConstructor
public class StepExecutionConfiguration {
...
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
// throw new RuntimeException("Step2 has failed"); // 예외 발생 주석 처리
System.out.println("step2 has executed"); // 주석 해제
return RepeatStatus.FINISHED; // 주석 해제
})
.build();
}
...
}
실행 결과
2023-07-04 01:17:58.842 INFO 25944 --- [ main] c.e.springbatch.SpringBatchApplication : Started SpringBatchApplication in 2.583 seconds (JVM running for 3.593)
2023-07-04 01:17:58.844 INFO 25944 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [name=user1]
2023-07-04 01:17:59.070 INFO 25944 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{name=user1}]
2023-07-04 01:17:59.116 INFO 25944 --- [ main] o.s.batch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=step1, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
2023-07-04 01:17:59.156 INFO 25944 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step2]
step2 has executed
2023-07-04 01:17:59.202 INFO 25944 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step2] executed in 46ms
2023-07-04 01:17:59.246 INFO 25944 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step3]
step3 has executed
2023-07-04 01:17:59.288 INFO 25944 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step3] executed in 42ms
2023-07-04 01:17:59.328 INFO 25944 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{name=user1}] and the following status: [COMPLETED] in 225ms
- 재실행한 결과 Step1은 이미 COMPLETED이므로 재실행하지 않으며, Step2와 Step3가 실행된다.
BATCH_STEP_EXECUTION
- Step1은 COMPLETED이므로 재실행하지 않는다.
- Step2와 Step3는 재실행하여 COMPLETED가 되었으며 Job도 정상적으로 종료된다.
BATCH_JOB_EXECUTION
- 동일한 JobInstanceId를 가지는 JobExecution이 새롭게 생성된다.
- 두 번째 실행된 Job의 Step2와 Step3가 COMPLETED이므로 새롭게 생성된 JobExecution도 COMPLETED다.
반응형
'프레임워크(Framework) > Spring' 카테고리의 다른 글
[Spring] 스프링 부트 3.2 RestClient 살펴보기 (0) | 2024.05.30 |
---|---|
[Spring Batch] 스프링 배치 StepContribution 이해하기 - Spring boot 2.x (0) | 2023.07.06 |
[Spring Batch] 스프링 배치 Tasklet의 여러 가지 구현 방식 - Spring boot 2.x (0) | 2023.07.02 |
[Spring Batch] 스프링 배치 Step 이해하기 - Spring boot 2.x (0) | 2023.07.01 |
[Spring Batch] 스프링 배치 JobInstance 이해하기 - Spring boot 2.x (0) | 2023.06.25 |