본문 바로가기
웹 개발/Back End

스프링 배치(Spring Batch)란?

by L3m0n S0ju 2025. 4. 12.

 

 

스프링 배치는 반복적인 대량 데이터 처리 작업을 자동화하고 성능을 최적화하는 Spring 기반 프레임워크입니다.

예를 들어, "매일 자정에 사용자의 포인트를 정산해야 한다"고 가정하면, 이를 수작업으로 처리하면 비효율적이고 오류 가능성이 큽니다.

스프링 배치를 사용하면 이러한 작업을 일정한 주기 또는 특정 이벤트에 따라 자동 실행하고, 효율적으로 데이터를 읽고, 가공하고, 저장할 수 있습니다.

 

 

✅ 대표적인 활용 예시

  • ETL(Extract, Transform, Load) 작업: 데이터베이스에서 데이터를 추출하고 변환하여 다른 시스템으로 적재
  • 데이터 마이그레이션: 기존 시스템의 데이터를 새로운 시스템으로 이동
  • 정산 작업: 월급 계산, 포인트 정산, 주문 상태 업데이트 등의 대량 데이터 처리

 

✅ 스프링 배치의 핵심 기능

스프링 배치는 대량 데이터 처리를 효율적으로 실행할 수 있도록 다양한 기능을 제공합니다.

특히, 안정적인 실행을 보장하고, 실패 시 복구할 수 있도록 여러 관리 기능을 제공합니다.

1️⃣ 배치 작업 실행 및 관리

  • Step & Tasklet 기반처리, Chunk 기반 처리: 하나의 배치를 여러 Step으로 나누어 실행하며, Chunk 단위로 데이터를 읽고, 가공하고, 저장 (예: 100개씩 처리 후 커밋)
  • JobRepository: 배치 실행 정보를 저장하고, 실행 상태(성공/실패/진행 중/재시작 가능 여부) 추적

2️⃣ 배치 작업의 안정성 보장

  • 재시작(Restart) 및 중단 복구(Checkpointing): 배치 실행 중 오류 발생 시, 마지막 성공한 지점부터 이어서 실행 가능
  • 예외 처리(Skip & Retry): 오류가 발생한 데이터만 건너뛰고, 나머지 정상 데이터는 계속 처리 가능

3️⃣ 성능 최적화 및 확장성

  • 병렬 처리 및 분산 처리 지원: 여러 Step을 병렬 실행하거나, 여러 서버에서 배치를 분산 실행 가능
  • 리소스 효율적인 배치 실행: 대량 데이터 처리 시 메모리 사용을 최적화하여 실행 가능

 

 

 


✅ 스프링 배치 실행 순서

🟢 배치 실행 시작(수동 배치 또는 스케줄러)

├── 1️⃣ Job 실행 (JobLauncher가 Job 실행)

│   ├── JobRepository에 실행 기록 저장 (STARTED 상태)

│   ├── Job 내부에 정의된 Step 실행

│   │ ├── Step 실행 (순차적 또는 병렬 실행 가능)

│   │ ├── Step 성공 시 다음 Step 실행

│   │ └── Step 실패 시 Retry 또는 Skip 정책 적용

│   └── 모든 Step이 성공하면 Job이 완료됨

├── 2️⃣ Job 종료

│   ├── 성공: JobRepository에 COMPLETED 상태 저장

│   ├── 실패: JobRepository에 FAILED 상태 저장, 재시작 가능

│   └── 예외 발생 시, 특정 데이터 건너뛰기 (Skip)

└── 3️⃣ 실행 로그 저장 및 후속 처리

├── 배치 결과 로깅 (JobExecutionListener 활용 가능)

├── 배치 수행 후 알림 (이메일, Slack 등)

└── 필요 시, Job 재실행

 

 

 

 


수동 배치 실행 예시

@Operation(summary = "지정된 일자 및 시간에 휴가조정 히스토리에서 적용일에 해당하면 조정 수행")
@PostMapping(value = {"/run/adjustment"})
public ManualLeaveBatchExecuteResultDto runLeaveAdjustmentBatch(
        @Parameter(description = "배치 시작일 (예: yyyy-MM-dd)", example = "2024-07-22") 
        @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
        @Parameter(description = "배치 수행 시각 (예: 22)", example = "22") int hour) 
{
    return leaveBatchManualExecuteService.runLeaveAdjustmentBatch(date, hour);
}

 

/**
 * 연차 및 기타휴가 조정
 */
public ManualLeaveBatchExecuteResultDto runLeaveAdjustmentBatch(LocalDate startDate, int batchRunHour) {
    int executionCount = 0;
    ManualLeaveBatchExecuteResultDto resultDto = ManualLeaveBatchExecuteResultDto.of(LEAVE_ADJUSTMENT_JOB.name(), 
        LEAVE_ADJUSTMENT.name(), startDate, null);
    log.info("Processing batch for date: {}", startDate);
    LocalDateTime timeAt = LocalDateTime.of(startDate, LocalTime.of(batchRunHour, 0));
    batchUserAnnualLeaveService.processLeaveAdjustmentInBatches(startDate, SpringBatchInfo.of(timeAt, 
        LEAVE_ADJUSTMENT_JOB.name(), LEAVE_ADJUSTMENT.name()));     executionCount++;
    return resultDto.updateResult(executionCount);

 

 

일반적인 컨트롤러 서비스 로직과 동일합니다.

 

 

 


✅ 스케줄러(Scheduler)란?

  • *스케줄러(Scheduler)**는 특정 시간이나 주기마다 배치 작업을 자동 실행할 수 있도록 지원하는 기능입니다.

 

스프링 배치와 함께 사용되는 주요 스케줄러는 아래와 같습니다.

🔹 스케줄러 종류 및 특징

스케줄러  특징  적합한  배치
Spring Scheduler - @Scheduled 어노테이션 사용, 간단한 배치 실행 가능<br>- DB 저장 없이 메모리 기반으로 동작 단순한 반복 작업 (예: 하루 한 번 실행되는 배치)
Quartz Scheduler - 복잡한 스케줄링 지원 (다양한 트리거 조합 가능)<br>- DB에 스케줄 정보 저장 가능 복잡한 실행 조건이 필요한 배치 (예: 특정 요일마다 실행, 다중 트리거 설정 등)
Kubernetes CronJob - 컨테이너 기반 애플리케이션에서 배치 실행 가능<br>- 자동 스케일링 지원 클라우드 환경에서 실행되는 배치 (예: AWS, GCP 환경의 배치 작업)
Linux Crontab - 서버 OS에서 직접 배치를 실행하는 방식<br>- Spring과 독립적으로 동작 서버에서 관리하는 시스템 배치 (예: 로그 정리, 백업 작업)

 

 

✅ 언제 어떤 스케줄러를 사용해야 할까?

📌 Spring Scheduler

  • 간단한 배치(예: 하루 한 번 정기적으로 실행되는 작업)
  • @Scheduled 어노테이션만 추가하면 바로 사용 가능
  • 단, 서버가 재시작되면 스케줄이 사라지며, 실행 이력이 DB에 저장되지 않음

📌 Quartz Scheduler

  • 복잡한 스케줄링이 필요한 경우(예: 특정 요일마다 실행, 여러 개의 트리거 설정)
  • DB에 스케줄을 저장할 수 있어, 서버가 재시작되어도 유지됨
  • 다중 작업 실행, 분산 환경 지원 가능

📌 Kubernetes CronJob

  • 클라우드 환경에서 배치 실행이 필요한 경우
  • 컨테이너 기반 애플리케이션을 주기적으로 실행 가능
  • 예: AWS, GCP, Azure 등의 클라우드 환경에서 배치 실행

📌 Linux Crontab

  • Spring과 독립적으로 실행되는 서버 관리 작업(예: 로그 정리, 데이터 백업)
  • crontab -e로 설정하여 특정 시간마다 실행 가능
  • 스프링 애플리케이션과 완전히 독립적으로 실행 가능

 

 

 

 


✅ Step 처리 방식(Tasklet vs Chunk)

 

스프링 배치에서 배치 작업을 구현하는 방식은 Chunk 기반Tasklet 기반 두 가지가 있습니다.

각 방식의 차이점을 이해하고, 적절한 상황에서 선택하는 것이 중요합니다.

 

 

 


Tasklet 기반 배치란?

Tasklet 기반(batch-oriented processing)은 단순한 작업을 하나의 메서드 안에서 처리하는 방식입니다.

주로 한 번 실행되고 끝나는 배치 작업에 사용됩니다.

 

📌 Tasklet 기반 배치의 실행 흐름

  1. execute() 메서드에서 직접 로직 실행
  2. 반복 작업이 필요하다면 반복문을 통해 직접 제어
  3. 트랜잭션 범위를 Tasklet 전체로 관리

 

Tasklet 기반 배치 코드 예제

@Configuration
@EnableBatchProcessing
public class TaskletBasedBatchConfig {

    @Bean
    public Job taskletJob(JobRepository jobRepository, Step taskletStep) {
        return new JobBuilder("taskletJob", jobRepository)
                .start(taskletStep)
                .build();
    }

    @Bean
    public Step taskletStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) 
    {
        return new StepBuilder("taskletStep", jobRepository)
                .tasklet(new SimpleTasklet(), transactionManager)
                .build();
    }
    
}

 

@Slf4j
@RequiredArgsConstructor
@Component
public class SimpleTasklet implements Tasklet {

    private final SomeService someService;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
        log.info("Tasklet 배치 실행 시작");

        // 비즈니스 로직 수행
        someService.processData();

        log.info("Tasklet 배치 실행 완료");
        return RepeatStatus.FINISHED;
    }
}

 

 

 

📌 Tasklet 기반 배치 장점

간단한 작업에 적합 → 단순 로직 실행 시 코드가 간결해짐

Chunk 단위 트랜잭션이 필요 없는 작업에 적합

 

단점

  • 대량 데이터 처리에 적합하지 않음 (별도로 반복문을 구현해야 함)
  • 트랜잭션을 세밀하게 관리하기 어려움

 

 

 


 

Chunk 기반 배치란?

Chunk 기반(batch-oriented processing)은 데이터를 작은 덩어리(Chunk)로 나누어 처리하는 방식입니다.

Spring Batch에서 가장 일반적인 방식으로 사용되며, ItemReader → ItemProcessor → ItemWriter 단계를 거칩니다.

 

📌 Chunk 기반 배치의 실행 흐름

  1. ItemReader → 데이터 읽기
  2. ItemProcessor → 데이터 가공 (선택적)
  3. ItemWriter → 처리된 데이터 저장
  4. Chunk 단위로 반복하여 처리됨 (예: 100개씩)

 

 

Chunk 기반 배치 코드 예제

@Configuration
@EnableBatchProcessing
public class ChunkBasedBatchConfig {

    @Bean
    public Job chunkJob(JobRepository jobRepository, Step chunkStep) {
        return new JobBuilder("chunkJob", jobRepository)
                .start(chunkStep)
                .build();
    }

    @Bean
    public Step chunkStep(JobRepository jobRepository, PlatformTransactionManager                transactionManager) {
        return new StepBuilder("chunkStep", jobRepository)
                .<String, String>chunk(10, transactionManager)  // 한 번에 10개씩 처리
                .reader(itemReader())
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

    @Bean
    public ItemReader<String> itemReader() {
        return new ListItemReader<>(List.of("A", "B", "C", "D", "E"));
    }

    @Bean
    public ItemProcessor<String, String> itemProcessor() {
        return item -> "Processed " + item;
    }

    @Bean
    public ItemWriter<String> itemWriter() {
        return items -> items.forEach(System.out::println);
    }
}

 

 

Chunk 기반 배치 장점

데이터를 Chunk(예: 100개 단위)로 처리 → 성능 최적화

트랜잭션 단위로 관리 가능 → 특정 Chunk에서 실패 시 해당 Chunk 롤백 가능

Spring Batch에서 기본적으로 권장하는 방식

 

단점

  • 단순한 배치 작업에는 오버엔지니어링이 될 수 있음
  • ItemReader, ItemProcessor, ItemWriter 구현이 필요하여 코드가 길어질 수 있음

 

 

 


 

🔹 Tasklet vs Chunk 주요 차이점

비교 항목  Tasklet 기반 배치 Chunk 기반 배치
처리 방식 하나의 메서드 안에서 모든 작업 수행 데이터를 Chunk 단위(예: 100개)로 읽고 처리
적합한 작업 단순 반복 없는 작업 (예: 특정 테이블 정리, API 호출) 대량의 데이터를 읽고, 변환하고 저장하는 작업
트랜잭션 단위 전체 Tasklet이 하나의 트랜잭션으로 실행 Chunk 단위(예: 100개씩)로 커밋
실패 시 복구 실패 시 전체 트랜잭션 롤백 특정 Chunk만 롤백 후 이어서 실행 가능
구현 복잡도 코드가 간단하며 구현이 쉬움 Reader, Processor, Writer를 분리해야 해서 코드가 길어질 수 있음
성능 대량 데이터 처리에 비효율적 대량 데이터 처리에 최적화

 

 

 

 

 


✅ 언제 어떤 방식을 선택해야 할까?

 

📌 Tasklet 기반 배치를 선택해야 하는 경우

  • 데이터 처리가 아니라, 단순한 작업을 수행할 때
  • 한 번 실행되고 종료되는 배치 작업 (예: 특정 테이블 정리, 특정 API 호출)
  • 트랜잭션을 한 번만 걸면 충분한 경우

📌 Chunk 기반 배치를 선택해야 하는 경우

  • 대량 데이터를 다룰 때 (예: 1000만 개 이상의 데이터를 배치 처리)
  • ItemReader → ItemProcessor → ItemWriter 형태의 구조가 필요한 경우
  • Chunk 단위(예: 100개)로 트랜잭션을 관리하고 싶을 때
  • 특정 데이터에서 오류가 발생해도, 나머지 데이터를 처리하고 싶을 때

 

 

 


Tasklet 기반 배치를 Chunk 기반 배치처럼 사용하는 법

 

Tasklet + 비동기 예시 -> Tasklet을 커스터마이징하여 Chunk 방식처럼 데이터를 나눠서 배치를 진행할 수 있습니다.

 

LeaveAdjustmentTaskConfig

@Slf4j
@RequiredArgsConstructor
@Component
public class LeaveAdjustmentTaskConfig {

    private static final String LEAVE_ADJUSTMENT_JOB_NAME = "LeaveAdjustmentJob";
    private static final String LEAVE_ADJUSTMENT_STEP_NAME = "LeaveAdjustmentStep";

    private final LeaveAdjustmentTasklet tasklet;

    // 연차 및 기타휴가 조정
    @Bean(name = LEAVE_ADJUSTMENT_JOB_NAME)
    public Job leaveAdjustmentJob(JobRepository jobRepository, @Qualifier(LEAVE_ADJUSTMENT_STEP_NAME) Step taskletStep) {
        return new JobBuilder(LEAVE_ADJUSTMENT_JOB_NAME, jobRepository).start(taskletStep).build();
    }

    // 연차 및 기타휴가 조정
    @Bean(name = LEAVE_ADJUSTMENT_STEP_NAME)
    public Step leaveAdjustmentStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder(LEAVE_ADJUSTMENT_STEP_NAME, jobRepository).tasklet(tasklet, transactionManager).build();
	}

}

 

 

LeaveAdjustmentTasklet

@Slf4j
@RequiredArgsConstructor
@Component
public class LeaveAdjustmentTasklet implements Tasklet {

    private final BatchScheduleService batchScheduleService;
    private final BatchUserAnnualLeaveService batchUserAnnualLeaveService;

    /** 연차 조정*/
    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
        SpringBatchInfo batchInfo = BatchLoggingUtils.buildSpringBatchInfo(contribution, batchScheduleService.getBatchExecuteTime(contribution));
        LocalDate today = batchInfo.getBatchTime().toLocalDate();
        batchUserAnnualLeaveService.processLeaveAdjustmentInBatches(today, batchInfo);
        return RepeatStatus.FINISHED;
    }

}

 

Job, Steb, excute 까지는 기존Tasklet 방식과 동일

 

 

BatchUserLeaveService

/** 
 * 모든 회사에 대한 연차 및 기타휴가 조정
 */
  public void processLeaveAdjustmentInBatches(LocalDate today, SpringBatchInfo batchInfo) {
      AsyncJoinUtils asyncJoinUtil = new AsyncJoinUtils();
      List<Company> companyList = ...;
      
      int index = 0;
      for (Company company : companyList) {
          index++;
          log.info("[{}/{}] company index : jobName={}, companyUuid={}", index, 
              companyList.size(), batchInfo.getJobName(), company.getUuid());
          asyncJoinUtil.addAsyncJobs(
              CompletableFuture.supplyAsync(() -> 
                  batchUserLeaveAdjustmentProcessService.processAdjustLeaveByCompany(
                      company.getId(), asyncJoinUtil, batchInfo, today)));
      }
      asyncJoinUtil.waitAsyncJobsEnd();
      
  }

 

예시에서는 회사별로 processAdjustLeaveByCompany 함수 비동기로 실행합니다.

-> 회사가 10개면 각 회사에 쓰레드를 부여해서 진행하므로 부하를 줄일 수 있습니다.

 

 

BatchUserLeaveAdjustmentProcessService

/**
 * 각 회사에 대한 연차 조정 및 기타휴가 부여
 */
 @Transactional(propagation = Propagation.REQUIRES_NEW)
 public boolean processAdjustLeaveByCompany(Long companyId, AsyncJoinUtils asyncJoinUtil, 
     SpringBatchInfo batchInfo, LocalDate today) {

     // 적용 대상자 조회
     List<LeaveUserAdjustmentHistoryDto> userDtoList = ...

     for (LeaveUserAdjustmentHistoryDto userDto : userDtoList) {
         로직 ...
     }
     return true;
 }

 

 

 

 

 

정리: Tasklet 기반 배치도 비동기를 사용하면 Chunk 기반 배치처럼 데이터를 나눠서 처리할 수 있고 트랜잭션도 따로 관리할 수 있습니다.

 

 

참고: dkswnkkSpring Batch란? 간단한 개념과 코드 살펴보기

 

'웹 개발 > Back End' 카테고리의 다른 글

이벤트 리스너 사용법  (0) 2024.09.17
Java 헷갈리는 것들 모음  (0) 2024.09.16
마이바티스 사용법  (0) 2024.09.15
@Mapper 사용법(Dto -> 엔티티 매핑)  (0) 2024.09.09
스프링 시큐리티 개념  (0) 2024.05.19

댓글