Spring Batch는 단순히 배치를 실행하는 프레임워크가 아니다.
운영 환경에서는 배치가 실패하더라도,
그 실패 상태를 정확히 기록하고 복구 가능한 형태로 남기는 것이 핵심이다.
그 중심에는 두 개의 개념이 있다.
JobExecution 과 StepExecution.
이 둘의 관계를 정확히 이해해야만
운영 배치를 제대로 복구하고,
“재처리 가능한 시스템”을 설계할 수 있다.
1. JobExecution — 배치 실행의 단위
JobExecution은 하나의 Job이 실행될 때마다 생성되는 실행 인스턴스다.
즉, Job 이름이 같더라도 실행될 때마다 새로운 Execution이 생긴다.
| 필드 | 설명 |
|---|---|
| JOB_EXECUTION_ID | 실행 고유 ID |
| JOB_INSTANCE_ID | Job 정의의 인스턴스 |
| STATUS | 실행 상태 (STARTED, COMPLETED, FAILED 등) |
| START_TIME / END_TIME | 실행 시간 |
| EXIT_CODE | 종료 코드 |
| EXIT_MESSAGE | 상세 종료 메시지 |
예를 들어 하루 한 번 돌아가는 DailyReportJob이라면,
2025-10-16, 2025-10-17 각각의 실행에 대해 JobExecution이 1개씩 생긴다.
2. StepExecution — JobExecution의 하위 실행 단위
Job은 여러 Step으로 구성되고,
각 Step은 별도의 실행 이력(StepExecution)으로 관리된다.
| 필드 | 설명 |
|---|---|
| STEP_EXECUTION_ID | 실행 고유 ID |
| JOB_EXECUTION_ID | 상위 JobExecution ID |
| STATUS | Step 상태 |
| READ_COUNT / WRITE_COUNT / COMMIT_COUNT | 처리 건수 |
| EXIT_MESSAGE | 오류 메시지 및 상세 내용 |
즉, JobExecution은 “전체 배치 실행”,
StepExecution은 “세부 단계 실행 이력”이라고 보면 된다.
3. 실패한 Job을 다시 실행할 때 일어나는 일
Spring Batch는 내부적으로 JobExecution의 상태를 조회해
“이 Job이 이미 성공한 적이 있는지”를 확인한다.
- 성공 상태(
COMPLETED) → 새 Execution 생성 - 실패 상태(
FAILED) → 재시도 시 이전 Execution을 참조
💡 주의:
이전 Execution을 완전히 덮어쓰지 않는다.
새로운 JobExecution을 만들되,
이전 Execution의 JobParameters와 Step 상태를 복제한다.
4. 재실행 시 Step 상태 관리 구조
Spring Batch는 StepExecution을 통해
각 Step의 성공/실패를 구분해 저장한다.
예시:
| Step Name | Status | Read Count | Write Count |
|---|---|---|---|
| Step1 | COMPLETED | 1000 | 1000 |
| Step2 | FAILED | 500 | 499 |
→ Step2에서 실패 시,
다음 실행(JobExecution)은 Step1을 건너뛰고 Step2부터 재시작할 수 있다.
이 기능이 바로 Restartable Job이다.
설정 예시:
@Bean
public Job dailyBatch(JobBuilderFactory jobs, Step step1, Step step2) {
return jobs.get("dailyBatch")
.start(step1)
.next(step2)
.preventRestart(false) // 재시작 허용
.build();
}
5. 실패 Job 복구 흐름 요약
1️⃣ 운영자가 실패 Job을 확인
2️⃣ JobExecution 상태 확인 (FAILED)
3️⃣ 동일 JobParameters로 재실행 요청
4️⃣ 이미 성공한 Step은 SKIP, 실패 Step부터 재처리
5️⃣ 새로운 JobExecution 생성 후 StepExecution 복구
📜 SQL 레벨 예시:
SELECT JOB_NAME, STATUS, START_TIME, END_TIME
FROM BATCH_JOB_EXECUTION
WHERE JOB_NAME = 'DailyReportJob'
ORDER BY JOB_EXECUTION_ID DESC;
→ 가장 최근 실패 Execution ID 확인 후
관리자 툴(Spring Batch Admin 등)에서 재실행
6. 커스텀 리커버리 설계 (심화)
운영 환경에서는 단순한 restart 기능만으로 부족하다.
다음과 같은 확장 설계를 권장한다.
(1) 실패 Step 재시도 제한
.step("processStep")
.tasklet(tasklet)
.startLimit(3) // 최대 3회까지만 재시도
.build();
(2) 실패 Step의 상세 로그 남기기
StepExecutionListener.afterStep() 에서 실패 원인 기록
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (stepExecution.getStatus() == BatchStatus.FAILED) {
log.error("Step 실패: {}", stepExecution.getExitStatus().getExitDescription());
}
return stepExecution.getExitStatus();
}
(3) 외부 Job 관리 테이블 연동
- 실패 이력 별도 기록 (
TB_JOB_ERROR_LOG) - 운영자가 관리 화면에서 선택 후 부분 재처리 실행 가능
7. Job과 Step Execution의 시각적 모니터링
운영 모니터링 시스템(ELK, Grafana, Jenkins, or 내부 배치관리 툴)과 연계하면
실패 지점을 한눈에 파악할 수 있다.
- JobExecution 기준 집계 그래프
- StepExecution 처리율 (%)
- 실패 Step 로그 추적
💡 ELK에서 로그 필터링 시jobExecutionId, stepName, exitCode 필드를 key로 두면
시각화가 훨씬 깔끔하다.
8. 리커버리 설계의 핵심 — “중단된 지점에서의 연속성”
배치 복구의 진짜 목표는 “처음부터 다시 돌리는 것”이 아니다.
정상적으로 완료된 Step은 건드리지 않고,
실패한 Step만 안전하게 재처리하는 것.
그게 가능하려면
- JobExecution / StepExecution 이력 설계가 일관성 있게 유지되고,
- JobParameters가 매 실행마다 고유하게 관리돼야 한다.
마치며
운영 배치의 복구는 단순한 재시작이 아니라,
시스템 신뢰도를 지키는 설계 행위다.
JobExecution과 StepExecution을 이해하지 못한 채
운영 Job을 “다시 돌린다”는 건
복구가 아니라 재앙에 가깝다.
진짜 리커버리는 실패 이후에도
시스템이 ‘어디까지 일했는지’를 기억하게 만드는 일이다.