배치(Batch)란?
배치는 대량의 데이터를 한 번에 일괄 처리하는 방식을 말한다.
실시간으로 요청이 들어올 때마다 처리하는 게 아니라, 데이터를 모아뒀다가 한꺼번에 처리하는 것이다.
대표적인 사용 사례는 아래와 같다.
- 일매출 집계 — 하루치 거래 데이터를 새벽에 한 번에 집계
- 구독 메일 발송 — 정해진 시간에 구독자 전체에게 메일 일괄 전송
- 데이터 백업 — 트래픽이 적은 새벽 시간대에 DB 백업
Batch vs Scheduler
여기서 헷갈릴 수 있는 게 Batch와 Scheduler의 차이다.
구분 역할
| Batch | 대량 데이터를 일괄 처리하는 것 |
| Scheduler | 정해진 시간에 작업을 실행시키는 것 |
배치는 "대량 처리"를 의미하는 것이지, 주기적으로 실행된다는 뜻이 아니다.
Spring Batch는 Quartz 같은 스케줄러와 함께 사용하도록 설계된 것이지, 스케줄러를 대체하는 게 아니다.
Spring Batch 핵심 용어 정리
Spring Batch의 아키텍처는 Job → Step → Reader/Processor/Writer 계층 구조로 이루어져 있다.
Job
배치 처리의 최상위 단위다. 하나 이상의 Step을 포함하며, 각 Job은 고유한 이름을 가진다.
Job
├── Step 1
├── Step 2
└── Step 3
JobInstance
Job이 실제로 실행된 인스턴스다. 예를 들어 "매일 새벽 매출 집계 Job"이 1월 1일에 실행되면 1월 1일 JobInstance가 생성되고, 1월 2일에 실행되면 1월 2일 JobInstance가 새로 생성된다.
JobParameters
JobInstance를 생성하고 구별하는 데 사용되는 파라미터다.
String, Double, Long, Date 4가지 타입을 지원한다.
JobExecution
JobInstance의 한 번의 실행 시도를 나타낸다. 1월 1일 Job이 실패해서 재실행하면, 같은 JobInstance에 대한 새로운 JobExecution이 생성된다. 실행 상태, 시작/종료 시간 등을 담고 있다.
Step
Job의 하위 단계로, 실제 배치 처리 작업이 이루어지는 단위다. 각 Step은 Tasklet 방식 또는 Chunk 방식 중 하나로 구성된다.
StepExecution
Step의 한 번의 실행을 나타낸다. 읽은 아이템 수, 쓴 아이템 수, 커밋 횟수, 스킵한 아이템 수 같은 상세 정보를 포함한다.
ExecutionContext
Step 간, 또는 Job 실행 도중 데이터를 공유하는 저장소다. Job이 실패했을 때 마지막 실행 상태를 재구성해 재시작할 수 있게 해준다.
JobRepository
배치 작업에 관련된 모든 정보(JobExecution, StepExecution, JobParameters 등)를 저장하고 관리한다. Job이 실행되면 JobRepository가 새로운 Execution을 생성하고 상태를 추적한다.
JobLauncher
Job과 JobParameters를 받아 실제로 Job을 실행시키는 역할을 한다.
ItemReader / ItemProcessor / ItemWriter
- Chunk 방식 Step의 핵심 3요소다.
컴포넌트 역할
| ItemReader | DB, 파일, 메시지 큐 등에서 데이터를 읽어옴 |
| ItemProcessor | 읽어온 데이터를 필터링/변환 처리 (선택) |
| ItemWriter | 처리된 데이터를 DB나 파일에 최종 기록 |
메타 테이블
Spring Batch는 배치 작업 상태를 관리하기 위해 6개의 메타 테이블을 자동으로 생성한다.
테이블 설명
| BATCH_JOB_INSTANCE | Job 실행 인스턴스 정보 |
| BATCH_JOB_EXECUTION | JobExecution 실행 이력 (상태, 시작/종료 시간) |
| BATCH_JOB_EXECUTION_PARAMS | Job 실행 시 사용된 파라미터 |
| BATCH_JOB_EXECUTION_CONTEXT | JobExecution 단위 공유 데이터 |
| BATCH_STEP_EXECUTION | StepExecution 실행 이력 (read/write/skip 횟수 등) |
| BATCH_STEP_EXECUTION_CONTEXT | StepExecution 단위 공유 데이터 |
배치를 실행하면 이 테이블들에 자동으로 데이터가 쌓인다. 덕분에 실패한 Job을 재시작할 때 어디서 멈췄는지 알 수 있다.
Step 구성 방식 — Tasklet vs Chunk
Job을 실행하면 Step이 순차적으로 실행된다. Step은 크게 두 가지 방식으로 구성할 수 있다.
Tasklet 방식
단순한 단일 작업에 적합한 방식이다. 리소스 정리, 시스템 상태 체크처럼 "딱 한 번 실행하고 끝나는" 작업에 주로 사용한다.
execute() 메서드를 구현하면 되고, RepeatStatus.FINISHED를 반환하면 완료, RepeatStatus.CONTINUABLE을 반환하면 계속 실행된다.
@Bean
public Step taskletStep() {
return stepBuilderFactory.get("taskletStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("파일 정리 완료");
return RepeatStatus.FINISHED;
})
.build();
}
Chunk 방식
대용량 데이터 처리에 적합한 방식이다. 데이터를 청크(Chunk) 단위로 나눠서 각 청크마다 개별 트랜잭션을 처리한다.
[Reader → Processor → Writer] x N번 반복 (Chunk 단위)
@Bean
public Step chunkStep() {
return stepBuilderFactory.get("chunkStep")
.<String, String>chunk(10) // 청크 사이즈: 한 트랜잭션에 10개씩 처리
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
Chunk Size와 Page Size
JPA Paging Reader를 쓸 때 자주 나오는 개념이 Chunk Size와 Page Size다.
- Page Size — 한 번의 쿼리로 가져오는 데이터 수
- Chunk Size — 한 번의 트랜잭션으로 커밋하는 데이터 수
만약 Page Size가 5이고 Chunk Size가 10이라면, 1번의 트랜잭션을 위해 쿼리가 2번 실행된다. 이는 비효율적이므로 Page Size와 Chunk Size는 동일하게 맞추는 것을 권장한다.
또한 페이징 처리 시에는 데이터 순서를 보장하기 위해 반드시 ORDER BY를 사용해야 한다.
Step 실행 흐름 제어
단순 순차 실행
@Bean
public Job exampleJob() {
return jobBuilderFactory.get("exampleJob")
.start(step1())
.next(step2())
.next(step3())
.build();
}
조건부 분기 (Flow)
이전 Step의 ExitStatus에 따라 다음 Step을 분기할 수 있다.
@Bean
public Job flowJob() {
return jobBuilderFactory.get("flowJob")
.start(startStep())
.on(ExitStatus.FAILED.getExitCode())
.to(failOverStep())
.on("*").to(writeStep()).end()
.from(startStep())
.on(ExitStatus.COMPLETED.getExitCode())
.to(processStep())
.on("*").to(writeStep()).end()
.end()
.build();
}
on()은 ExitStatus를 기준으로 분기를 결정한다. RepeatStatus가 아니라 ExitStatus를 본다는 점을 주의해야 한다.
JPA 환경에서 Chunk 방식 예시
재고가 5 이하인 책을 읽어서 재주문을 생성하는 배치 Job이다.
@Configuration
@EnableBatchProcessing
public class BookOrderJobConfig {
// Reader: 재고 5 이하인 Book 조회
@Bean
@StepScope
public JpaPagingItemReader<Book> bookReader() throws Exception {
Map<String, Object> params = new HashMap<>();
params.put("stock", 5);
return new JpaPagingItemReaderBuilder<Book>()
.name("bookReader")
.entityManagerFactory(entityManagerFactory)
.queryString("SELECT b FROM Book b WHERE b.stock <= :stock ORDER BY b.id ASC")
.parameterValues(params)
.pageSize(10)
.build();
}
// Processor: Book → Order 변환
@Bean
@StepScope
public ItemProcessor<Book, Order> bookOrderProcessor() {
return book -> {
Order order = new Order();
order.setBook(book);
order.setQuantity(10);
return order;
};
}
// Writer: Order를 DB에 저장
@Bean
@StepScope
public JpaItemWriter<Order> orderWriter() {
return new JpaItemWriterBuilder<Order>()
.entityManagerFactory(entityManagerFactory)
.build();
}
// Step 구성
@Bean
@StepScope
public Step bookOrderStep() throws Exception {
return stepBuilderFactory.get("bookOrderStep")
.<Book, Order>chunk(10)
.reader(bookReader())
.processor(bookOrderProcessor())
.writer(orderWriter())
.build();
}
// Job 구성
@Bean
@JobScope
public Job bookOrderJob() throws Exception {
return jobBuilderFactory.get("bookOrderJob")
.start(bookOrderStep())
.build();
}
}
ItemReader 주요 구현체
Spring Batch는 다양한 데이터 소스에 맞는 ItemReader 구현체를 제공한다.
구현체 설명
| JdbcCursorItemReader | JDBC 커서 방식으로 DB 조회 |
| JdbcPagingItemReader | JDBC 페이징 방식으로 DB 조회 |
| JpaPagingItemReader | JPA 페이징 방식으로 DB 조회 |
| FlatFileItemReader | CSV, TXT 같은 파일 읽기 |
| StaxEventItemReader | XML 파일 읽기 |
| ListItemReader | 메모리 내 List 읽기 (테스트용) |
Cursor vs Paging
DB에서 읽어올 때 Cursor 방식과 Paging 방식 중 하나를 선택할 수 있다.
방식 특징
| Cursor | 하나의 Connection으로 커서를 이동하며 순차 읽기. 대량 데이터에서 성능 유리 |
| Paging | 쿼리를 페이지 단위로 반복 실행. 멀티 스레드 환경에서 안전 |
멀티 스레드 Step에서는 Cursor 방식이 thread-safe하지 않으므로 Paging 방식을 사용해야 한다.
ItemWriter 주요 구현체
구현체 설명
| JdbcBatchItemWriter | JDBC 배치로 DB 저장 |
| JpaItemWriter | JPA로 DB 저장 (merge() 방식) |
| FlatFileItemWriter | 파일로 출력 |
| CompositeItemWriter | 여러 Writer를 조합해서 사용 |
정리
Spring Batch의 핵심 구조를 한 줄로 정리하면 이렇다.
Job → Step(s) → [Tasklet] or [Reader → Processor → Writer]
- 대용량 처리가 필요하면 Chunk 방식
- 단순 단일 작업은 Tasklet 방식
- Page Size와 Chunk Size는 동일하게 설정
- 페이징 쿼리에는 ORDER BY 필수
배치 실행 이력은 메타 테이블에 자동으로 쌓이므로, 실패했을 때 어디서 멈췄는지 추적하고 재시작할 수 있다는 것도 Spring Batch의 큰 장점이다.
'Java & Spring > Spring' 카테고리의 다른 글
| OSIV · 낙관적 락 · 비관적 락 (0) | 2026.05.09 |
|---|---|
| 페이지네이션의 원리? 필요성? (0) | 2026.04.10 |
| [MapStruct] 내부 구현 방식 - 설계 문제 해결 (0) | 2026.04.09 |
| Spring 프록시 원리 (0) | 2026.04.03 |
| MapStruct (0) | 2026.03.22 |