Spring Batch로 대용량 데이터 처리하기: 학습 정리

2025. 10. 19. 22:34·Spring

목차

  • TL;DR
  • 문제 상황: 대용량 데이터 처리의 필요성
  • Spring Batch 핵심 개념
  • Job, Step, Chunk 이해하기
  • 실제 배치 프로세스 설계
  • 실패 처리와 재시작 전략
  • 성능 최적화 방안
  • 실무 적용 시 고려사항
  • 마무리: 학습 후 느낀 점

TL;DR

Spring Batch를 학습하며 정리한 내용입니다. Spring Batch는 대용량 데이터를 안정적으로 처리하기 위한 프레임워크로, Job-Step-Chunk 구조를 통해 데이터를 읽고, 가공하고, 쓰는 과정을 체계적으로 관리합니다. 실패 시 재시작, 트랜잭션 관리, 병렬 처리 등의 기능을 제공하여 엔터프라이즈 환경에서 배치 처리를 효율적으로 구현할 수 있습니다.


문제 상황: 대용량 데이터 처리의 필요성

일반적인 처리 방식의 한계

// 문제가 되는 접근
List<Order> allOrders = orderRepository.findAll();  // 100만 건 조회
for (Order order : allOrders) {
    processOrder(order);  // OutOfMemoryError 위험
}

배치 처리가 필요한 시나리오

  • 일일 정산: 하루 동안의 모든 주문 데이터 집계
  • 월말 리포트: 월간 판매 통계 생성
  • 데이터 마이그레이션: 레거시 시스템에서 신규 시스템으로
  • 대량 알림 발송: 프로모션 메일/푸시 발송

배치 처리의 요구사항

신뢰성: 실패 시 재시작 가능
효율성: 메모리 효율적인 처리
추적성: 처리 상태 모니터링
확장성: 데이터 증가에 대응

Spring Batch 핵심 개념

기본 아키텍처

┌─────────────────────────────────────┐
│            Job Repository           │ ← 메타데이터 저장
└────────────────┬────────────────────┘
                 │
┌────────────────┴────────────────────┐
│            Job Launcher             │ ← Job 실행
└────────────────┬────────────────────┘
                 │
┌────────────────┴────────────────────┐
│               Job                   │
├─────────────────────────────────────┤
│    Step 1  →  Step 2  →  Step 3    │
└─────────────────────────────────────┘
         │
    ┌────┴────┐
    │  Chunk  │
    ├─────────┤
    │ Reader  │ → 데이터 읽기
    │Processor│ → 데이터 가공
    │ Writer  │ → 데이터 쓰기
    └─────────┘

주요 컴포넌트 역할

JobRepository

  • 배치 메타데이터 관리
  • 실행 이력 저장
  • 재시작 포인트 관리

Job

  • 전체 배치 프로세스 정의
  • 하나 이상의 Step으로 구성

Step

  • 실제 배치 처리 단위
  • Chunk 또는 Tasklet 방식

Chunk

  • 데이터를 묶음 단위로 처리
  • 트랜잭션 경계

Job, Step, Chunk 이해하기

Job 구조

Job: 일일 주문 정산
│
├── Step 1: 주문 데이터 검증
│   └── Tasklet: 데이터 무결성 체크
│
├── Step 2: 주문 집계 처리
│   └── Chunk: Reader → Processor → Writer
│
└── Step 3: 리포트 생성
    └── Tasklet: 집계 결과 파일 생성

Chunk 기반 처리 흐름

Chunk Size = 100

[DB: 10,000개 주문]
        ↓
    ItemReader
        ↓
  [100개씩 읽기]  ← Chunk 단위
        ↓
   ItemProcessor
        ↓
  [100개씩 처리]
        ↓
    ItemWriter
        ↓
  [100개씩 쓰기]  ← 트랜잭션 커밋
        ↓
    (반복...)

트랜잭션 경계

for (청크 개수만큼 반복) {
    트랜잭션 시작
    ├── Read: 100개 읽기
    ├── Process: 100개 처리
    ├── Write: 100개 쓰기
    └── 트랜잭션 커밋
    
    실패 시 → 해당 청크만 롤백
}

실제 배치 프로세스 설계

시나리오: 일일 주문 정산 배치

요구사항:
- 매일 새벽 2시 실행
- 전날 주문 데이터 집계
- 가게별, 카테고리별 매출 계산
- 실패 시 재시작 가능

Step 1: 데이터 검증

┌──────────────────────────────┐
│   어제 날짜 주문 존재 확인         │
│   데이터 무결성 체크              │
│   중복 실행 방지                 │
└──────────────┬───────────────┘
               │
         검증 통과?
         ├── Yes → Step 2
         └── No → Job 실패

Step 2: 주문 데이터 처리

Reader (Paging)
├── SELECT * FROM orders 
├── WHERE date = yesterday
├── LIMIT 1000 OFFSET ?
│
Processor (비즈니스 로직)
├── 주문 금액 계산
├── 수수료 계산
├── 프로모션 적용
│
Writer (집계 테이블)
├── INSERT INTO daily_summary
└── UPDATE store_statistics

Step 3: 리포트 생성

집계 데이터 조회
     ↓
CSV/Excel 생성
     ↓
S3 업로드 or Email 발송

실패 처리와 재시작 전략

실패 시나리오별 처리

1. Reader 실패 (DB 연결 오류)
   → Retry Policy 적용
   → 3회 재시도 후 실패

2. Processor 실패 (비즈니스 로직 오류)
   → Skip Policy 적용
   → 문제 데이터 스킵 후 계속

3. Writer 실패 (대상 시스템 오류)
   → Rollback 후 재시작
   → 마지막 성공 지점부터

재시작 메커니즘

Job 실행 상태:
┌────────────────────────────────┐
│ JobInstance (2024-01-15-정산)   │
├────────────────────────────────┤
│ JobExecution #1                │
│ - Status: FAILED               │
│ - Step1: COMPLETED             │
│ - Step2: FAILED (Chunk 50)     │
└────────────────────────────────┘
              ↓
         재시작 시
              ↓
┌────────────────────────────────┐
│ JobExecution #2                │
│ - Step1: SKIP (완료됨)           │
│ - Step2: RESTART (Chunk 50부터) │
└────────────────────────────────┘

Skip & Retry 정책

Skip Policy:
├── 특정 예외 발생 시 해당 아이템 스킵
├── skipLimit 설정 (최대 10개)
└── 스킵된 아이템 별도 로깅

Retry Policy:
├── 네트워크 오류 시 재시도
├── retryLimit 설정 (최대 3회)
└── exponential backoff 적용

성능 최적화 방안

1. Paging vs Cursor

Paging ItemReader:
- 장점: 각 페이지가 독립적
- 단점: 데이터 변경 시 누락/중복 가능
- 사용: 정적 데이터, OFFSET 성능 OK

Cursor ItemReader:
- 장점: 일관된 읽기, 메모리 효율
- 단점: 재시작 시 처음부터
- 사용: 대용량 데이터, 실시간 처리

2. 병렬 처리 전략

Multi-threaded Step:
┌──────────────────┐
│   Single Step    │
├──────────────────┤
│  Thread 1: Chunk │
│  Thread 2: Chunk │
│  Thread 3: Chunk │
└──────────────────┘
- 같은 Step을 여러 쓰레드로

Parallel Steps:
┌─────────┬─────────┬─────────┐
│ Step A  │ Step B  │ Step C  │
└─────────┴─────────┴─────────┘
- 독립적인 Step들 동시 실행

Partitioning:
┌────────────────────────┐
│     Master Step        │
├────┬────┬────┬────┬────┤
│ P1 │ P2 │ P3 │ P4 │ P5 │
└────┴────┴────┴────┴────┘
- 데이터를 파티션으로 분할
- 각 파티션 독립 처리

3. 청크 크기 최적화

작은 청크 (10~100):
- 트랜잭션 자주 커밋
- 실패 영향 최소화
- 처리 속도 느림

큰 청크 (1000~5000):
- 트랜잭션 오버헤드 감소
- 처리 속도 향상
- 메모리 사용량 증가
- 실패 시 롤백 범위 큼

최적값 찾기:
- 데이터 크기 고려
- 처리 로직 복잡도
- 시스템 리소스
- 테스트를 통한 튜닝

실무 적용 시 고려사항

1. 메타데이터 스키마

BATCH_JOB_INSTANCE: Job 정보
BATCH_JOB_EXECUTION: 실행 이력
BATCH_STEP_EXECUTION: Step 실행 정보
BATCH_JOB_EXECUTION_PARAMS: 파라미터
BATCH_JOB_EXECUTION_CONTEXT: 실행 컨텍스트
BATCH_STEP_EXECUTION_CONTEXT: Step 컨텍스트

2. 모니터링

주요 모니터링 지표:
- Job 실행 시간
- 처리된 레코드 수
- Skip/Retry 횟수
- 실패율
- 메모리/CPU 사용량

Alert 조건:
- Job 실패
- 처리 시간 초과
- Skip 임계치 초과
- 메모리 부족

3. 스케줄링

Spring Scheduler:
- 간단한 스케줄링
- 단일 서버 환경
- @Scheduled 어노테이션

Jenkins/Airflow:
- 복잡한 의존관계
- 분산 환경
- UI 기반 관리
- 실행 이력 관리

4. 테스트 전략

단위 테스트:
- Reader/Processor/Writer 개별 테스트
- Mock 데이터 사용

통합 테스트:
- End-to-End 시나리오
- 테스트 DB 사용
- 실패 시나리오 검증

성능 테스트:
- 대용량 데이터 처리
- 병목 지점 파악
- 최적 청크 크기 결정

마무리: 학습 후 느낀 점

배운 점

  1. 체계적인 배치 처리: Job-Step-Chunk 구조를 통한 체계적 관리
  2. 실패 처리의 중요성: 재시작, Skip, Retry 전략의 필요성
  3. 성능 최적화: 청크 크기, 병렬 처리 등 다양한 최적화 방법
  4. 모니터링과 운영: 메타데이터를 활용한 배치 관리

아직 부족한 부분

  1. 대용량 실전 경험: 실제 수억 건 데이터 처리 경험 부족
  2. 복잡한 비즈니스 로직: 다단계 의존관계 처리
  3. 분산 배치 처리: Spring Cloud Data Flow 등
  4. 실시간 배치: 스트리밍과 배치의 결합

실무 적용 예시

일일 정산 배치:
- 매장별 매출 집계
- 수수료 계산
- 정산 리포트 생성

주문 데이터 아카이빙:
- 오래된 데이터 이관
- 성능 최적화

마케팅 발송:
- 대량 푸시 알림
- 타겟 마케팅 메일

데이터 동기화:
- 외부 시스템 연동
- 캐시 갱신

 

Spring Batch는 단순해 보이지만 실제로는 매우 강력한 프레임워크입니다. 대용량 데이터를 안정적으로 처리하기 위한 다양한 기능들을 제공하며, 특히 실패 처리와 재시작 메커니즘이 인상적이었습니다. 실무에서는 비즈니스 요구사항에 맞춰 적절한 전략을 선택하고, 지속적인 모니터링과 최적화가 중요할 것으로 판단됩니다.

 

'Spring' 카테고리의 다른 글

Redis ZSET으로 실시간 랭킹 시스템 구축하기  (0) 2025.09.11
Kafka와 아웃박스 패턴으로 이벤트 파이프라인 구축하기  (0) 2025.09.05
ApplicationEvent로 비즈니스 경계 나누기: 고민과 선택  (3) 2025.08.28
결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략  (1) 2025.08.22
DB 인덱스 설계와 Redis 캐싱(Port-Adapter) 적용기  (2) 2025.08.15
'Spring' 카테고리의 다른 글
  • Redis ZSET으로 실시간 랭킹 시스템 구축하기
  • Kafka와 아웃박스 패턴으로 이벤트 파이프라인 구축하기
  • ApplicationEvent로 비즈니스 경계 나누기: 고민과 선택
  • 결제 시스템에 Circuit Breaker를 도입하며 – 계층별 책임 분리와 안정성 확보 전략
JoshDev
JoshDev
    • 분류 전체보기 (24)
      • Java (3)
      • Spring (9)
      • Test Code (2)
      • WIL (6)
      • Vue.js (2)
      • WEB (0)
      • DB (1)
        • MySQL (1)
  • 인기 글

  • hELLO· Designed By정상우.v4.10.4
JoshDev
Spring Batch로 대용량 데이터 처리하기: 학습 정리
상단으로

티스토리툴바