안녕하세요.
단순한 배치 작업은 하나의 Step으로 끝나지만, 실무의 복잡한 비즈니스는 대개 여러 단계의 과정을 거칩니다. 예를 들어 ‘데이터 정리 -> 주 데이터 처리 -> 결과 리포트 생성 -> 임시 파일 삭제’와 같은 흐름이죠.
오늘은 Spring Batch에서 여러 개의 Step을 유기적으로 연결하고, 관리 효율을 높이는 다중 Step 구성 전략을 상세히 알아보겠습니다.
1. 순차적 Step 실행 (Sequential Steps)
가장 기본이 되는 방식입니다. next() 메서드를 사용하여 Step을 기차 놀이처럼 연결합니다.

위 도식처럼 앞선 Step이 성공적으로 완료되어야만 다음 Step으로 넘어갑니다. 만약 중간에 에러가 발생하면 배치는 그 지점에서 중단됩니다.
코드 예제 1: 단순 순차 연결
@Bean
public Job sequentialJob() {
return new JobBuilder("sequentialJob", jobRepository)
.start(step1()) // 1단계: 기초 데이터 검증
.next(step2()) // 2단계: 메인 비즈니스 로직 (Chunk)
.next(step3()) // 3단계: 알림 전송 (Tasklet)
.build();
}
2. Step의 그룹화: Flow 활용하기
Step이 많아지면 Job 설정 코드가 너무 길어지고 복잡해집니다. 이때 관련된 Step들을 하나의 Flow로 묶어서 관리하면 재사용성과 가독성이 높아집니다.
코드 예제 2: Flow를 활용한 구조화
@Bean
public Job flowJob() {
return new JobBuilder("flowJob", jobRepository)
.start(mainProcessingFlow()) // Flow로 시작
.next(cleanupStep()) // 마지막 정리 작업
.end()
.build();
}
@Bean
public Flow mainProcessingFlow() {
return new FlowBuilder<SimpleFlow>("mainProcessingFlow")
.start(step1())
.next(step2())
.build();
}
이렇게 하면 mainProcessingFlow를 다른 Job에서도 가져다 쓸 수 있는 모듈화가 가능해집니다.
3. 병렬 Step 실행 (Split)
모든 Step이 순차적일 필요는 없습니다. 서로 의존성이 없는 작업(예: ‘A 업체 데이터 읽기’와 ‘B 업체 데이터 읽기’)은 동시에 실행하여 전체 배치 시간을 단축할 수 있습니다.

코드 예제 3: Split을 이용한 병렬 처리
@Bean
public Job parallelJob() {
Flow flow1 = new FlowBuilder<SimpleFlow>("flow1").start(stepA()).build();
Flow flow2 = new FlowBuilder<SimpleFlow>("flow2").start(stepB()).build();
return new JobBuilder("parallelJob", jobRepository)
.start(flow1)
.split(new SimpleAsyncTaskExecutor()) // 병렬 실행을 위한 Executor 설정
.add(flow2)
.end()
.build();
}
4. 실무 적용 팁
- 상태 관리: 각 Step의 성공/실패 여부는
BATCH_STEP_EXECUTION테이블에 상세히 기록됩니다. 여러 Step을 쓸수록 이 메타 데이터를 통해 장애 지점을 찾기가 수월해집니다. - 데이터 공유: Step 간에 데이터를 공유해야 한다면
ExecutionContext를 사용하세요. Step 1에서 계산한 값을 Step 2에서 참조할 수 있습니다. - 멱등성: 여러 Step 중 중간에 실패하여 재시작할 때, 이미 성공한 Step은 건너뛰고 실패한 Step부터 다시 시작하는 것이 Spring Batch의 기본 동작입니다. 따라서 각 Step은 다시 실행되어도 안전하도록(멱등성) 설계해야 합니다.
5. 결론
여러 Step을 구성하는 것은 단순히 코드를 쪼개는 것이 아니라, 비즈니스 프로세스를 논리적으로 설계하는 과정입니다.
- 단순한 흐름은
next() - 복잡한 로직은
Flow - 독립적인 대량 작업은
split()
이 세 가지만 적절히 조합해도 현업의 웬만한 복잡한 배치는 모두 구현할 수 있습니다. 다음 시간에는 여기서 한 발 더 나아가, 상황에 따라 길을 선택하는 조건부 분기 처리를 다뤄보겠습니다.






