
1️⃣ 배치(Batch)란?
일정 시간 동안 쌓인 대량의 데이터를 한 번에 처리하는 방식
- 실시간 처리 ❌
- 정합성·안정성·재시작이 핵심
- 정산, 청구, 통계, 마이그레이션에 주로 사용
2️⃣ Spring Batch 프레임워크를 사용하는 이유
비즈니스 로직에만 집중하기 위해
Spring Batch가 대신 처리해 주는 것들:
- 트랜잭션 관리
- 실행 이력 기록
- 실패 시 재시작
- 중복 실행 방지
- 대용량 처리 패턴(Chunk, Partition)
3️⃣ 실무에서의 Batch DB 구성
실무 원칙
- Batch 메타데이터 DB
- 운영 데이터 DB
👉 두 DB를 분리
이유
- 운영 쿼리와 메타 쿼리 충돌 방지
- 장애 전파 차단
- 운영/백업 정책 분리
4️⃣ 배치에서 ‘기록’이 중요한 이유
배치의 핵심은:
중복 처리하거나 놓치는 데이터를 방지하는 것
이를 위해 모든 실행 상태를 기록한다.
- 언제 실행했는지
- 어디까지 처리했는지
- 성공/실패 여부
👉 이 기록이 바로 메타데이터

🔹 1. BATCH_JOB_INSTANCE
📌 “이 Job은 무엇인가?”
Job + JobParameters 조합의 유일성을 관리하는 테이블
칼럼 설명
| JOB_INSTANCE_ID | Job Instance PK |
| JOB_NAME | Job 이름 |
| JOB_KEY | JobParameters를 해시한 값 |
💡 실무 활용 포인트
- 같은 파라미터로 Job 중복 실행 방지
- “이 Job이 이미 실행된 적 있는가?” 판단 기준
🔹 2. BATCH_JOB_EXECUTION
📌 “이 Job은 언제, 어떻게 실행됐는가?”
Job 실행 한 번당 1 row
| 컬럼 | 설명 |
| JOB_EXECUTION_ID | Job 실행 PK |
| JOB_INSTANCE_ID | 어떤 Job Instance인지 |
| STATUS | 실행 상태 |
| START_TIME | 시작 시간 |
| END_TIME | 종료 시간 |
| EXIT_CODE | 종료 코드 |
| EXIT_MESSAGE | 실패 사유 메시지 |
💡 실무에서 자주 보는 칼럼
- STATUS
- COMPLETED
- FAILED
- STOPPED
- START_TIME / END_TIME
- 배치 소요 시간 분석
- EXIT_MESSAGE
- 실패 원인 1차 확인
🔹 3. BATCH_JOB_EXECUTION_PARAMS
📌 “이 Job은 어떤 파라미터로 실행됐는가?”
Job 실행 시 전달한 파라미터 저장
| 컬럼 | 설명 |
| JOB_EXECUTION_ID | 어떤 실행인지 |
| KEY_NAME | 파라미터 이름 |
| TYPE_CD | 타입 (STRING, DATE 등) |
| STRING_VAL | 값 |
💡 실무 활용 예
- billingMonth=2026-01
- requestDate=2026-01-31
👉 운영 중 특정 월/조건 배치 이력 조회할 때 핵심
🔹 4. BATCH_STEP_EXECUTION
📌 “Step 단위로 얼마나 처리했는가?”
성능·처리량 분석의 핵심 테이블
| 컬럼 | 설명 |
| STEP_EXECUTION_ID | Step 실행 PK |
| JOB_EXECUTION_ID | 소속 Job |
| STEP_NAME | Step 이름 |
| STATUS | Step 상태 |
| READ_COUNT | 읽은 건수 |
| WRITE_COUNT | 쓴 건수 |
| FILTER_COUNT | 필터링된 건수 |
| COMMIT_COUNT | 커밋 횟수 |
| ROLLBACK_COUNT | 롤백 횟수 |
| START_TIME | 시작 |
| END_TIME | 종료 |
⭐ 가장 중요한 칼럼 TOP 5
- READ_COUNT
- WRITE_COUNT
- FILTER_COUNT
- COMMIT_COUNT
- ROLLBACK_COUNT
👉 대용량 배치 튜닝·장애 분석의 핵심
🔹 5. BATCH_STEP_EXECUTION_CONTEXT
📌 “Step이 어디까지 처리했는가?”
Step 재시작을 위한 상태 저장소
| 컬럼 | 설명 |
| STEP_EXECUTION_ID | Step 실행 |
| SHORT_CONTEXT | 요약 |
| SERIALIZED_CONTEXT | 실제 Context |
💡 실무 예시
- 마지막 처리 ID
- 마지막 페이지 번호
- 마지막 커서 위치
👉 Cursor / Paging / Partition Step에서 매우 중요
🔹 6. BATCH_JOB_EXECUTION_CONTEXT
📌 “Job 전체에서 공유하는 상태”
Job 범위 ExecutionContext
| 컬럼 | 설명 |
| JOB_EXECUTION_ID | Job 실행 |
| SERIALIZED_CONTEXT | 공유 데이터 |
💡 활용 예
- Step 간 공통 데이터 전달
- 계산 결과 캐시
🔹 7. 시퀀스 테이블 (DB별)
- BATCH_JOB_SEQ
- BATCH_JOB_EXECUTION_SEQ
- BATCH_STEP_EXECUTION_SEQ
👉 PK 생성용 (직접 다룰 일 거의 없음)
3️⃣ 배치 작업하면서 “특히 많이 쓰는 컬럼 요약”
📊 운영 모니터링용
| 목적 | 컬럼 |
| 성공/실패 여부 | STATUS |
| 실행 시간 | START_TIME, END_TIME |
| 실패 원인 | EXIT_MESSAGE |
📈 성능 분석용
| 목적 | 컬럼 |
| 처리량 | READ_COUNT, WRITE_COUNT |
| 필터링 | FILTER_COUNT |
| 트랜잭션 비용 | COMMIT_COUNT |
| 장애 | ROLLBACK_COUNT |
🔁 재시작/정합성
| 목적 | 컬럼 |
| 재시작 여부 | STATUS, STEP_EXECUTION_CONTEXT |
| 마지막 처리 위치 | SERIALIZED_CONTEXT |
5️⃣ Batch 기본 설정

batch.yml 예시
spring:
batch:
job:
enabled:false # 애플리케이션 시작 시 자동 실행 방지
jdbc:
initialize-schema:always# 메타데이터 테이블 자동 생성
6️⃣ Batch 실행 방법
1. JobLauncher 직접 실행
jobLauncher.run(job, jobParameters);
2. API 호출
@PostMapping("/batch/run")
publicvoidrunBatch() {
jobLauncher.run(job, jobParameters);
}
3. 스케줄러 실행
@Scheduled(cron = "0 0 2 * * *")
publicvoidrunBatch() {
jobLauncher.run(job, jobParameters);
}
7️⃣ JobParameters란?

Job 실행을 구분하는 식별자
- Job + JobParameters → JobInstance
- 같은 Job이라도 파라미터가 다르면 다른 실행
JobParametersparams=newJobParametersBuilder()
.addString("invMonth","202601")
.toJobParameters();
👉 정산 배치에서 월별 실행 구분에 필수
8️⃣ Step 설정 핵심
Step 옵션
- skip
- → 특정 예외 발생 시 건너뜀
- retry
- → 예외 발생 시 재시도
- Writer 롤백 제어
- → Chunk 단위 트랜잭션 관리
- StepListener
- → Step 실행 전/후 훅
.stepBuilderFactory.get("step")
.<A, B>chunk(1000)
.faultTolerant()
.skip(Exception.class)
.skipLimit(10)
.listener(stepListener)
.build();
9️⃣ Job 설정 핵심

Step Flow
jobBuilderFactory.get("job")
.start(step1)
.next(step2)
.build();
조건 분기
.start(step1)
.on("FAILED").end()
.from(step1).on("*").to(step2)
JobListener
.jobListener(jobListener)
👉 Job 실행 전/후 공통 로직 처리
🔟 JPA vs JDBC (Batch 관점)
JPA 문제점
- Reader 성능 차이 ❌
- Writer 성능 차이 큼
이유
- IDENTITY 전략
- Bulk Insert/Update 불가
- SQL이 건별로 실행됨
JDBC 장점
- Bulk 쿼리 가능
- 대량 데이터 처리에 적합
- Batch Writer 성능 우수
JdbcBatchItemWriter<Item> writer =
newJdbcBatchItemWriterBuilder<Item>()
.sql("INSERT INTO table (...) VALUES (...)")
.dataSource(dataSource)
.build();
👉 Batch에서는 JDBC가 사실상 표준
Grid Size vs Chunk Size vs Page Size
[ DB ] --- (SELECT 1000개: PageSize) ---> [ 서버 Reader 메모리 ]
|
(1개씩 꺼내기)
↓
[ ItemProcessor ]
↓
(1개씩 가공해서 넣기)
↓
[ Chunk Buffer ] (서버 메모리 List)
(1,000개가 찰 때까지 반복)
↓
[ DB ] <--- (INSERT/UPDATE 1000개: Writer) --- [ 1,000개 완성! ]
[ DB ] <--- (Commit: 트랜잭션 종료)
📌 요약하자면
- PageSize: DB에서 서버로 데이터를 퍼오는 바가지 크기 (SELECT 쿼리 단위)
- ChunkSize: 서버에서 처리를 다 끝내고 DB에 확정 짓는 단위 (Transaction/Write 단위)
- Chunk Buffer: Writer에게 넘겨주기 전까지 가공된 데이터들을 잠시 담아두는 서버 안의 바구니
대부분의 실무에서는 바가지 크기(PageSize)와 확정 단위(ChunkSize)를 1000으로 똑같이 맞춰서 사용
Tasklet 방식 vs Chunk 방식

1. Tasklet 방식: "한 번에 하나의 작업"
Tasklet은 하나의 스텝(Step) 안에서 단일 로직을 처음부터 끝까지 수행하는 방식입니다.
- 동작 원리: execute() 메서드 하나가 실행되고, RepeatStatus.FINISHED를 반환할 때까지 전체 로직이 돌아갑니다.
- 주요 용도: 단순한 DB 업데이트 하나, 파일 삭제, 로그 출력, 시스템 명령어 실행 등 데이터 읽기/쓰기가 복잡하지 않은 작업에 적합합니다.
- 특징: 트랜잭션 범위가 Tasklet 전체이며, 데이터가 작을 때 유리합니다.
2. Chunk 방식: "나눠서 반복 처리"
Chunk는 대량의 데이터를 일정 단위(Chunk)로 쪼개서 반복적으로 읽고, 가공하고, 쓰는 방식입니다.
- 동작 원리: Reader(읽기) → Processor(처리) → Writer(쓰기)의 과정을 chunkSize만큼 반복합니다.
- Reader: 데이터를 1개씩 읽어옵니다.
- Processor: 읽어온 1개를 가공합니다.
- Writer: chunkSize만큼 모인 리스트를 한 번에 DB에 저장합니다.
- 주요 용도: 100만 건 같은 대용량 데이터 처리, 복잡한 비즈니스 로직이 들어가는 ETL 작업에 적합합니다.
- 특징: chunkSize 단위로 트랜잭션이 커밋(Commit)되므로, 에러가 나도 해당 덩어리만 롤백하면 되어 안정적입니다.
Partitioning

하나의 Step을 여러 개의 “Worker Step”으로 쪼개 병렬로 실행하는 방식

1. 주요 구성 요소 (Components)
- PartitionStep: 전체 파티셔닝 작업을 총괄하는 마스터(Master) 스텝입니다.
- PartitionHandler: 파티션을 어떻게 다룰지(어떤 Worker 스텝으로 보낼지, 어떤 스레드 풀을 쓸지 등)를 결정합니다.
- StepExecutionSplitter: 데이터를 어떤 범위로 나눌지 결정합니다. (예: 100만 건을 10만 건씩 10개로 분할)
- Step (Worker Step): 실제로 비즈니스 로직(Read-Process-Write)을 수행하는 독립적인 작업 단위입니다.
2. 실행 흐름 (Execution Flow)
- execute(): PartitionStep이 실행을 시작합니다.
- handle(): PartitionStep은 PartitionHandler에게 파티션 처리를 요청합니다.
- split(): PartitionHandler는 StepExecutionSplitter를 호출하여 데이터를 여러 개의 실행 단위(StepExecution)로 쪼갭니다.
- execute() [repeat]: 분할된 각 실행 단위들에 대해 Worker Step들이 병렬적으로 실행됩니다. 그림에서 repeat은 여러 개의 스레드나 프로세스가 동시에 작업을 수행함을 의미합니다.
- join: 모든 Worker Step들의 작업이 끝날 때까지 기다린 후 결과를 취합합니다.
- aggregate: PartitionStep이 최종적으로 모든 파티션의 결과를 하나로 모아 정리하고 작업을 종료합니다.
@Bean
public Step step1Master() {
return stepBuilderFactory.get("step1.master")
.<String, String>partitioner("step1", partitioner())
.step(step1())
.gridSize(10)
.taskExecutor(taskExecutor())
.build();
}
