시작에 앞서, 해당 포스팅은 Spring Batch의 예시 코드에 대해 정리하고 설명하기보다 Spring Batch를 적용하기 위해 공부하며 정리한 개념과 구조, 동작 방식에 대해 공부한 기록을 남긴 것에 더 가깝습니다.
실제 예시 코드와 사용 방법에 대한 더 자세한 내용은 포스팅 맨 하단에 제가 보고 공부했던 좋은 포스팅들의 링크를 함께 두겠습니다.
잘못된 부분이나 궁금한 부분은 댓글로 남겨주시면 답변드리겠습니다. 감사합니다.
Spring Batch란,
먼저 배치 프로세싱은 '일괄처리'라는 뜻을 가지고 있으며, 일괄처리의 의미는 일련의 작업을 정해진 로직으로 수행하는 것이라고 할 수 있습니다.
공식 문서에 따르면 Spring Batch는 이러한 배치 프로세싱을 기반으로 로깅/추적, 트랜잭션 관리, 작업 처리 통계, 작업 재시작, 건너뛰기, 리소스 관리 등 대용량 레코드 처리에 필수적인 재사용 가능한 기능들을 제공한다고 나와있습니다.
- Read : 원하는 조건의 데이터 레코드를 DB에서 읽어옵니다.
- Processing : 읽어온 데이터를 비즈니스 로직에 따라 처리합니다.
- Write : 처리된 데이터를 DB에 업데이트(저장)합니다.
일반적인 배치 프로그램의 시나리오는 다음과 같이 'Read(가져와서)' -> 'Processing(처리하고)' -> 'Write(저장하는)' 구조로 이루어져 있으며, Spring Batch는 이 기본 배치 반복을 자동화하여 일반적으로 상호작용 없이 오프라인 환경에서 유사한 트랜잭션을 세트로 처리하는 기능을 제공합니다.
그리고 Spring Batch 기능과 스케쥴러 기능에 대한 차이가 헷갈릴 수 있는데, 공식 문서에 따르면 Spring Batch는 스케쥴링 프레임워크가 아니며, 스케쥴러를 대체하는 것이 아니라 스케쥴러와 함께 작동하기 위한 것으로 설명되어 있으며, 실제로 간단한 Spring Batch 코드를 작성하고 사용해보게 되면 그 차이를 쉽게 이해할 수 있습니다.
***
읽어온 데이터를 가공, 처리하는 비즈니스 로직(Processing)과 저장하는 부분(Write)을 따로 분리한 이유는 각각의 역할을 단순 명료하게 분리하기 위함입니다. 이처럼 대용량 데이터에 대한 배치 처리는 최대한 단순하게 읽고, 처리하고, 저장되도록 하는 것이 좋습니다.
Spring Batch의 메타 테이블,
Spring Batch 어플리케이션을 운영하기 위한 메타 데이터는 여러 테이블에 나눠져 있으며, 이 테이블이 있어야만 Spring Batch가 정상 작동합니다. H2 DB를 사용할 경우에는 해당 테이블을 Spring Boot가 자동으로 생성해주지만, MySQL이나 Oracle과 같은 DB를 사용할 때는 개발자가 직접 생성해야 합니다.
직접 생성하는 방법으로는 Spring Batch에 존재하는 스키마를 활용하여 create table을 하는 방법과
# Batch
spring.batch.initialize-schema=always
properties 파일에 spring.batch.initialize-schema=always 설정을 통해 생성하는 방법이 있습니다.
(일반적으로 메타 테이블 없이는 Spring Batch Framework를 실행시킬 수 없으나, 필요에 따라 커스터마이징을 통해 메타 테이블 없이도 실행되게 만들 수 있다고 합니다.)
메타 테이블에 대해서 자세하게 살펴보기 전에 이어지는 간단한 Spring Batch 예시 코드를 통해 Spring Batch 동작 방식을 먼저 살펴보겠습니다.
간단한 코드 예시,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
먼저 의존성을 추가합니다. spring batch 외에도 메타 테이블이 필요하기 때문에 사용하는 데이터베이스 연결에 관련된 의존성 및 설정은 기본적으로 필요합니다.
(참고 자료는 포스팅 맨 하단에 링크해놓겠습니다.)
@EnableBatchProcessing 어노테이션을 통해 Spring Batch 기능을 활성화합니다.
@Slf4j
@RequiredArgsConstructor
@Configuration
public class TestJobConfiguration {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
@Bean
public Job testJob() {
return jobBuilderFactory.get("testJob") // testJob 이라는 이름의 Batch Job을 생성합니다.
.start(testStep1())
.next(testStep2(null))
.build();
}
@Bean
public Step testStep1() {
return stepBuilderFactory.get("testStep1") // testStep1 라는 이름의 Batch Step을 생성합니다.
.tasklet((contribution, chunkContext) -> { // Tasklet은 Step 안에서 단일로 수행될 커스텀한 기능들을 선언할 떄 사용합니다.
log.info(">>>>> This is Step1");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
@JobScope
public Step testStep2(@Value("#{jobParameters[requestDate]}") String requestDate) {
return stepBuilderFactory.get("testStep2") // testStep2 라는 이름의 Batch Step을 생성합니다.
.tasklet((contribution, chunkContext) -> { // Tasklet은 Step 안에서 단일로 수행될 커스텀한 기능들을 선언할 떄 사용합니다.
log.info(">>>>> This is Step2");
log.info(">>>>> requestDate = {}", requestDate);
return RepeatStatus.FINISHED;
})
.build();
}
}
TestJobConfiguration.class
(아래 Spring Batch의 구조에 대한 설명의 참고 자료로 사용할 목적으로 간단하게만 작성하였습니다.)
Spring Batch의 구조,
먼저 Spring Batch에서 Job은 Spring Batch가 해야 할 작업 중 가장 큰 범위를 나타냅니다.
Bean을 통해 Job을 등록시킨 후 Spring Boot 실행 시 간단한 Parameter 설정을 통해 원하는 Job을 실행시킬 수 있는데요.
Spring Batch는 최소 1개 이상의 Job을 생성할 수 있고, 각각의 Job 안에는 여러 Step이 존재할 수 있으며, 각각의 Step은 Tasklet 혹은 Read, Processing, Write 묶음 과정이 존재할 수 있습니다.
이때 Tasklet 하나와 Read & Processing & Write 한 묶음을 같은 레벨로 볼 수 있으며, 이는 각각 Tasklet과 Chunk 지향 처리 방식으로 볼 수 있습니다. 하지만 Tasklet과 Chunk 지향 처리 방식은 각기 다른 것으로 볼 수 있기 때문에 Read & Processing 이후 Tasklet으로 마무리 짓는 것과 같은 형태로 Step을 만들 수 없다는 것이 주의할 부분입니다.
***
Chunk 지향 처리란 한 번에 하나씩 데이터를 읽어 Chunk라는 덩어리를 만든 뒤, Chunk 단위로 트랜잭션을 다루는 것을 의미하며, 해당 포스팅에서는 Tasklet 위주의 개념과 구조, 동작 방식을 다룰 예정입니다.
다시 메타 테이블,
다시 Spring Batch의 메타 테이블에 대해 살펴보면 Spring Batch는 6개의 메타 테이블과 3개의 시퀀스 테이블이 존재하며, Spring Batch의 Job이 실행될 때마다 Job에 대한 다양한 정보가 여기에 저장됩니다.
Spring Batch에서 해당 Table이 없이 실행되지 않게 했다는 것은 그만큼 중요한 정보들이 저장된다는 것으로도 볼 수 있습니다.
위에서 본 간단한 동작 코드와 구조를 토대로 Job에 대한 정보가 기록되는 메타 테이블에 대해서 살펴보겠습니다.
* BATCH_JOB_INSTANCE
BATCH_JOB_INSTANCE 테이블은 Spring Batch 메타 테이블 전체 계층 구조의 최상위 역할을 하며 JobParameters에 의해 생성되는 테이블입니다.
JobParameters는 Spring Batch가 실행될 때 외부에서 받을 수 있는 파라미터이며, 예를 들어 특정 날짜를 JobParameters로 넘기면 Spring Batch에서는 해당 날짜 데이터로 조회, 가공, 입력 등의 작업을 할 수 있게 됩니다.
JobInstanceAlreadyCompleteException: A job instance already exists and is complete for parameters={같은 Job Parameter}. If you want to run this job again, change the parameters.
같은 Batch Job이라도 JobParameters가 다르면 BATCH_JOB_INSTANCE에 기록되며, JobParameters가 같다면 위와 같은 오류와 함께 데이터가 기록되지 않습니다.
* JobInstance = Job + 식별 JobParameters
개발자는 전달되는 매개변수를 제어할 수 있기 때문에 JobInstance 정의 방법을 효과적으로 제어할 수 있습니다.
* BATCH_JOB_EXECUTION
BATCH_JOB_EXECUTION과 BATCH_JOB_INSTANCE는 부모-자식 관계입니다.
BATCH_JOB_EXECUTION은 작업을 실행하려는 단일 시도의 기술 개념을 나타내기 때문에 자신의 부모인 BATCH_JOB_INSTANCE가 성공 또는 실패했던 모든 내역 정보(시작 시간, 종료 코드 등)를 가지고 있습니다.
만약 어떤 Job이 실행에 실패했을 때, FK인 JOB_INSTANCE_ID로 연결된 BATCH_JOB_EXECUTION row의 결과 STATUS 값이 FAILED 인 것을 확인할 수 있습니다.
또한 실패 이후 같은 JobInstance로 실행되어 성공했을 때, row를 보면 같은 JOB_INSTANCE_ID를 가진 2개의 row가 쌓인 것을 볼 수 있는데요. 첫 번째 row의 결과인 STATUS는 FAILED이지만, 두 번째 row의 STATUS는 COMPLETED 인 것을 볼 수 있습니다.
이 결과를 통해 알 수 있는 중요한 내용은 실패 후 동일한 JobParameters로 다시 실행했는데도 오류가 발생하지 않고 실행된다는 점입니다. 따라서 Spring Batch는 동일한 JobParameters로 성공한 기록이 있을 때만 재수행이 안된다는 것을 알 수 있습니다.
(JobInstance에 해당하는 실행이 성공적으로 완료되지 않은 한 완료된 것으로 간주하지 않으며, 하나의 JobInstance에 대한 실패와 성공 JobExecution이 여러 개 있을 수 있다는 것입니다.)
* BATCH_JOB_EXECUTION_PARAMS
BATCH_JOB_EXECUTION 테이블이 생성될 당시에 입력받은 JobParameters에 대한 정보를 담고 있습니다.
* BATCH_JOB_EXECUTION_CONTEXT
BATCH_JOB_EXECUTION_CONTEXT 테이블에는 JobExecution의 ExecutionContext 정보가 들어있습니다. 이 ExecutionContext 데이터는 일반적으로 JobInstance가 실패 시 중단된 위치에서 다시 시작할 수 있는 정보를 저장하고 있습니다.
* BATCH_STEP_EXECUTION
BATCH_STEP_EXECUTION 테이블에는 StepExecution에 대한 정보를 저장하고 있습니다. BATCH_JOB_EXECUTION 테이블과 여러 면에서 유사하며, STEP의 EXECUTION 정보인 읽은 수, 커밋 수, 스킵 수 등 다양한 정보를 추가로 담고 있습니다.
* BATCH_STEP_EXECUTION_CONTEXT
BATCH_STEP_EXECUTION_CONTEXT 테이블에는 StepExecution의 ExecutionContext 정보가 들어있습니다. 이 ExecutionContext 데이터는 일반적으로 JobInstance가 실패 시 중단된 위치에서 다시 시작할 수 있는 정보를 저장하고 있습니다.
JobParameters와 Scope,
Spring Batch의 경우 외부 혹은 내부에서 파라미터를 받아 여러 Batch 컴포넌트에서 사용할 수 있게 지원하고 있습니다. 이 파라미터를 JobParameters라고 합니다.
Scope에는 @JobScope와 @StepScope가 있으며 Bean이 생성될 때 JobParameter가 생성되기 때문에 JobParameters를 사용하기 위해서는 @JobScope 또는 @StepScope 선언이 필요합니다. 사용법은 아래와 같이 SpEL로 선언해서 사용할 수 있습니다.
@Value("#{jobParameters[파라미터명]}")
@JobScope를 사용하게 되면 Spring Batch가 Spring 컨테이너를 통해 지정된 Job의 실행 시점에 해당 컴포넌트를 Spring Bean으로 생성합니다. 마찬가지로 StepScope는 Step 실행 시점에 Bean이 생성됩니다.
이렇게 어플리케이션 실행 시점에 Bean을 생성하는 것이 아니라 Job, Step의 실행 시점에 Bean이 생성되도록 지연시켰을 때, JobParameters의 Late Binding이 가능하다는 점과 동일한 컴포넌트를 병렬 혹은 동시에 사용할 때 유리하다는 크게 2가지 장점이 있습니다.
JobLauncher, JobRepository
'JobLauncher'는 구현된 Job을 실행할 수 있게 해주는 Interface로 JobLauncher는 실행을 위한 run 메서드 하나만 가지고 있으며, Job과 JobParameters를 인자로 받습니다.
'JobRepository'는 JobLauncher, Job 및 Step 구현에 대한 CRUD 작업을 제공하는 인터페이스입니다.
< 참고 자료 >
* Spring Batch의 처음부터 끝까지 상세하게 설명해주는 참고 자료로 해당 자료를 통해 공부하며 정리한 내용입니다.
* 공식 문서
'Programming > Spring Boot' 카테고리의 다른 글
JPA @Modifying clearAutomatically 속성 발생할 수 있는 문제 (0) | 2022.04.20 |
---|---|
생성자 주입과 필드 주입, 수정자 주입 정리 (feat. 의존성 관계 주입) (0) | 2022.03.31 |
Redis 동시성 처리를 위한 Transaction 사용 (MULTI, EXEC, DISCARD, WATCH) (2) | 2022.03.05 |
Spring Boot 타임리프 Thymeleaf layout 적용하는 방법 (4) | 2022.02.28 |
Querydsl Paging 페이징 처리, Custom PageRequest 사용하는 이유 (0) | 2022.02.28 |