개발기술/Spring

배치처리

bsh6226 2025. 4. 21. 08:24

배치 처리란?

한꺼번에(일괄로) 많은 데이터를 처리하는 방식. 주로 사용자 개입 없이, 예약된 시점에 자동 실행됨

 

배치 처리의 구성 요소 (Spring 기준)

Spring에서는 보통 @Scheduled 또는 Spring Batch를 이용해 구현해.

 

@Scheduled (간단한 주기성 작업)

  • 가볍고 단순한 배치에 적합
  • 트랜잭션/재시도/병렬처리 기능 없음
@Scheduled(cron = "0 0 0 * * *") // 매일 자정
public void sendDailyReport() {
    // 작업 수행
}

 

Spring Batch (전문 배치 프레임워크)

 

@Bean
public Job myJob() {
    return jobBuilderFactory.get("myJob")
        .start(step1())
        .next(step2())
        .build();
}

insert 성능 최적화 = batch insert
대량처리 안정성 + 트랜잭션 + 재시도 등 파이프라인 운영은 Spring Batch의 몫

 

 

대규모 일괄 작업에 최적화된 프레임워크

  • 청크 처리 (N개 단위 커밋) : 예: 1000개씩 데이터를 읽고 → 처리하고 → 저장 후 커밋, 실패 시 해당 청크만 재시도 가능.
  • 트랜잭션 관리 : 각 청크(작업 단위)마다 별도 트랜잭션으로 묶어 실패 시 롤백 가능.
  • 재시도/중단/재개 기능 : 장애 발생 시 실패한 부분부터 이어서 실행 가능 (JobRepository에 상태 저장).
  • Job/Step 단위 분리 가능 : 작업 전체(Job)와 그 하위 단계(Step)를 구조적으로 분리하여 유연한 처리 가능.
 

 

  @Scheduled Spring Batch
🧠 목적 단순 주기적 실행 대규모 배치 처리에 최적화
🔁 실행 방식 메서드 단위로 주기 실행 Job/Step 기반으로 처리 흐름 정의
⚙ 트랜잭션 제어 개발자가 직접 제어 프레임워크에서 제공
🧾 상태 관리 기본적으로 없음 실행 상태(JobInstance), 실패/재시도 관리
📦 병렬 처리 Executor 설정 필요 기본 기능으로 분할/병렬 처리 지원
📑 로그, 모니터링 수동 구현 JobRepository 통해 자동 관리 가능
💥 장애 복구 수동 처리 중단 지점부터 재시작 가능 (restartable)

🔍 예제 중심 비교

예시 1. 단순한 API 호출 (공공 데이터 저장)

 
@Scheduled(cron = "0 0 1 * * *") // 매일 1시에 실행
public void getHolidayInfo() {
    List<Holiday> holidays = webClient.get(...);
    holidayRepository.saveAll(holidays);
}

항목 설명
작업 크기 작음 (몇 백 개 수준)
실패 처리 실패하면 그냥 로그만 남기고 끝
트랜잭션 크게 중요하지 않음
재시작 필요 없음
병렬 처리 필요 없음

 

예시 2. 수십만 고객의 포인트 정산

항목 설명
작업 크기 매우 큼 (수십만 건 이상)
실패 처리 실패 시 롤백, 재시도 필요
트랜잭션 청크 단위로 커밋
재시작 필요 중간부터 이어서 가능해야 함
병렬 처리 고객별로 병렬 처리 유리
✅ 적합 Spring Batch 사용 권장

 

@Bean
public Step pointCalcStep() {
    return stepBuilderFactory.get("pointCalcStep")
        .<Customer, PointResult>chunk(1000)
        .reader(customerReader())
        .processor(pointCalculator())
        .writer(pointWriter())
        .build();
}

 

JPA 관점의 batch insert


  • JPA에서는 saveAll() 등을 통해 여러 개의 엔티티를 한꺼번에 persist하면,

    내부적으로 EntityManager.persist()를 반복 호출하게 됩니다.

    이때 persistence context (1차 캐시) 에 엔티티가 계속 쌓이는데,
    너무 많아지면 메모리 사용 증가 + 성능 저하 + OutOfMemoryRisk 가 생길 수 있어요.

    그래서 하는 게?
    일정 수만큼 저장 후 flush() + clear() 호출 👉 batch 처리처럼 동작

    java
    복사
    편집
    for (int i = 0; i < entities.size(); i++) {
        entityManager.persist(entities.get(i));
        if (i % 100 == 0) {
            entityManager.flush();  // DB에 반영
            entityManager.clear();  // persistence context 초기화