300===Dev Framework/Spring Batch

Spring Batch Step & Status 완벽 가이드 📊

블로글러 2024. 11. 7. 09:32

안녕하세요! 오늘은 Spring Batch의 Step 생명주기와 상태값 처리에 대해 자세히 알아보겠습니다.

Step의 생명주기 🔄

1. 기본 상태값 흐름

STARTING -> STARTED -> COMPLETED
                   -> FAILED
                   -> STOPPED

2. Step 상태값 정의

public enum BatchStatus {
    COMPLETED,  // 성공적 완료
    STARTING,   // 시작 준비
    STARTED,    // 실행 중
    STOPPING,   // 중지 진행 중
    STOPPED,    // 중지됨
    FAILED,     // 실패
    ABANDONED,  // 포기됨
    UNKNOWN     // 알 수 없음
}

Step 구현과 상태 처리 🛠

1. 기본 Step 구성

@Configuration
@RequiredArgsConstructor
public class StepConfig {
    private final StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step sampleStep() {
        return stepBuilderFactory.get("sampleStep")
            .<InputData, OutputData>chunk(100)
            .reader(reader())
            .processor(processor())
            .writer(writer())
            .faultTolerant()
            .skipLimit(3)
            .skip(Exception.class)
            .listener(new StepExecutionListener() {
                @Override
                public void beforeStep(StepExecution stepExecution) {
                    stepExecution.setStatus(BatchStatus.STARTED);
                }

                @Override
                public ExitStatus afterStep(StepExecution stepExecution) {
                    if (stepExecution.getStatus() == BatchStatus.COMPLETED) {
                        return ExitStatus.COMPLETED;
                    }
                    return ExitStatus.FAILED;
                }
            })
            .build();
    }
}

2. 상세 상태 처리

@Component
public class DetailedStepExecutionListener implements StepExecutionListener {

    @Override
    public void beforeStep(StepExecution stepExecution) {
        // 초기 상태값 설정
        stepExecution.getExecutionContext().putString("status", "INITIALIZING");
        // 시작 시간 기록
        stepExecution.getExecutionContext().putString("startTime", 
            LocalDateTime.now().toString());
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        // 처리된 아이템 수 확인
        int readCount = stepExecution.getReadCount();
        int writeCount = stepExecution.getWriteCount();
        int skipCount = stepExecution.getSkipCount();

        // 성공 조건 검증
        if (readCount == writeCount && skipCount == 0) {
            return new ExitStatus("COMPLETED_PERFECT");
        } else if (skipCount > 0) {
            return new ExitStatus("COMPLETED_WITH_SKIPS");
        }

        return ExitStatus.FAILED;
    }
}

주요 상태값 처리 예시 💡

1. 조건부 Step 실행

@Bean
public Job conditionalJob() {
    return jobBuilderFactory.get("conditionalJob")
        .start(firstStep())
        .on("COMPLETED").to(successStep())
        .from(firstStep())
        .on("FAILED").to(recoveryStep())
        .from(firstStep())
        .on("*").to(defaultStep())
        .end()
        .build();
}

2. 커스텀 상태값 처리

public class CustomExitStatus {
    public static final ExitStatus COMPLETED_WITH_WARNING = 
        new ExitStatus("COMPLETED_WITH_WARNING");
    public static final ExitStatus NEED_RETRY = 
        new ExitStatus("NEED_RETRY");
}

@Component
public class CustomStepExecutionListener implements StepExecutionListener {

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        if (someWarningCondition()) {
            return CustomExitStatus.COMPLETED_WITH_WARNING;
        } else if (needsRetry()) {
            return CustomExitStatus.NEED_RETRY;
        }
        return ExitStatus.COMPLETED;
    }
}

3. 재시도 로직

@Bean
public Step retryableStep() {
    return stepBuilderFactory.get("retryableStep")
        .<InputData, OutputData>chunk(10)
        .reader(reader())
        .processor(processor())
        .writer(writer())
        .faultTolerant()
        .retryLimit(3)
        .retry(TemporaryFailureException.class)
        .listener(new RetryListener() {
            @Override
            public <T, E extends Throwable> boolean open(RetryContext context, 
                RetryCallback<T, E> callback) {
                return true;
            }

            @Override
            public <T, E extends Throwable> void close(RetryContext context, 
                RetryCallback<T, E> callback, Throwable throwable) {
                // 재시도 완료 후 처리
            }

            @Override
            public <T, E extends Throwable> void onError(RetryContext context, 
                RetryCallback<T, E> callback, Throwable throwable) {
                // 에러 발생 시 처리
            }
        })
        .build();
}

상태값 모니터링과 로깅 📝

1. Step 실행 메트릭 수집

@Component
@Slf4j
public class MetricStepExecutionListener implements StepExecutionListener {

    @Override
    public void beforeStep(StepExecution stepExecution) {
        log.info("Step {} starting - Job Execution ID: {}", 
            stepExecution.getStepName(),
            stepExecution.getJobExecutionId());
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        log.info("Step {} completed with status: {}", 
            stepExecution.getStepName(),
            stepExecution.getStatus());

        log.info("Metrics - Read: {}, Written: {}, Filtered: {}, Skipped: {}", 
            stepExecution.getReadCount(),
            stepExecution.getWriteCount(),
            stepExecution.getFilterCount(),
            stepExecution.getSkipCount());

        return stepExecution.getExitStatus();
    }
}

2. 상태값 데이터베이스 저장

@Component
@RequiredArgsConstructor
public class StepStatusPersistenceListener implements StepExecutionListener {

    private final StepExecutionRepository repository;

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        StepExecutionStatus status = StepExecutionStatus.builder()
            .stepName(stepExecution.getStepName())
            .status(stepExecution.getStatus().toString())
            .readCount(stepExecution.getReadCount())
            .writeCount(stepExecution.getWriteCount())
            .startTime(stepExecution.getStartTime())
            .endTime(stepExecution.getEndTime())
            .build();

        repository.save(status);
        return stepExecution.getExitStatus();
    }
}

실전 예시: 복잡한 상태 처리 🎯

1. 조건부 처리 파이프라인

@Configuration
public class ComplexStepConfig {

    @Bean
    public Step complexStep() {
        return stepBuilderFactory.get("complexStep")
            .<InputData, OutputData>chunk(100)
            .reader(reader())
            .processor(compositeProcessor())
            .writer(writer())
            .listener(new StepExecutionListener() {
                @Override
                public ExitStatus afterStep(StepExecution stepExecution) {
                    if (stepExecution.getSkipCount() > threshold) {
                        return new ExitStatus("TOO_MANY_SKIPS");
                    }
                    return stepExecution.getExitStatus();
                }
            })
            .build();
    }

    @Bean
    public CompositeItemProcessor<InputData, OutputData> compositeProcessor() {
        List<ItemProcessor<InputData, OutputData>> delegates = Arrays.asList(
            validationProcessor(),
            transformationProcessor(),
            enrichmentProcessor()
        );

        CompositeItemProcessor<InputData, OutputData> processor = 
            new CompositeItemProcessor<>();
        processor.setDelegates(delegates);

        return processor;
    }
}

References 📚

  1. Spring Batch Official Documentation - Step Processing
    https://docs.spring.io/spring-batch/docs/current/reference/html/step.html

  2. Spring Batch - Step Execution
    https://docs.spring.io/spring-batch/docs/current/reference/html/domain.html#stepExecution

  3. Baeldung - Spring Batch Processing
    https://www.baeldung.com/spring-batch-processing-logic

728x90