Spring Batch Guide - 03. 메타 테이블 엿보기

이준영·2021년 1월 11일
0

Spring Batch Guide

목록 보기
3/5

Spring Batch Guide 시리즈는 이동욱 개발자님의 Spring Batch 가이드를 보고 학습한 내용을 정리한 글입니다.

많은 내용이 원 글과 유사할 수 있습니다. 이 점 양해바랍니다 🙏🏻

이전 게시글에서 간단하게 메타 테이블을 소개한 적이 있습니다.

이 메타 테이블이 어떠한 역할을 하고 무엇을 담고 있는지를 하나씩 알아보도록 하겠습니다.

1. BATCH_JOB_INSTANCE

먼저 BATCH_JOB_INSTANCE 에 대해 알아보겠습니다.

메타 테이블이 저장된 MySQL에서 조회를 해보면 한 개의 row가 검색됩니다.

  • JOB_INSTANCE_ID

    • BATCH_JOB_INSTANCE 테이블의 PK
  • JOB_NAME

    • 수행한 Batch Job Name

조회된 row의 JOB_NAME이 이전에 수행했던 simpleJob임을 볼 수 있습니다.

BATCH_JOB_INSTANCE 테이블은 Job Parameter에 따라 생성되는 테이블입니다. Job Parameter는 Spring Batch가 실행될 때 외부에서 받을 수 있는 파라미터입니다.

만약 특정 날짜를 Job Parameter로 넘기면 Spring Batch에서는 해당 날짜 데이터로 조회/가공/입력 등의 작업을 할 수 있습니다.

같은 Batch Job이라도 Job Parameter가 다르면 BATCH_JOB_INSTANCE에는 기록되며, Job Parameter가 같아면 기록되지 않습니다.

실제 어플리케이션 코드를 수정해서 한 번 확인해보도록 하겠습니다.

@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // Constructor DI를 위한 lombok 어노테이션
@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();
    }
}

코드에서 @Value 어노테이션은 org.springframework.beans.factory.annotation 패키지의 어노테이션입니다.

이번에는 'Job Parameter를 이런 방식으로 사용할 수 있다' 정도로만 보고 가도록 하겠습니다.

위의 코드는 Job Parameter로 받은 값을 로그에 추가로 출력시키는 기능입니다.

그럼 Job Parameter를 넣어서 Spring Batch를 실행해보도록 하겠습니다. 이번에도 실행 환경에서 수정을 해서 Job Parameter를 넣어보겠습니다.

실행 환경에서 Configuration > Environment > Program argumentsrequestDate=20210110 값을 입력합니다.

그리고 어플리케이션을 실행하면 Job Parameter가 로그에 찍힌 것을 볼 수 있습니다!

BATCH_JOB_INSTANCE를 다시 보면 Job Instance가 하나 더 추가된 것을 볼 수 있습니다.

그럼 동일한 Job Parameter로 Batch를 실행 시 Job을 새로 생성하지 않는지 확인해보도록 하겠습니다.

JobInstanceAlreadyCompleteException으로 에러 메시지가 출력되었습니다.

A job instance already exists and is complete for parameters={requestDate=20210110}. If you want to run this job again, change the parameters.

메시지를 읽어보면 이미 존재하는 Job Instance이고 만약 Job을 실행하고 싶다면 파라미터를 변경하라고 말합니다.

그럼 파라미터를 requestDate=20210111으로 변경해서 실행해보도록 하겠습니다.

이전과 마찬가지로 올바르게 로그를 출력하면서 Job이 실행되었습니다.

그리고 BATCH_JOB_INSTANCE 테이블에도 하나의 row가 더 생긴 것을 확인할 수 있습니다.

즉 동일한 Job이 Job Parameter가 달라지면 그때마다 BATCH_JOB_INSTANCE에 생성되고 동일한 Job Parameter는 여러개 존재할 수 없습니다.

2. BATCH_JOB_EXECUTION

이번에는 BATCH_JOB_EXECUTION 테이블에 대해 알아보겠습니다.

BATCH_JOB_EXECUTION 테이블을 보면 3개의 row가 있습니다.

파라미터 없이 실행한 simpleJob, requireDate=20210110 파라미터로 실행한 simpleJob, requestDate=20210111 파라미터로 실행한 simpleJob까지 총 3개의 실행 데이터입니다.

Job Instance마다 하나의 row가 생성되는 점은 BATCH_JOB_INSTANCE와 크게 달라보이지 않습니다. 그러나 사실 BATCH_JOB_EXECUTIONBATCH_JOB_INSTANCE부모-자식 관계입니다.

BATCH_JOB_EXECUTION은 부모인 BATCH_JOB_INSTANCE가 Job을 실행했던(성공, 실패 여부와 상관없이) 모든 내역을 갖고 있습니다.

이를 확인해보기 위해 실습 코드를 한번 바꿔보겠습니다.

@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // Constructor DI를 위한 lombok 어노테이션
@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();
    }
}

그리고 이번에는 Job Parameter를 requestDate=20210109로 변경해보겠습니다.

Batch를 실행해보면

이처럼 Batch Job이 실패했음을 볼 수 있습니다. 그럼 BATCH_JOB_EXECUTION 테이블에는 어떤 row가 기록됐는지 확인해보겠습니다.

JOB_INSTANCE_ID가 4인 Job의 STATUSFAILED란 것을 확인할 수 있었습니다.

그럼 Job이 성공하도록 코드를 수정해보겠습니다.

@Slf4j // log 사용을 위한 lombok 어노테이션
@RequiredArgsConstructor // Constructor DI를 위한 lombok 어노테이션
@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) -> {
                    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();
    }
}

그리고 다시 Batch를 실행해보면

Job이 정상적으로 수행되는 것을 확인할 수 있습니다. 그럼 이번에 성공한 기록도 BATCH_JOB_EXECUTION에 기록이 될까요?

JOB_INSTANCE_ID가 4인 Job의 STATUSCOMPLETE인 row가 추가됨을 확인할 수 있었습니다.

이 점이 BATCH_JOB_INSTANCE와 결정적인 차이입니다.

Job Parameter가 requestDate=20210109인 작업이 2번 실행되었고 첫 번째는 실패, 두 번째는 성공했다는 것을 알 수 있습니다.

여기서 알아둬야 할 점은 동일한 Job Parameter로 2번 실행했는데 에러가 발생하지 않았다는 점입니다. Spring Batch는 같은 Job Parameter를 가지는 Job의 성공한 기록이 있을 때만 재실행이 안된다는 것을 알 수 있습니다.

3. JOB, BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION

지금까지 알아본 두 테이블과 실행한 Job간의 관계는 다음과 같습니다.

지금까지 소개한 BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION 두 개의 테이블 외에도 Job과 관련된 테이블은 더 많이 존재합니다.

간단하게 한 가지만 더 소개하면 BATCH_JOB_EXECUTION_PARAM 테이블은 BATCH_JOB_EXECUTION 테이블이 생성될 당시 입력받은 Job Parameter를 담고 있습니다.

이 외 다양한 테이블은 추후 학습해나가면서 소개하도록 하겠습니다.

BATCH_JOB_EXECUTION 외에도 BATCH_STEP_EXECUTION 관련 테이블도 많이 존재합니다. 이는 나중에 학습할 Spring Batch 재시도/SKIP 전략 편에서 자세히 알아보도록 하겠습니다.

4. Spring Batch Test 코드는?

Spring Batch에 적응하기 전까지는 H2를 이용한 테스트 코드는 자제하시길 추천드립니다.

기존에 develop/production 환경에서 Spring Batch를 사용하시는 분들이 Batch Job Instance Context 문제로 여려움을 겪는 것을 많이 봤기 때문입니다.

초반부는 MySQL을 이용하면서 메타 테이블 정보가 남아있는 상태에서의 Spring Batch에 최대한 적응하도록 학습하겠습니다.

정리하며

이번에는 Spring Batch를 실행할 때 필요한 메타 테이블 중 BATCH_JOB_INSTANCEBATCH_JOB_EXECUTION 테이블을 살펴보았습니다.

Job Parameter가 같은 Job이 BATCH_JOB_INSTANCE 테이블에서는 여러번 저장(실행)될 수 없다는 것과 BATCH_JOB_EXECUTION 테이블에는 여러번 저장될 수는 있지만 한번 성공한 Job은 다시 저장될 수 없다는 것을 알 수 있었습니다.

즉, 'Job Parameter를 통해 서로 다른 Job으로 구분된다', '한 번 성공한 Job의 경우 다시 실행할 수 없다'는 것을 잘 알아두면 좋을 것 같습니다 😆

참고 자료

3. Spring Batch 가이드 - 메타테이블엿보기

profile
growing up 🐥

0개의 댓글