주제
- 배치의 개념과 구성 요소에 대해 알아본다.
- 하나의 Job을 여러 Step으로 구성하는 방법을 알아본다.
- Tasklet, Chunk 단위로 처리해본다.
1. Spring Batch란
1.1 정의
1.1.1 Batch 작업이란?
대용량 데이터를 처리하려면 많은 시간과 자원이 소요될 수 있고, 동일한 데이터를 사용하는 다른 서비스에 영향을 줄 수 있다. 때문에 이를 실시간이 아닌 한번에 많은 데이터를 처리할 때가 있다.
실시간이 아닌 일괄적으로 모아서 처리하는 작업을 배치 작업이라고 한다.
1.1.2 Spring Batch란?
- 엔터프라이즈 시스템의 일상적인 작업에 필수적인 강력한 배치 응용 프로그램을 개발할 수 있도록 설계된 가볍고 포괄적인 배치 프레임워크
- 로깅/추적, 트랜잭션 관리, 작업 처리 통계, 작업 재시작, 건너뛰기, 리소스 관리 등 대용량 레코드 처리에 필수적인 재사용 가능한 기능을 제공
- 최적화 및 파티셔닝 기술을 통해 매우 높은 볼륨 및 고성능 배치 작업을 가능하게 하는 고급 기술 서비스 및 기능을 제공
- 단순하고 복잡한 대용량 배치 작업은 확장성이 뛰어난 방식으로 프레임워크를 활용하여 상당한 양의 정보를 처리할 수 있음
(spring.io 제공)
1.2 배치 구성도
1.2.1 Spring Batch 구성도
- 배치는 하나 또는 여러 개의 Job으로 구성될 수 있다.
- 하나의 Job은 하나 또는 여러 개의 Step을 갖고 있다.
- 각 Step은 ItemReader, ItemProcessor, ItemWriter를 갖는다. (ItemProcessor는 생략될 수 있음)
- Job은 JobLauncher가 실행하며, 현재 실행 중인 프로세스의 메타정보는 JobRepository에 저장된다.
1.2.2 Spring Batch 구성 요소
Job
- 배치 처리 과정을 하나의 단위로 만들어 표현한 객체
JobRepository
- 배치 수행과 관련된 수치 데이터와 잡의 상태를 유지 관리
- 스프링 배치 내의 대부분의 중요 컴포넌트가 공유함
- 실행된 Step, 현재 상태, 읽은 아이템 및 처리된 아이템 수 등이 모두 저장됨
JobLauncher
- Job을 실행하는 역할
- Job의 재실행 가능 여부 검증, 실행 방법, 파라미터 유효성 검증 등을 수행함
- 직접 다룰일이 거의 없음
- 각 Step이 실행되면 JobRepository는 현재 상태로 갱신
Step
- Job을 구성하는 독립된 단위
- Tasklet 혹은 Chunk 기반으로 처리할 수 있음
- Job의 상태를 나타내는 단위
Tasklet
- Step이 중단될 때까지 execute 메서드가 계속 반복해서 수행하고, 수행할 때마다 독립적인 트랜잭션을 획득
- 초기화, Stored Procedure 실행, 알림 전송과 같은 용도에 Tasklet 방식이 사용될 수 있다.
Chunk
- ItemReader에서 데이터를 읽어 Chunk라는 덩어리를 만든 뒤 Chunk 단위로 트랜잭션을 다룸
- 많은 블로그에서 한 번에 한개씩 데이터를 읽어야한다고 가이드를 하고있지만,
Chunk를 만들기 위해 한 번에 한개씩 읽는다면 I/O 작업의 오버헤드가 많이 발생할 수 있다.
Chunk 크기가 10,000이라면 한 번에 10,000개의 아이템을 일겅서 메모리에 적재한 후 일괄 처리한다면 Chunk를 바로 만들어 처리할 수 있기 때문에 성능상 이점을 취할 수 있다. - 백엔드 I/O 작업 횟수, 처리량을 고려하여 Chunk 크기를 적절하게 설정해야 한다.
Spring Batch에서는 주로 두 가지 방식으로 배치 작업을 구현합니다: Tasklet과 Chunk 방식입니다. 각각의 방식은 특정한 상황과 요구사항에 맞게 사용됩니다.
1. **Tasklet 방식:**
- Tasklet은 간단한 단위의 작업을 처리하는 방식입니다. Spring Batch에서는 `Tasklet` 인터페이스를 구현하거나 `MethodInvokingTaskletAdapter`를 사용하여 Tasklet을 구현할 수 있습니다.
- 주로 한 번의 실행으로 완료될 작업에 사용됩니다. 예를 들어, 파일을 읽어 데이터를 가공하고 처리하는 등의 작업에 적합합니다.
- Tasklet은 스텝의 시작과 끝에서 실행되며, 성공 또는 실패 여부에 따라 다음 스텝으로 이동합니다.
- 사용자 정의한 비즈니스 로직을 직접 구현하고자 할 때 유용합니다.
2. **Chunk 방식:**
- Chunk 방식은 데이터를 묶어서 일괄 처리하는 방식으로, 대용량 데이터 처리에 적합합니다.
- 스텝 내에서 읽기, 가공/처리, 쓰기 작업을 트랜잭션 단위로 묶어서 처리합니다.
- 하나의 트랜잭션 내에서 여러 아이템을 읽어오고, 한 번에 처리하며, 그 후에 한 번의 트랜잭션으로 한 번에 여러 아이템을 쓸 수 있습니다.
- Chunk 방식은 대용량 데이터를 효율적으로 처리할 때 유용하며, 처리량을 높이는 데 도움이 됩니다.
- 병렬 처리도 지원하므로 높은 처리량과 성능을 얻을 수 있습니다.
적절한 용도:
- **Tasklet 방식**: 단순한 작업 또는 비즈니스 로직을 특정한 순서로 처리해야 할 때 사용됩니다. 예를 들어, 특정 파일을 읽어서 데이터베이스에 저장하는 작업이나, API 호출을 통해 데이터를 가져와서 가공하는 작업 등이 포함됩니다.
- **Chunk 방식**: 대용량 데이터를 처리해야 할 때, 데이터를 묶어서 일괄 처리하고 성능을 향상시키고 싶을 때 사용됩니다. 데이터베이스에서 대량의 레코드를 읽어와 가공한 후 다시 데이터베이스에 저장하는 등의 작업에 많이 사용됩니다.
물론, 상황에 따라 Tasklet과 Chunk 방식을 조합하여 복잡한 배치 프로세스를 구성할 수도 있습니다.
어떤 방식을 선택하느냐에 따라 성능과 유지 보수성 등이 크게 영향을 받을 수 있으므로, 프로젝트의 요구사항과 목표에 맞게 선택하는 것이 중요합니다.
출처 : ChatGpt
1.3 Spring Batch 메타 데이터
- 스프링 배치에서는 각 작업이 실행될 때마다 Job 또는 Step에 대한 상태와 이력을 기록한다.
- 스프링 배치 라이브러리에서 DDL과 옵션을 통해 자동 생성 기능을 제공함
- 총 6개의 테이블로 이루어져 있으며 데이터가 적재되는 순서는 다음과 같다
테이블명 용도 BATCH_JOB_INSTANCE Job의 생성 정보 BATCH_JOB_EXECUTION Job의 실행 정보
(Instance 하나에 여러 번의 Job Execution 발생 가능)BATCH_JOB_EXECUTION_PARAM Job에 사용되는 파라미터 값 BATCH_JOB_EXECUTION_CONTEXT Job에 사용되는 모든 정보가 기록되는 Context 저장
Job Execution 하나당 하나의 Job Execution Context를 가짐BATCH_STEP_EXECUTION Step의 실행 정보 BATCH_STEP_EXECUTIOIN_CONTEXT Step에 사용되는 모든 정보가 기록되는 Context 저
2. Spring Batch 실습
2.1 사전 세팅
2.1.1 Dependency
implementation 'org.springframework.boot:spring-boot-starter-batch'
2.1.1 Yaml
# 자동 생성 옵션
spring:
batch:
jdbc.initialize-schema: always
job.enabled: false # Application 시작할 때 Job 자동 실행 막음
2.2 간단한 배치 프로그램 만들어보기(Tasklet)
2.2.1 Tasklet으로 여러 Step을 처리하기
package com.kingpiggy.app.batch.statistic.dailyuserate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static com.kingpiggy.app.batch.statistic.dailyuserate.DailyUseRateStatBatchConstant.*;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class DailyUseRateStatBatchConfig {
private final JobBuilderFactory jobBuilderFactory;
private final StepBuilderFactory stepBuilderFactory;
private final DailyUseRateStatBatchService dailyUseRateStatJobService;
@Bean(name = JOB_DAILY_USE_RATE_STAT)
public Job dailyUseRateStatJob(){
return jobBuilderFactory.get(JOB_DAILY_USE_RATE_STAT)
.start(step1())
.next(step2())
.build();
}
@JobScope
public Step step1() {
return stepBuilderFactory.get(STEP1_DAILY_USE_RATE_STAT)
.tasklet(((contribution, chunkContext) -> {
log.info("[step1#tasklet] param : [{}]", chunkContext.getStepContext().getJobParameters().get(PARAM_DAILY_USE_RATE_STAT));
dailyUseRateStatJobService.doSomethingA();
return RepeatStatus.FINISHED;
}))
.build();
}
@JobScope
public Step step2() {
return stepBuilderFactory.get(STEP2_DAILY_USE_RATE_STAT)
.tasklet(((contribution, chunkContext) -> {
log.info("[step2#tasklet] param : [{}]", chunkContext.getStepContext().getJobParameters().get(PARAM_DAILY_USE_RATE_STAT));
dailyUseRateStatJobService.doSomethingB();
return RepeatStatus.FINISHED;
}))
.build();
}
}
2.3 간단한 배치 프로그램 만들어보기(ItemReader, ItemProcessor, ItemWriter)
2.3.1 Chunk 단위를 ItemReader, ItemProceesor, ItemWriter로 처리하기
@Bean(name = JOB_WEEKLY_USE_RATE_STAT)
public Job weeklyUseRateStatJob(){
return jobBuilderFactory.get(JOB_WEEKLY_USE_RATE_STAT)
.start(step1())
.build();
}
@JobScope
public Step step1() {
return stepBuilderFactory.get(STEP1_WEEKLY_USE_RATE_STAT)
.<WeeklyUseRateDto, WeeklyUseRateDto> chunk(weeklyUseRateStatBatchProps.getChunkSize())
.reader(reader(null))
.processor(processor())
.writer(writer())
.build();
}
@Bean
@StepScope
public WeeklyUseRateStatBatchItemReader reader(@Value("#{jobParameters[" + PARAM_WEEKLY_USE_RATE_STAT + "]}") String region) {
return new WeeklyUseRateStatBatchItemReader(getWeeklyUseRateData());
}
@Bean
@StepScope
public WeeklyUseRateStatBatchItemProcessor processor() {
return new WeeklyUseRateStatBatchItemProcessor();
}
@Bean
@StepScope
public WeeklyUseRateStatBatchItemWriter writer() {
return new WeeklyUseRateStatBatchItemWriter();
}
private List<WeeklyUseRateDto> getWeeklyUseRateData() {
List<WeeklyUseRateDto> sampleDataList = new ArrayList<>();
for (int i=0; i<100; i++) {
sampleDataList.add(new WeeklyUseRateDto("Name_" + i, "N"));
}
return sampleDataList;
}
# 참고자료
728x90
'# Back-End > Spring' 카테고리의 다른 글
ShedLock을 이용한 스케줄 작업 중복 방지하기 (0) | 2023.08.29 |
---|---|
[JPA] 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock) (0) | 2023.04.22 |
Insert문 전에 발생하는 Select문을 제거해보자 (0) | 2023.04.15 |
JPA Batch Insert/Update를 적용하여 성능 향상하기 (0) | 2023.04.15 |
@Transactional 어노테이션을 이용한 트랜잭션 관리 (0) | 2023.04.08 |
[Spring Data JPA] Pageable을 이용한 Paging 조회 (0) | 2022.10.01 |
Logback과 slf4j 간단한 예제 (0) | 2022.09.02 |