Contents
see List개요
Spring Batch 5.x는 Spring Framework 6 기반으로 재구축되며 Java 17 이상을 요구합니다. Builder 패턴 중심의 새로운 설정 방식, 개선된 Chunk 처리 모델, 그리고 향상된 파티셔닝 기능을 제공합니다. 대용량 데이터의 ETL(Extract-Transform-Load), 일괄 처리, 리포트 생성 등에서 검증된 프레임워크로, 수백만에서 수억 건의 데이터를 안정적으로 처리하는 패턴을 살펴봅니다.
핵심 개념
Job은 배치 처리의 최상위 단위이며, 하나 이상의 Step으로 구성됩니다. Step은 실제 데이터 처리가 이루어지는 단위로, Chunk 기반 또는 Tasklet 기반으로 구현됩니다.
Chunk 기반 처리는 ItemReader → ItemProcessor → ItemWriter의 3단계로 데이터를 처리합니다. 설정된 chunk-size 만큼 읽고, 변환하고, 한꺼번에 기록합니다. 트랜잭션은 chunk 단위로 관리됩니다.
파티셔닝(Partitioning)은 대용량 데이터를 여러 파티션으로 나누어 병렬 처리합니다. 각 파티션은 독립적인 Step 인스턴스로 실행되어 선형적인 성능 향상을 기대할 수 있습니다.
재시작과 스킵은 Spring Batch의 핵심 안정성 기능입니다. 실패한 Job을 마지막 체크포인트부터 재시작하고, 개별 레코드 오류를 스킵하여 전체 배치가 중단되지 않도록 합니다.
실전 예제
Spring Batch 5.x 스타일의 Job 설정입니다.
@Configuration
@RequiredArgsConstructor
public class OrderBatchConfig {
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
private final DataSource dataSource;
@Bean
public Job orderProcessingJob() {
return new JobBuilder("orderProcessingJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(readOrderStep())
.next(aggregateStep())
.next(reportStep())
.listener(jobCompletionListener())
.build();
}
@Bean
public Step readOrderStep() {
return new StepBuilder("readOrderStep", jobRepository)
.<OrderEntity, OrderDto>chunk(500, transactionManager)
.reader(orderReader())
.processor(orderProcessor())
.writer(orderWriter())
.faultTolerant()
.skipLimit(100)
.skip(DataIntegrityViolationException.class)
.retryLimit(3)
.retry(DeadlockLoserDataAccessException.class)
.listener(chunkListener())
.build();
}
}
JdbcPagingItemReader로 대용량 데이터를 효율적으로 읽습니다.
@Bean
public JdbcPagingItemReader<OrderEntity> orderReader() {
Map<String, Order> sortKeys = new LinkedHashMap<>();
sortKeys.put("id", Order.ASCENDING);
return new JdbcPagingItemReaderBuilder<OrderEntity>()
.name("orderReader")
.dataSource(dataSource)
.selectClause("SELECT id, customer_id, amount, status, created_at")
.fromClause("FROM orders")
.whereClause("WHERE status = 'PENDING' AND created_at >= :startDate")
.sortKeys(sortKeys)
.parameterValues(Map.of("startDate", LocalDate.now().minusDays(7)))
.pageSize(500)
.rowMapper(new BeanPropertyRowMapper<>(OrderEntity.class))
.build();
}
파티셔닝을 활용한 병렬 처리입니다.
@Bean
public Step partitionedStep() {
return new StepBuilder("partitionedStep", jobRepository)
.partitioner("workerStep", rangePartitioner())
.step(workerStep())
.gridSize(8) // 8개 파티션으로 분할
.taskExecutor(batchTaskExecutor())
.build();
}
@Bean
public Partitioner rangePartitioner() {
return gridSize -> {
Long min = jdbcTemplate.queryForObject(
"SELECT MIN(id) FROM orders WHERE status = 'PENDING'", Long.class);
Long max = jdbcTemplate.queryForObject(
"SELECT MAX(id) FROM orders WHERE status = 'PENDING'", Long.class);
long range = (max - min) / gridSize + 1;
Map<String, ExecutionContext> partitions = new HashMap<>();
for (int i = 0; i < gridSize; i++) {
ExecutionContext context = new ExecutionContext();
context.putLong("minId", min + (range * i));
context.putLong("maxId", min + (range * (i + 1)) - 1);
partitions.put("partition" + i, context);
}
return partitions;
};
}
@Bean
public TaskExecutor batchTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(0);
executor.setThreadNamePrefix("batch-");
return executor;
}
활용 팁
- chunk-size 튜닝: 일반적으로 500~1000이 적합합니다. 너무 작으면 트랜잭션 오버헤드가, 너무 크면 메모리와 롤백 범위가 커집니다.
- JdbcPagingItemReader vs JdbcCursorItemReader: 대용량에서는 PagingReader가 메모리 효율적이고, 커서 리더는 중간 크기 데이터에서 더 빠릅니다.
- CompositeItemWriter: 하나의 Step에서 여러 대상(DB, 파일, API)에 동시에 기록해야 할 때 사용합니다.
- Job 스케줄링: Spring Scheduler 또는 Jenkins, Airflow와 연동하여 정기 실행합니다.
- 모니터링: Spring Batch의 메타 테이블(BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION 등)을 조회하여 실행 이력과 실패 원인을 추적합니다.
마무리
Spring Batch 5.x는 대용량 데이터 처리의 복잡성을 프레임워크 레벨에서 해결합니다. Chunk 기반 처리로 메모리를 효율적으로 관리하고, 파티셔닝으로 병렬 처리 성능을 극대화하며, 재시작/스킵 정책으로 안정성을 보장합니다. 데이터 파이프라인, 정산 시스템, 리포트 생성 등 대용량 일괄 처리가 필요한 곳에서 Spring Batch는 여전히 최고의 선택입니다.