안녕하세요.

배치 애플리케이션을 운영하다 보면 누구나 한 번쯤은 “실패”를 마주하게 됩니다. 대량의 데이터를 처리하다 보면 네트워크 장애, DB 커넥션 타임아웃, 예기치 못한 데이터 형식의 오류 등 수많은 변수가 배치를 중단시킵니다. 수백만 건의 데이터 중 90%를 처리했는데 마지막 10%에서 에러가 발생했다면, 우리는 다시 처음부터 모든 작업을 반복해야 할까요? 아니면 실패한 바로 그 지점부터 똑똑하게 다시 시작할 수 있을까요?
Spring Batch의 가장 강력한 특징 중 하나는 바로 ‘회복 탄력성(Resilience)’입니다. Spring Batch는 배치의 실행 이력을 데이터베이스에 꼼꼼하게 기록하며, 이를 바탕으로 실패한 작업을 중단된 지점부터 정확히 재개할 수 있는 매커니즘을 내장하고 있습니다. 하지만 이 재시작 기능을 제대로 활용하기 위해서는 Spring Batch의 내부 메타 데이터 구조와 JobInstance, JobExecution의 관계를 완벽히 이해해야 합니다.
오늘은 실패한 배치를 재시작하는 구체적인 방법과 그 뒤에서 일어나는 프레임워크의 동작 원리, 그리고 실무에서 재시작을 고려한 배치 설계 시 반드시 지켜야 할 원칙들을 7,000자 이상의 방대한 분석으로 정리해 드립니다.
1. 재시작의 근간: JobInstance와 JobExecution의 관계
재시작을 이해하기 위한 첫 번째 열쇠는 JobInstance와 JobExecution의 차이입니다. 이전 포스팅에서도 강조했듯이, JobInstance는 “어느 날짜의 정산 작업”이라는 논리적인 작업 단위입니다. 반면 JobExecution은 그 작업을 실제로 “실행하려는 시도”입니다.
만약 ‘2023-10-27’ 일자 정산 JobInstance가 첫 번째 시도(JobExecution)에서 실패했다면, 메타 테이블인 BATCH_JOB_EXECUTION에는 해당 작업의 상태가 FAILED로 기록됩니다. 이후 우리가 동일한 파라미터로 배치를 다시 실행하면, Spring Batch는 기존에 실패한 JobInstance가 있음을 확인하고, 새로운 JobExecution을 생성하여 이전의 실패 기록을 바탕으로 작업을 이어갑니다. 만약 상태가 COMPLETED라면 재시작을 거부하겠지만, FAILED라면 구원투수처럼 나타나 나머지 작업을 완수하는 것이죠.
2. Spring Batch는 어떻게 중단 지점을 기억하는가?
Spring Batch가 “어디서부터 다시 시작해야 할지”를 아는 비밀은 바로 ExecutionContext에 있습니다.
Chunk 지향 처리 과정에서 Spring Batch는 주기적으로(Chunk 단위 커밋 시점마다) 현재까지 읽은 데이터의 위치(예: 마지막으로 읽은 데이터의 ID나 페이징 번호)를 BATCH_STEP_EXECUTION_CONTEXT 테이블에 저장합니다. 배치가 재시작될 때, ItemReader는 이 컨텍스트 정보를 읽어와서 자신이 마지막으로 어디까지 읽었는지 파악하고, 그 다음 줄부터 데이터를 읽기 시작합니다.
이것이 가능하려면 우리가 사용하는 ItemReader가 ‘상태를 저장할 수 있는(Stateful)’ 리더여야 합니다. Spring Batch가 제공하는 대부분의 표준 리더들(JdbcPagingItemReader, FlatFileItemReader 등)은 이 기능을 내장하고 있습니다. 만약 직접 커스텀 리더를 만든다면 ItemStream 인터페이스를 구현하여 이 상태 저장 로직을 직접 넣어주어야 재시작 기능을 100% 활용할 수 있습니다.
3. 재시작을 위한 실무적인 방법들
실패한 배치를 다시 돌리는 방법은 크게 세 가지로 나뉩니다.
첫째, 동일한 파라미터로 다시 실행하기입니다. 가장 기본적인 방법입니다. 스케줄러(Jenkins 등)나 커맨드 라인에서 실패했을 때와 토씨 하나 틀리지 않은 동일한 파라미터를 인자로 주어 실행하면, Spring Batch가 알아서 재시작 모드로 동작합니다.
둘째, JobOperator 사용하기입니다. 운영 환경에서 관리자 페이지(Admin UI)를 구축했다면 JobOperator.restart(executionId) 메서드를 통해 특정 실행 ID를 기반으로 명시적인 재시작을 명령할 수 있습니다. 이는 파라미터를 일일이 입력할 필요 없이 클릭 한 번으로 장애를 복구할 수 있게 해줍니다.
셋째, allow-start-if-complete 설정 활용입니다. 기본적으로 성공한 Step은 재시작 시 건너뜁니다. 하지만 어떤 Step은 성공 여부와 상관없이 매 실행마다 반드시 다시 돌아야 할 때가 있습니다(예: 임시 테이블 초기화). 이럴 때 Step 설정에서 allowStartIfComplete(true)를 주면, 이미 성공했더라도 다시 실행되도록 강제할 수 있습니다.
4. 재시작 설계 시 주의사항: 멱등성(Idempotency)
재시작 기능이 강력하긴 하지만, 개발자가 아무 생각 없이 코드를 짜면 심각한 데이터 오류를 낳을 수 있습니다. 여기서 가장 중요한 개념이 바로 멱등성입니다. 멱등성이란 “연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질”을 말합니다.
예를 들어, 포인트를 1,000점 지급하는 배치가 있다고 합시다. 100명 중 50명에게 포인트를 주고 배치가 죽었습니다. 재시작했을 때, 앞서 포인트를 이미 받은 50명에게 또 1,000점을 주면 안 됩니다. 중복 지급이 발생하기 때문입니다. 이를 방지하려면 배치가 “이미 포인트가 지급된 사용자인지”를 체크하는 로직을 갖추거나, update 문을 통해 상태를 변경하는 방식처럼 여러 번 실행해도 최종 상태가 동일하도록 설계해야 합니다. 재시작 기능은 편리하지만, 그 이면에는 이러한 정교한 데이터 설계가 뒷받침되어야 합니다.
5. 예제 코드 분석
실패 복구를 위한 설정과 수동 재시작 로직을 코드로 살펴보겠습니다.
코드 1: Step 재시작 허용 설정
성공 여부와 상관없이 항상 실행되어야 하는 Step에 대한 설정입니다.
@Bean
public Step prepareStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("prepareStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
log.info(">>>>> 전처리 작업을 수행합니다. (항상 실행)");
return RepeatStatus.FINISHED;
}, transactionManager)
.allowStartIfComplete(true) // 이전 실행이 성공했더라도 재시작 시 다시 실행!
.build();
}
코드 2: JobOperator를 이용한 수동 재시작 요청
API를 통해 특정 실패 건을 재시작시키는 컨트롤러 예시입니다.
@RestController
@RequiredArgsConstructor
public class BatchRestartController {
private final JobOperator jobOperator;
@PostMapping("/batch/restart/{executionId}")
public String restartJob(@PathVariable Long executionId) throws Exception {
// 실패한 executionId를 전달받아 재시작 수행
// 내부적으로 기존 파라미터를 그대로 사용하여 재시작함
Long newExecutionId = jobOperator.restart(executionId);
return "New Job Execution Started! ID: " + newExecutionId;
}
}

6. 결론
Spring Batch의 재시작 기능은 장애 상황에서 개발자와 운영자의 밤잠을 지켜주는 고마운 기능입니다. 하지만 이 기능은 공짜로 얻어지는 것이 아닙니다. 프레임워크가 제공하는 메타 데이터 관리 매커니즘을 존중하고, 데이터 처리 과정에서 멱등성을 확보하려는 노력이 동반되어야 합니다.
“실패는 피할 수 없지만, 복구는 선택할 수 있습니다.”
잘 설계된 재시작 전략은 단순한 기능 구현 이상의 가치를 가집니다. 그것은 바로 시스템에 대한 ‘신뢰’입니다. 어떤 풍파에도 굴하지 않고 맡은 바 임무를 완수하는 견고한 배치 시스템을 여러분의 손으로 직접 만들어 보시기 바랍니다.
이것으로 Spring Batch의 동적 파라미터 활용과 실패 복구 전략에 대한 심화 가이드를 마칩니다. 여러분의 배치 개발 여정에 이 지식이 든든한 등불이 되기를 바랍니다. 감사합니다.






