Spring Batch

1. 기본 개념과 메타 데이터 테이블

배치 어플리케이션

  • 대용량 데이터 - 배치 어플리케이션은 대량의 데이터를 가져오거나, 전달하거나, 계산하는 등의 처리를 할 수 ​​있어야 합니다.
  • 자동화 - 배치 어플리케이션은 심각한 문제 해결을 제외하고는 사용자 개입 없이 실행되어야 합니다.
  • 견고성 - 배치 어플리케이션은 잘못된 데이터를 충돌/중단 없이 처리할 수 있어야 합니다.
  • 신뢰성 - 배치 어플리케이션은 무엇이 잘못되었는지를 추적할 수 있어야 합니다. (로깅, 알림)
  • 성능 - 배치 어플리케이션은 지정한 시간 안에 처리를 완료하거나 동시에 실행되는 다른 어플리케이션을 방해하지 않도록 수행되어야합니다.

 

Accenture의 배치 노하우 & 기술력과 Spring 프레임워크가 합쳐져 만들어진 것

DI, AOP, 서비스 추상화 등 Spring 프레임워크의 3대 요소를 모두 사용할 수 있으면서, Accenture의 Batch 노하우가 담긴 아키텍처를 사용

 

Quartz는 스케줄러의 역할이지, Batch 와 같이 대용량 데이터 배치 처리에 대한 기능을 지원하지 않습니다.
반대로 Batch 역시 Quartz의 다양한 스케줄 기능을 지원하지 않아서 보통은 Quartz + Batch를 조합해서 사용합니다.
정해진 스케줄마다 Quartz가 Spring Batch를 실행하는 구조라고 보시면 됩니다.

 

정해진 시간마다 데이터 가공이 필요한 경우에 어디서든 Spring Batch가 사용

 

Batch Job을 생성하는 simpleJob 코드를 보시면 simpleStep1을 품고 있음을 알 수 있습니다.
Spring Batch에서 Job은 하나의 배치 작업 단위를 얘기하는데요.
Job 안에는 아래처럼 여러 Step이 존재하고, Step 안에 Tasklet 혹은 Reader & Processor & Writer 묶음이 존재합니다.


Tasklet 하나와 Reader & Processor & Writer 한 묶음이 같은 레벨입니다.
그래서 Reader & Processor가 끝나고 Tasklet으로 마무리 짓는 등으로 만들순 없다습니다.

 

 

출처:  metaDataSchema

 

Spring Batch에선 메타 데이터 테이블들이 필요

Spring Batch의 메타 데이터는 다음과 같은 내용들을 담고 있습니다.

  • 이전에 실행한 Job이 어떤 것들이 있는지
  • 최근 실패한 Batch Parameter가 어떤것들이 있고, 성공한 Job은 어떤것들이 있는지
  • 다시 실행한다면 어디서 부터 시작하면 될지
  • 어떤 Job에 어떤 Step들이 있었고, Step들 중 성공한 Step과 실패한 Step들은 어떤것들이 있는지

기본적으로 H2 DB를 사용할 경우엔 해당 테이블을 Boot가 실행될때 자동으로 생성해주지만, MySQL이나 Oracle과 같은 DB를 사용할때는 개발자가 직접 생성

 

 

SimpleJobConfiguration

@Slf4j
@RequiredArgsConstructor
@Configuration
public class SimpleJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1())
                .build();
    }

    @Bean
    public Step simpleStep1() {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    log.info(">>> This is Step1");
                    return RepeatStatus.FINISHED;
                }).build();
    }
}

BATCH_JOB_INSTANCE

Job Parameter에 따라 생성되는 테이블로 실행했던 simpleJob을 확인 가능

Job Parameter - Spring Batch가 실행될때 외부에서 받을 수 있는 파라미터

  • JOB_INSTANCE_ID
    • BATCH_JOB_INSTANCE 테이블의 PK
  • JOB_NAME
    • 수행한 Batch Job Name
@Slf4j
@RequiredArgsConstructor
@Configuration
public class SimpleJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1(null))
                .build();
    }

    @Bean
    @JobScope
    public Step simpleStep1(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    log.info(">>>>> This is Step1");
                    log.info(">>>>> requestDate = {}", requestDate);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

 

반복 실행시 

Caused by: org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={requestDate=20180805}.  If you want to run this job again, change the parameters.

 

requestDate 변경 후 실행시

BATCH_JOB_EXECUTION

JOB_EXECUTION와 JOB_INSTANCE는 부모-자식 관계

JOB_EXECUTION은 자신의 부모 JOB_INSTACNE가 성공/실패했던 모든 내역을 갖고 있습니다.

 

JOB_EXECUTION 테이블을 보시면 3개의 ROW가 있는데요.
저희가 실행했던 파라미터가 없는 simpleJob, requestDate=20180805 파라미터로 실행했던 simpleJob, requestDate=20180806 파라미터로 실행했던 simpleJob 까지 이렇게 3개의 실행 데이터

 

중간에 실패 시켰을 경우

@Slf4j
@RequiredArgsConstructor
@Configuration
public class SimpleJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1(null))
                .next(simpleStep2(null))
                .build();
    }

    @Bean
    @JobScope
    public Step simpleStep1(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    throw new IllegalArgumentException("step1에서 실패합니다.");
                })
                .build();
    }

    @Bean
    @JobScope
    public Step simpleStep2(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep2")
                .tasklet((contribution, chunkContext) -> {
                    log.info(">>>>> This is Step2");
                    log.info(">>>>> requestDate = {}", requestDate);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

 

 

중간 실패 로직을 정상으로 변경 후 실행

@Slf4j
@RequiredArgsConstructor
@Configuration
public class SimpleJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job simpleJob() {
        return jobBuilderFactory.get("simpleJob")
                .start(simpleStep1(null))
                .next(simpleStep2(null))
                .build();
    }

    @Bean
    @JobScope
    public Step simpleStep1(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep1")
                .tasklet((contribution, chunkContext) -> {
                    //throw new IllegalArgumentException("step1에서 실패합니다.");
                    log.info(">>>>> This is Step1");
                    log.info(">>>>> requestDate = {}", requestDate);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    @JobScope
    public Step simpleStep2(@Value("#{jobParameters[requestDate]}") String requestDate) {
        return stepBuilderFactory.get("simpleStep2")
                .tasklet((contribution, chunkContext) -> {
                    log.info(">>>>> This is Step2");
                    log.info(">>>>> requestDate = {}", requestDate);
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
}

 

동일한 JOB_INSTANCE_ID 값에 대한 실패와 성공 데이터가 쌓이는 것을 확인 할 수 있습니다.

Spring Batch는 동일한 Job Parameter로 성공한 기록이 있을때만 재수행이 안된다는 것을 알 수 있습니다.

 

Reference

https://jojoldu.tistory.com/326

https://jojoldu.tistory.com/325

https://jojoldu.tistory.com/324

'Spring Batch' 카테고리의 다른 글

2. spring Batch Job Flow  (0) 2021.06.20