도입
실무 시스템에서는 모든 처리를 실시간으로 할 필요가 없습니다. 주문 결제 승인처럼 즉시 응답이 필요한 작업도 있지만, 정산, 통계 집계, 리포트 생성, 데이터 마이그레이션, 백업, 로그 분석처럼 모아서 처리하는 편이 더 효율적인 작업도 많습니다.
이런 작업은 보통 사용자의 요청 하나에 즉시 반응하기보다, 일정 시간 동안 쌓인 데이터를 대상으로 한 번에 실행됩니다. 이를 일괄 처리 또는 배치 처리라고 부릅니다.
그래서 배치 처리를 이해한다는 것은 단순히 “밤에 도는 작업”을 아는 것이 아니라, 대량 데이터 처리와 재시작성, 장애 복구, 스케줄링, 처리량 최적화를 함께 설계하는 관점을 이해하는 일에 가깝습니다.
필요성
온라인 요청은 보통 짧은 응답 시간을 목표로 합니다. 사용자가 버튼을 누르면 몇 초 안에 결과를 받아야 하므로, 긴 계산이나 대량 데이터 처리를 그 자리에서 수행하면 사용자 경험과 시스템 안정성이 모두 나빠질 수 있습니다.
반면 배치 작업은 처리량과 안정성이 더 중요합니다. 수백만 건의 데이터를 읽고, 검증하고, 변환하고, 저장해야 할 수 있으며, 중간에 실패하면 어디까지 처리했는지 추적하고 재시작할 수 있어야 합니다.
즉 배치 처리는 빠른 단건 응답보다, 대량 반복 작업을 정확하고 재현 가능하게 끝내는 데 초점을 둔 실행 모델입니다.
- 대량 데이터를 정해진 시간에 처리해야 할 때
- 정산, 집계, 리포트, 통계처럼 반복적인 업무가 있을 때
- 실시간 요청 흐름과 무거운 처리를 분리하고 싶을 때
- 실패 후 재시작, 재처리, 중복 방지가 중요한 작업을 설계할 때
정의
배치 처리에서 핵심 단위는 개별 요청이 아니라 작업 묶음입니다. 예를 들어 하루 동안 쌓인 주문 데이터를 대상으로 정산 파일을 만들거나, 매월 말 고객별 청구서를 생성하거나, 특정 테이블의 데이터를 다른 시스템으로 이관하는 작업이 배치에 해당합니다.
이 작업들은 보통 사용자가 화면에서 기다리는 동안 실행되지 않습니다. 스케줄러, 운영자 명령, 이벤트, 배치 런처 같은 별도 트리거에 의해 실행됩니다.
즉 배치 처리는 “언제 실행할지”, “무엇을 읽을지”, “어떻게 나눠 처리할지”, “실패하면 어떻게 복구할지”까지 포함하는 실행 모델입니다.
"배치 처리의 본질은 단순 반복 실행이 아니라
대량 작업을 안정적으로 끝내기 위한 실행 단위, 상태 관리, 복구 전략을 설계하는 데 있습니다."
핵심 원리
배치 처리는 보통 Read → Process → Write 흐름으로 이해할 수 있습니다. 먼저 입력 데이터를 읽고, 비즈니스 규칙에 따라 변환하거나 검증한 뒤, 결과를 파일·DB·외부 시스템 등에 기록합니다.
중요한 점은 전체 데이터를 한 번에 메모리에 올려 처리하지 않는다는 것입니다. 대량 데이터를 chunk 또는 page 단위로 나누어 처리하고, 각 단위마다 트랜잭션 경계를 잡는 편이 안정적입니다.
또 배치 작업은 실패 가능성을 전제로 설계해야 합니다. 수십만 건을 처리하다가 중간에 실패했을 때 처음부터 다시 처리할지, 마지막 성공 지점부터 재시작할지, 일부 데이터는 skip 할지, retry 할지 명확한 정책이 필요합니다.
Batch Processing Flow
1) Job 실행
2) Step 시작
3) ItemReader 로 데이터 읽기
4) ItemProcessor 로 검증/변환
5) ItemWriter 로 결과 저장
6) chunk 단위 commit
7) 실행 메타데이터 저장
8) 실패 시 재시작 또는 복구 정책 적용
구성 요소
| 요소 | 역할 | 실무 해석 |
|---|---|---|
| Job | 배치 작업 전체 | 예: 일별 정산 Job, 고객 등급 갱신 Job |
| Step | Job을 이루는 개별 단계 | 읽기/처리/쓰기 또는 tasklet 단위의 실행 단계 |
| ItemReader | 입력 데이터 읽기 | DB, 파일, API, 큐 등에서 데이터를 가져옴 |
| ItemProcessor | 데이터 검증·변환 | 비즈니스 규칙 적용, 필터링, DTO 변환 |
| ItemWriter | 결과 저장 | DB insert/update, 파일 쓰기, 외부 시스템 전송 |
| JobRepository | 실행 메타데이터 저장 | JobExecution, StepExecution, 재시작성 관리 기반 |
| JobLauncher | Job 실행 트리거 | 스케줄러, API, CLI 등에서 Job을 시작할 때 사용 |
| JobParameters | Job 실행 식별과 입력값 | 날짜, 파일명, 대상 범위 등 실행마다 달라지는 값 |
실시간 처리와 차이
| 구분 | 배치 처리 | 실시간 처리 |
|---|---|---|
| 처리 단위 | 여러 건을 묶어서 처리 | 개별 요청 또는 이벤트를 즉시 처리 |
| 중요 기준 | 처리량, 정확성, 재시작성 | 응답 시간, 최신성, 지연 최소화 |
| 실행 시점 | 스케줄, 수동 실행, 특정 조건 | 요청 또는 이벤트 발생 즉시 |
| 대표 예시 | 정산, 리포트, 백업, 데이터 이관 | 로그인, 결제 승인, 주문 생성 |
| 실패 처리 | 재시작, 재처리, skip/retry | 즉시 응답, 보상 트랜잭션, 재시도 이벤트 |
기본 구조
batch-system/
├── job/
│ ├── daily-settlement-job
│ └── customer-grade-update-job
├── step/
│ ├── read-source-data
│ ├── transform-data
│ └── write-result
├── reader/
│ ├── database-reader
│ └── file-reader
├── processor/
│ └── business-rule-processor
├── writer/
│ ├── database-writer
│ └── file-writer
├── repository/
│ └── job-execution-metadata
└── trigger/
├── scheduler
├── api
└── cli
기본 구현
@Configuration
public class CustomerGradeBatchConfig {
@Bean
public Job customerGradeJob(
JobRepository jobRepository,
Step customerGradeStep
) {
return new JobBuilder("customerGradeJob", jobRepository)
.start(customerGradeStep)
.build();
}
@Bean
public Step customerGradeStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
ItemReader<Customer> customerReader,
ItemProcessor<Customer, CustomerGradeResult> customerProcessor,
ItemWriter<CustomerGradeResult> customerWriter
) {
return new StepBuilder("customerGradeStep", jobRepository)
.<Customer, CustomerGradeResult>chunk(1000, transactionManager)
.reader(customerReader)
.processor(customerProcessor)
.writer(customerWriter)
.build();
}
}
패턴 1. Chunk 기반 처리
Chunk 기반 처리는 배치에서 가장 흔한 구조입니다. 데이터를 하나씩 읽고, 필요한 경우 처리하고, 일정 개수가 모이면 한 번에 기록하고 commit 합니다.
이 방식의 장점은 메모리 사용량과 트랜잭션 경계를 통제할 수 있다는 점입니다. 전체 데이터를 한꺼번에 처리하지 않기 때문에 대량 데이터에도 안정적으로 대응할 수 있습니다.
chunk size = 1000
read 1
process 1
...
read 1000
process 1000
write 1000 items
commit
패턴 2. 스케줄링과 JobParameters
배치 작업은 매일 새벽, 매월 말, 특정 파일 업로드 후, 운영자 명령 등 다양한 방식으로 실행될 수 있습니다. 이때 실행마다 달라지는 값은 코드에 박아 넣기보다 JobParameters 로 전달하는 편이 좋습니다.
예를 들어 targetDate=2026-05-19, inputFile=/data/orders.csv 같은 값은 같은 Job 정의를 여러 실행에 재사용하게 만들어 줍니다.
Job Name
dailySettlementJob
Job Parameters
targetDate=2026-05-19
region=KR
inputFile=/batch/input/orders-20260519.csv
패턴 3. 재시작성과 멱등성
배치 작업은 오래 실행되고 많은 데이터를 다루기 때문에 실패 가능성이 항상 있습니다. 네트워크 오류, DB lock, 외부 API 장애, 데이터 오류, 서버 재시작 같은 상황은 언제든 발생할 수 있습니다.
따라서 좋은 배치 설계는 실패를 막는 데서 끝나지 않고, 실패 후 재시작이 가능한지, 이미 처리한 데이터를 다시 처리해도 결과가 깨지지 않는지, 중복 insert나 중복 정산이 생기지 않는지를 함께 봅니다.
이때 중요한 개념이 멱등성(idempotency) 입니다. 같은 데이터를 다시 처리하더라도 최종 결과가 한 번 처리한 것과 같도록 만드는 설계가 필요합니다.
패턴 4. Skip 과 Retry
일부 오류는 일시적입니다. 예를 들어 DB deadlock, 네트워크 지연, 외부 API 일시 장애는 다시 시도하면 성공할 수 있습니다. 이런 경우 retry 정책이 적합합니다.
반대로 특정 입력 데이터 한 건이 잘못된 경우, 그 한 건을 skip 하고 나머지를 처리하는 것이 더 나을 수 있습니다. 다만 금융 정산처럼 한 건의 오류도 전체 결과에 영향을 주는 작업이라면 skip을 허용하면 안 됩니다.
즉 skip과 retry는 기술 설정이 아니라 데이터 의미와 업무 위험도에 따라 결정해야 하는 정책입니다.
오류 처리 분류
- Retry: 일시적 오류, 다시 시도하면 성공 가능
- Skip: 일부 데이터 오류, 업무상 건너뛰기 허용
- Fail: 정확성이 중요해 즉시 중단해야 하는 오류
패턴 5. 확장과 병렬 처리
Spring Batch 공식 문서는 많은 배치 문제가 단일 스레드·단일 프로세스 Job으로도 해결될 수 있으므로, 복잡한 병렬 구현을 고려하기 전에 현실적인 Job 성능을 먼저 측정하라고 권장합니다.
성능이 실제로 부족하다면 여러 선택지가 있습니다. 멀티스레드 Step, 병렬 Step, partitioning, remote chunking 같은 방식으로 읽기 범위나 처리 단위를 나눌 수 있습니다.
다만 병렬화는 항상 정답이 아닙니다. DB lock, 외부 API rate limit, Writer thread-safety, 트랜잭션 경합, 순서 보장 문제를 함께 검토해야 합니다.
한계와 주의점
배치는 기본적으로 지연을 허용하는 처리 모델입니다. 따라서 사용자가 즉시 결과를 기대하는 업무를 배치로 밀어내면 사용자 경험이 나빠질 수 있습니다.
또 배치 작업은 대량 데이터를 다루기 때문에 실패 시 영향 범위도 큽니다. 잘못된 조건으로 실행하면 많은 데이터를 한 번에 잘못 수정할 수 있고, 중복 실행 방지나 dry-run 검증이 없으면 복구가 어려울 수 있습니다.
마지막으로 배치는 운영 관점이 매우 중요합니다. 실행 시간, 실패 알림, 재시작 방법, 처리 건수, skip 건수, retry 횟수, 지연 시간 같은 지표가 없다면 장애 대응이 어려워집니다.
- 즉시 응답이 필요한 업무를 배치로 처리하면 사용자 경험이 나빠질 수 있음
- 중복 실행 방지 없이 재시작하면 중복 저장이나 중복 정산이 발생할 수 있음
- chunk 크기를 잘못 잡으면 DB 부하, 메모리 사용량, 재처리 범위가 커질 수 있음
- 병렬화는 성능을 높일 수 있지만 lock, 순서, thread-safety 문제를 만들 수 있음
- 운영 지표와 실패 알림 없이 배치를 운영하면 장애를 늦게 발견할 수 있음
자주 하는 실수
- 전체 데이터를 한 번에 메모리에 올려 처리함
- chunk 크기를 근거 없이 정하고 성능 측정을 하지 않음
- 실패 후 재시작 시 이미 처리한 데이터가 중복 반영됨
- JobParameters 없이 실행해 같은 JobInstance 구분이 어려워짐
- Skip 과 Retry 를 업무 의미 없이 기술 설정으로만 정함
- 병렬 처리부터 적용하고 DB lock 과 writer thread-safety 를 나중에 발견함
- 처리 건수, 실패 건수, 실행 시간, 지연 시간 지표를 남기지 않음
실무 루틴
- 먼저 이 작업이 실시간 처리인지 배치 처리인지 구분한다.
- Job 단위와 Step 단위를 명확히 나눈다.
- 입력 범위를
JobParameters로 받을 수 있게 설계한다. - Reader, Processor, Writer 의 책임을 분리한다.
- chunk 크기와 transaction boundary 를 성능 측정 기반으로 정한다.
- 재시작 시 중복 반영이 없도록 멱등성을 설계한다.
- 실행 시간, 처리 건수, 실패 건수, skip/retry 건수를 운영 지표로 남긴다.
디버깅
점검 체크리스트
- JobParameters 가 올바른가
- JobInstance / JobExecution / StepExecution 상태는 무엇인가
- read count / process count / write count 가 기대와 맞는가
- 실패 지점은 Reader, Processor, Writer 중 어디인가
- chunk commit 범위는 어디까지인가
- 재시작 시 중복 반영 가능성은 없는가
- skip / retry 정책이 업무 의미와 맞는가