프레임워크(Framework)/Spring

[Spring Batch] 스프링 배치 StepExecution 이해하기 - Spring boot 2.x

잇트루 2023. 7. 4. 21:21
반응형
본 내용은 온라인 강의 사이트 인프런의 정수원 님의 강의 내용이 포함되어 있습니다.
스프링 배치 - Spring Boot 기반으로 개발하는 Spring Batch
 

스프링 배치 - Spring Boot 기반으로 개발하는 Spring Batch - 인프런 | 강의

초급에서 중~고급에 이르기까지 스프링 배치의 기본 개념부터 API 사용법과 내부 아키텍처 구조를 심도있게 다룹니다. 그리고 스프링 배치 각 기능의 흐름과 원리를 학습하게 되고 이를 바탕으

www.inflearn.com

 

 

Intro

스프링 배치의 도메인은 크게 두 가지로 나눌 수 있다.

  1. 배치를 특정 단계 또는 흐름에 따라 처리하고 구성하는 역할을 하는 도메인
    • Job, Step, Flow, Tasklet, …
  2. 배치의 단계마다 실행 정보나 상태 정보를 데이터베이스에 저장하기 위한 메타데이터 도메인
    • 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다.
반응형