| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- Spring Batch
- 프로그래머스
- 배치
- 스프링 배치
- 자바
- java
- DB
- 스케줄러
- Til
- spring boot
- 김영한
- 백엔드
- redis
- 이펙티브 자바
- 코드카타
- 템플릿 메서드 패턴
- GoF 23
- Spring
- 스프링
- Effective Java
- 트러블슈팅
- 계산기
- lv1
- 추상클래스
- 프록시 패턴
- 로드밸런서
- 성능 개선
- 디자인 패턴
- 빌더 패턴
- 토스
- Today
- Total
김코딩
[Spring Batch] 2편. Job, JobInstance, JobExecution의 관계 본문
들어가며
1편에서 Spring Batch가 왜 필요한지 알아보았다. 그중 "실패 시 어디까지 처리했는지 기억하고 재시작할 수 있다"는 장점이 있었는데, 이게 어떻게 가능한지 내부를 들여다보면 Job, JobInstance, JobExecution이라는 세 가지 개념이 등장한다.
이 세 용어는 이름이 비슷해서 처음엔 굉장히 헷갈린다. 하지만 한 번 제대로 정리해두면 Spring Batch의 재시작 메커니즘, 중복 실행 방지, 메타데이터 관리가 전부 한 줄기로 꿰어진다.
이 글에서는 내가 학습하면서 가장 헷갈렸던 이 세 개념을 정리한다.
Job이 곧 JobInstance일까?
처음 공식 문서를 봤을 때 가장 먼저 든 생각은 이거였다.
"Job과 JobInstance가 이름도 비슷한데, 같은 거 아닌가?"
결론부터 말하면 다르다. 그리고 이 둘의 관계는 Java의 클래스-객체 관계와 거의 동일하다.

// Java에서 클래스는 설계도, 객체는 그 설계도에서 찍어낸 실체
class Member { ... }
Member m1 = new Member("철수", 28); // 객체 1
Member m2 = new Member("영희", 27); // 객체 2
Spring Batch도 똑같다. Job은 코드로 정의하는 "설계도"이고, 실행할 때마다 JobParameters와 조합되어 "JobInstance"라는 실체가 만들어진다.
A Job defines what a job is and how it is to be executed, and a JobInstance is a purely organizational object to group executions together, primarily to enable correct restart semantics.
— Spring Batch Reference: The Domain Language of Batch
💭 나의 생각
이 비유를 스스로 찾아냈을 때 막혔던 퍼즐이 풀리는 느낌이었다. 공식 문서에서도 "Job과 JobInstance는 다르다"라고는 명확히 나와 있지만, 왜 나눴는지에 대한 직관적인 설명은 부족했다. Java 개발자에게는 클래스-객체 비유가 가장 빠른 이해 경로인 것 같다.
JobInstance의 유일성은 어떻게 결정될까
Job이 클래스고 JobInstance가 객체라면, 하나의 Job으로 여러 JobInstance를 만들 수 있다는 뜻이다. 그렇다면 무엇이 각 JobInstance를 구분 짓는가?
답은 JobParameters다. 공식 문서의 표현을 빌리면:
Thus, the contract can be defined as: JobInstance = Job + identifying JobParameters.
— Spring Batch Reference: The Domain Language of Batch

예를 들어 dailySettlementJob이라는 Job을 매일 다른 날짜 파라미터로 실행하면, 매일 다른 JobInstance가 생성된다.
// 4/18 실행 - 4/17 데이터 정산
JobParameters params1 = new JobParametersBuilder()
.addString("date", "2026-04-17")
.toJobParameters();
// 4/19 실행 - 4/18 데이터 정산
JobParameters params2 = new JobParametersBuilder()
.addString("date", "2026-04-18")
.toJobParameters();
Job 이름은 dailySettlementJob으로 동일하지만 date 파라미터 값이 다르므로, 두 실행은 서로 다른 JobInstance로 취급된다.
💭 나의 생각
"파라미터 이름"이 아니라 "파라미터 값"이 유일성을 결정한다는 점이 중요하다. 두 번 모두 date라는 동일한 키를 쓰지만, 값(2026-04-17 vs 2026-04-18)이 다르기 때문에 다른 JobInstance가 된다. 처음에는 이걸 잘못 이해해서 "같은 파라미터 이름을 쓰면 같은 Instance인가?" 라고 생각했다.
JobExecution은 또 뭘까
JobInstance가 "해야 할 작업 1건"이라면, JobExecution은 그 작업을 실제로 돌린 시도다.
A JobExecution refers to the technical concept of a single attempt to run a Job.
— Spring Batch Reference: The Domain Language of Batch
왜 이렇게 둘로 나눴을까? 바로 재시작 때문이다.
실무에서 배치는 실패할 수 있다. 네트워크가 끊기거나, DB가 순간 포화되거나, 예상하지 못한 데이터가 들어올 수 있다. 그런 일이 일어났을 때 "어떤 작업의 몇 번째 시도였는지"를 추적할 구조가 필요하다.

위 그림을 보면 이해가 쉽다:
- Job:
dailySettlementJob하나 - JobInstance: 날짜별로 3개 (4/17, 4/18, 4/19)
- JobExecution:
- Instance A (4/17): 첫 시도에 성공 → Execution 1개
- Instance B (4/18): 첫 시도 실패, 두 번째 시도에 성공 → Execution 2개
- Instance C (4/19): 첫 시도에 성공 → Execution 1개
중요한 건 Instance B의 경우, 같은 JobInstance 아래에 두 개의 JobExecution이 쌓였다는 점이다. 첫 실패가 기록으로 남아있기 때문에 두 번째 시도에서 "30만 건 처리 완료된 상태였지" 하고 이어서 갈 수 있는 것이다.
💭 나의 생각
이 구조는 장애 복구 관점에서 굉장히 잘 설계되어 있다. 운영 중 장애가 나면 실패한 Execution의 이력을 보고 원인을 파악하고, 같은 Instance로 재실행하면 이어서 처리된다. 결제나 정산처럼 절대 중복 처리되면 안 되는 도메인에서 Spring Batch를 쓰는 이유가 여기에 있다.
같은 파라미터로 재실행하면 어떻게 될까
위 그림에서 Instance B가 "실패 → 재시도"로 Execution 2개를 가진 것을 보면, 이런 의문이 생긴다.
"그럼 이미 성공한 Instance A(4/17)를 실수로 또 실행하면 Execution이 2개 생기는 건가?"
답은 아니다. Spring Batch는 이런 경우 아예 실행 자체를 차단한다.

규칙을 정리하면:
| 이전 실행 상태 | 재실행 시도 결과 |
| COMPLETED (성공) | JobInstanceAlreadyCompleteException 발생, 실행 차단 |
| FAILED (실패) | 같은 JobInstance에 새 JobExecution 생성, 이어서 실행 |
공식 문서의 표현은 다음과 같다.
If it is run again with the same identifying job parameters as the first run, a new JobExecution is created. However, there is still only one JobInstance.
— Spring Batch Reference: The Domain Language of Batch
(위 문장은 실패 후 재실행 시나리오에 대한 설명이다. 성공한 Instance를 재실행하려 하면 예외가 발생한다.)
실무에서 만날 수 있는 상황
이 규칙이 왜 중요한지 실무 관점에서 생각해보자.
새벽에 정산 배치가 성공적으로 끝났는데, 코드에 버그가 있어서 정산 금액이 잘못 계산됐다는 게 오전에 발견됐다. CTO가 "어제 날짜로 다시 정산 돌려!"라고 지시한다. 같은 파라미터로 재실행 버튼을 누르면?
답: JobInstanceAlreadyCompleteException이 발생하며 실행이 차단된다.
이럴 때는 파라미터에 식별자를 추가하여 새로운 JobInstance로 만들거나, 별도의 보정 Job을 작성해서 실행하는 방식으로 해결한다.
// 방법 1: 파라미터에 식별자 추가
JobParameters params = new JobParametersBuilder()
.addString("date", "2026-04-17")
.addString("reason", "bugfix-2026-04-18") // 새 Instance가 됨
.toJobParameters();
💭 나의 생각
처음엔 "왜 성공한 걸 다시 못 돌리게 막아놨지?"라고 의아했다. 하지만 정산, 결제, 포인트 지급 같은 도메인을 생각해보면 당연한 설계다. 실수로 한 번 더 돌렸다가는 돈이 두 번 빠져나가는 사고가 날 수 있다. Spring Batch는 "프레임워크가 개발자의 실수를 막아주는" 사례라고 느꼈다. Saga 패턴에서 보상 트랜잭션(Compensating Transaction)을 별도로 설계하는 철학과도 비슷한 결이다.
이 모든 상태는 어디에 저장될까
Job, JobInstance, JobExecution의 상태 정보는 어디엔가 영구적으로 저장되어야 한다. 메모리에만 있으면 프로세스가 죽는 순간 날아가서 재시작이 불가능하다.
Spring Batch는 이를 위해 9개의 메타데이터 테이블을 자동으로 생성한다.

각 테이블의 역할은 다음과 같다.
Job 레벨
BATCH_JOB_INSTANCE: JobInstance 정보 (Job 이름, 파라미터 해시)BATCH_JOB_EXECUTION: JobExecution 정보 (시작/종료 시간, 상태)BATCH_JOB_EXECUTION_PARAMS: 전달받은 JobParameters의 실제 값BATCH_JOB_EXECUTION_CONTEXT: Job 단위 상태 저장소 (재시작용)
Step 레벨
BATCH_STEP_EXECUTION: Step 실행 정보 (read/write/skip/commit 카운트)BATCH_STEP_EXECUTION_CONTEXT: Step 단위 상태 저장소 (재시작용, 예: 마지막 처리 ID)
시퀀스
BATCH_JOB_INSTANCE_SEQ,BATCH_JOB_EXECUTION_SEQ,BATCH_STEP_EXECUTION_SEQ: 각 테이블의 PK 생성용 시퀀스
실무에서 유용한 쿼리
배치 운영 중 이 테이블들은 가장 먼저 조회하게 된다.
-- 최근 실행된 Job 이력 확인
SELECT
ji.JOB_NAME,
je.JOB_EXECUTION_ID,
je.START_TIME,
je.END_TIME,
je.STATUS,
je.EXIT_CODE
FROM BATCH_JOB_INSTANCE ji
JOIN BATCH_JOB_EXECUTION je ON ji.JOB_INSTANCE_ID = je.JOB_INSTANCE_ID
ORDER BY je.START_TIME DESC
LIMIT 20;
-- 특정 실행의 Step별 처리 건수 확인
SELECT
STEP_NAME,
STATUS,
READ_COUNT,
WRITE_COUNT,
COMMIT_COUNT,
ROLLBACK_COUNT,
READ_SKIP_COUNT,
WRITE_SKIP_COUNT
FROM BATCH_STEP_EXECUTION
WHERE JOB_EXECUTION_ID = ?;
💭 나의 생각
장애 대응 시 가장 먼저 이 테이블을 뒤진다고 한다. 로그보다 빠를 때가 많다는 얘기를 여러 곳에서 봤는데, 실제 구조를 보니 그럴 만하다고 느꼈다. "read_count = 300,000, write_count = 299,998" 같은 숫자만 보면 "어디서 2건이 날아갔는지" 즉시 파악할 수 있는 구조다.
정리
이번 편에서 정리한 내용을 한 문장으로 요약하면 다음과 같다.
Job은 작업의 설계도, JobInstance는 파라미터로 구체화된 작업 1건, JobExecution은 그 작업의 실제 시도다.
그리고 이 세 개념의 상태는 모두 메타데이터 테이블 9개에 자동으로 기록된다. 이 기록이 Spring Batch의 재시작, 중복 실행 방지, 실행 이력 관리를 가능하게 한다.
💭 나의 생각
1편을 쓸 때 "Spring Batch는 대용량 데이터를 안전하고 재시작 가능하게 처리하는 프레임워크"라고 정리했었다. 2편을 쓰면서 보니 "안전성"과 "재시작성"이 전부 Job-Instance-Execution 구조와 메타데이터 테이블에서 나온다는 걸 명확히 알게 됐다. 다음 편에서는 이제 드디어 실제 코드로 들어가서, Chunk 기반 처리와 Reader/Processor/Writer를 다룬다.
다음 편 예고
다음 편에서는 Spring Batch 실무에서 가장 많이 쓰는 Chunk 기반 처리 구조를 다룬다. ItemReader, ItemProcessor, ItemWriter 세 요소가 각각 어떤 역할을 하고, 실제 코드가 어떻게 생겼는지 회원 등급 재계산 배치 예제로 알아본다.
참고 자료
'Spring Batch' 카테고리의 다른 글
| [Spring Batch] 4편. 실전 코드 패턴과 Reader/Processor/Writer (0) | 2026.04.18 |
|---|---|
| [Spring Batch] 3편. Chunk 기반 처리의 원리와 Job/Step 구조 (0) | 2026.04.18 |
| [Spring Batch] 1편. Spring Batch는 왜 필요할까? (1) | 2026.04.18 |