Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
Tags
- 스프링 배치
- vm
- Spring
- 웹 서버
- 백엔드
- HTTP
- 스프링 부트
- 스프링
- ORM
- Java
- 가상화
- spring boot
- 데이터베이스
- 배포
- Spring Security
- web server
- 자바
- CS
- spring cloud
- mysql
- 도커
- Container
- 영속성 컨텍스트
- 스프링 시큐리티
- CI/CD
- JPA
- 컨테이너
- virtualization
- computer science
- spring batch
Archives
- Today
- Total
개발 일기
[Spring Batch] Spring Batch에서 Tasklet 방식과 Chunk 방식 비교 본문
Spring Batch에서 데이터를 처리하는 방법에는 Tasklet방식과 Chunk방식이 있다.
이 두 가지 비교를 통해 각각 어떤 상황에 쓸 수 있을지 알아 두어야 적절한 데이터 처리 방식을 채택하여 배치 처리를 할 수 있을 것 같아 정리하게 됐다.
Tasklet 처리 방식
- Tasklet은 Spring Batch에서 사용되는 인터페이스로, 배치 작업에서 단일 작업(task)을 수행하기 위한 구성 요소이다.
- 해당 데이터 처리 방식에서 Step은 하나의 Tasklet으로 구성된다.
- Tasklet을 사용하면 하나의 작업을 간단하게 실행할 수 있으며, 특정 로직을 정의하여 배치 작업 중 반복적으로 실행시킬 수 있다.
- Tasklet의 execute() 메서드가 호출될 때마다 단일 트랜잭션이 생성되며 Tasklet의 전체 실행이 하나의 트랜잭션으로 묶인다.
➜ 즉, execute()이 한번 실행될때마다 하나의 트랜잭션 범위가 되는 것이다. - 아무래도 처리 메소드(execute())가 하나로 되어 있고 그 직접 구현한 메소드를 반복적으로 실행하기 때문에 처리 로직이 복잡하지 않은 경우 적합하고 대용량 데이터를 다루는 경우 적합하지 않다.
- 데이터 읽기/처리/쓰기와 같은 반복적인 작업보다는 파일 삭제, 데이터 초기화와 같은 비반복적이고 명확한 작업 그리고 대용량 데이터가 아닌 배치 처리에 적합
즉, Tasklet 지향 처리란, 단일 작업을 수행하는 Tasklet을 사용하여 한 번에 한 작업을 처리하고, 그 작업이 끝나면 트랜잭션이 완료되는 구조를 의미
위 코드가 Tasklet 인터페이스이며 단일 메소드인 execute()를 제공하고 있다. execute()의 응답 객체인 RepeatStatus는 Enum 타입으로 RepeatStatus.FINISHED와 RepeatStatus.CONTINUABLE 이렇게 두 가지가 있다.
예외가 터지거나 RepeatStatus.FINISHED를 반환할때까지 반복적으로 execute()를 반복한다.
@Bean
public Step step() {
return stepBuilderFactory.get("step")
.tasklet((contribution, chunkContext) -> {
// 비즈니스 로직 작성
return RepeatStatus.FINISHED;
})
.build();
}
위와 같이 익명 클래스와 람다 표현식을 통해 Step 선언 시에 tasklet을 함께 선언할 수 있으며 아래와 같이 별도 클래스로 따로 정의한 후 끌어다 쓰는 방법이 있다. 위의 코드가 더 간결해보이기 때문에 로직만 단순하다면 위의 방식으로 처리하는 것이 좋겠지만 로직이 복잡하다면 다소 코드의 가독성을 해칠 수 있으므로 아래와 같이 따로 정의하여 가독성, 재사용성, 확장성을 가져오는 방식으로 구현하는 것이 좋을 것 같다.
@Bean
public Step step() {
return stepBuilderFactory.get("step")
.tasklet(new CustomTasklet())
.build();
}
public class CustomTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
// 비즈니스 로직 작성
return RepeatStatus.FINISHED;
}
}
두번째로는 Chunk기반 처리 방식이다.
Chunk 기반 처리 방식
- 대략적으로 Chunk의 뜻은 '하나의 덩어리(묶음)'라는 뜻으로 생각하면 된다.
- 스프링 배치에서의 Chunk란? 데이터 덩어리로 작업 할 때 각 커밋 사이에 처리되는 row 수
- 즉, 전체 처리해야할 Task를 여러 개의 묶음(Chunk)들로 분리하여 처리하는 것이다.
- 하나의 Chunk가 하나의 트랜잭션 범위가 된다.
➜ 실패할 경우엔 해당 Chunk 만큼만 롤백이 되고, 이전에 커밋된 트랜잭션 범위까지는 반영이 되는 것이다. - Step은 ItemReader, ItemProcessor, ItemWriter 이렇게 3가지로 구성된다.
➜ ItemReader와 ItemProcessor에서 데이터는 1건씩 처리되고 ItemWriter에선 Chunk 단위로 한번에 처리되는 것이다. - 대용량 데이터에 대해 읽기/처리/쓰기와 같은 반복적이며 상대적으로 복잡한 배치 작업을 처리할때 적합하다.
즉, Chunk 지향 처리란 한 번에 하나씩 데이터를 읽어 Chunk라는 덩어리를 만든 뒤, Chunk 단위로 트랜잭션을 다루는 것을 의미한다!
@Configuration
@RequiredArgsConstructor
public class ChunkBatchConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager platformTransactionManager;
private final BeforeEntityRepository beforeEntityRepository;
private final AfterEntityRepository afterEntityRepository;
@Bean
public Job transferDataJob() {
System.out.println("transfer data job");
return new JobBuilder("transferDataJob", jobRepository)
.start(transferDataStep())
.build();
}
@Bean
public Step transferDataStep() {
System.out.println("transferData");
return new StepBuilder("transferData", jobRepository)
.<BeforeEntity, AfterEntity> chunk(10, platformTransactionManager)
.reader(beforeReader())
.processor(middleProcessor())
.writer(afterWriter())
.build();
}
@Bean
public RepositoryItemReader<BeforeEntity> beforeReader() {
return new RepositoryItemReaderBuilder<BeforeEntity>()
.name("beforeReader")
.pageSize(10)
.methodName("findAll")
.repository(beforeEntityRepository)
.sorts(Map.of("id", Sort.Direction.ASC))
.build();
}
@Bean
public ItemProcessor<BeforeEntity, AfterEntity> middleProcessor() {
return new ItemProcessor<BeforeEntity, AfterEntity>() {
@Override
public AfterEntity process(BeforeEntity item) throws Exception {
AfterEntity afterEntity = AfterEntity.builder()
.username(item.getUsername())
.build();
return afterEntity;
}
};
}
@Bean
public RepositoryItemWriter<AfterEntity> afterWriter() {
return new RepositoryItemWriterBuilder<AfterEntity>()
.repository(afterEntityRepository)
.methodName("save")
.build();
}
}
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] 멀티 모듈 (단일) 프로젝트 (Multi-Module Single Project): 역할과 책임이 명확한 모듈 설계 (1) | 2024.10.10 |
---|---|
[Spring Boot] @ResponseBody와 ResponseEntity 비교 (2) | 2024.09.19 |
[Spring Batch] Spring Batch5 이전 버전(4)과의 차이점 (3) | 2024.09.18 |
[Spring Batch] 배치 처리와 스프링 배치 그리고 스케줄러 (2) | 2024.09.17 |
[Spring Boot] 예외처리 및 @RestControllerAdvice & @ExceptionHandler (0) | 2024.09.15 |