Spring Batch 리커버리 구조 설계 — JobExecution과 StepExecution의 관계

Spring Batch는 단순히 배치를 실행하는 프레임워크가 아니다.
운영 환경에서는 배치가 실패하더라도,
실패 상태를 정확히 기록하고 복구 가능한 형태로 남기는 것이 핵심이다.

그 중심에는 두 개의 개념이 있다.
JobExecutionStepExecution.

이 둘의 관계를 정확히 이해해야만
운영 배치를 제대로 복구하고,
“재처리 가능한 시스템”을 설계할 수 있다.


1. JobExecution — 배치 실행의 단위

JobExecution은 하나의 Job이 실행될 때마다 생성되는 실행 인스턴스다.
즉, Job 이름이 같더라도 실행될 때마다 새로운 Execution이 생긴다.

필드설명
JOB_EXECUTION_ID실행 고유 ID
JOB_INSTANCE_IDJob 정의의 인스턴스
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
STATUSStep 상태
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 NameStatusRead CountWrite Count
Step1COMPLETED10001000
Step2FAILED500499

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을 “다시 돌린다”는 건
복구가 아니라 재앙에 가깝다.

진짜 리커버리는 실패 이후에도
시스템이 ‘어디까지 일했는지’를 기억하게 만드는 일이다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다