Posts 스프링 배치 핵심 개념 살펴보기
Post
Cancel

스프링 배치 핵심 개념 살펴보기

들어가기 전


해당 글은 스프링 배치 공식 문서의 내용을 공부하면서 정리한 글입니다.

일반적인 배치 구조


  • 아래 다이어그램은 수십 년 동안 사용된 배치 참조 아키텍처를 단순하게 표현한 것으로, 배치 처리에 필요한 구성 요소의 개요를 보여준다.
  • Job에는 하나의 단계부터 여러 단계가 있으며, 각 단계에는 하나의 ItemReader, ItemProcessor, ItemWriter가 있다.
  • JobLauncher를 사용하여 작업을 시작하고, 현재 실행 중인 프로세스에 대한 메타데이터를 JobRepository에 저장해야 한다.

image

Job


Job은 전체 배치 프로세스를 캡슐화하는 엔티티이다.

  • 다른 Spring 프로젝트와 마찬가지로 Job은 XML 구성 파일 또는 Java 기반 구성과 함께 연결된다.
  • Job은 다음 다이어그램과 같이 전체 계층의 맨 위에 있다.

image

  • Job은 Step 인스턴스를 위한 컨테이너 역할을 한다.
    • 즉, 논리적으로 함께 속하는 여러 Step을 결합하고 ‘재시작 가능성’과 같은 모든 단계에 대한 속성을 전체적으로 구성할 수 있다.
  • Job에 대한 속성에는 다음이 포함된다.
    • Job의 간단한 이름
    • Step 인스턴스의 정의 및 순서
    • Job을 재시작할 수 있는지 여부
  • Java 구성을 사용하는 경우, Spring Batch는 Job 인터페이스 위에 일부 표준 기능을 더한 SimpleJob 클래스 형태로 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.springframework.batch.core;

import org.springframework.lang.Nullable;

public interface Job {
    String getName();

    boolean isRestartable();

    void execute(JobExecution var1);

    @Nullable
    JobParametersIncrementer getJobParametersIncrementer();

    JobParametersValidator getJobParametersValidator();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package org.springframework.batch.core.job;

public class SimpleJob extends AbstractJob {
    private List<Step> steps;

    public SimpleJob() {
        this((String)null);
    }

    public SimpleJob(String name) {
        super(name);
        this.steps = new ArrayList();
    }

    public void setSteps(List<Step> steps) {
        this.steps.clear();
        this.steps.addAll(steps);
    }

    public Collection<String> getStepNames() {
        List<String> names = new ArrayList();
        Iterator var2 = this.steps.iterator();

        while(var2.hasNext()) {
            Step step = (Step)var2.next();
            names.add(step.getName());
            if (step instanceof StepLocator) {
                names.addAll(((StepLocator)step).getStepNames());
            }
        }

        return names;
    }

    public void addStep(Step step) {
        this.steps.add(step);
    }

    public Step getStep(String stepName) {
        Iterator var2 = this.steps.iterator();

        while(var2.hasNext()) {
            Step step = (Step)var2.next();
            if (step.getName().equals(stepName)) {
                return step;
            }

            if (step instanceof StepLocator) {
                Step result = ((StepLocator)step).getStep(stepName);
                if (result != null) {
                    return result;
                }
            }
        }

        return null;
    }

    protected void doExecute(JobExecution execution) throws JobInterruptedException, JobRestartException, StartLimitExceededException {
        StepExecution stepExecution = null;
        Iterator var3 = this.steps.iterator();

        while(var3.hasNext()) {
            Step step = (Step)var3.next();
            stepExecution = this.handleStep(step, execution);
            if (stepExecution.getStatus() != BatchStatus.COMPLETED) {
                break;
            }
        }

        if (stepExecution != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Upgrading JobExecution status: " + stepExecution);
            }

            execution.upgradeStatus(stepExecution.getStatus());
            execution.setExitStatus(stepExecution.getExitStatus());
        }

    }
}

  • 자바 기반 구성을 사용할 때, 아래의 예와 같이 Job의 인스턴스화에 빌더 컬렉션을 사용할 수 있다.
1
2
3
4
5
6
7
8
@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}
1
2
3
4
5
6
7
8
9
@Bean
public Job importUserJob(JobCompletionNotificationListener listener, Step step1) {
    return jobBuilderFactory.get("importUserJob")
            .incrementer(new RunIdIncrementer())
            .listener(listener)
            .flow(step1)
            .end()
            .build();
}

JobInstance


JobInstance는 논리적 작업 실행 단위이다.

  • 위에서 살펴본 다이어그램의 ‘EndOfDay’ 작업과 같이 하루가 끝날 때 한 번 실행해야 하는 배치 작업을 생각해보자.
    • ‘EndOfDay’ 작업이 하나 있지만 각 작업 실행은 개별적으로 추적해야 한다.
    • 이러한 경우, 하루에 하나의 논리적 JobInstance가 있다.
      • 예를 들어 1월 1일 실행, 1월 2일 실행 등이 있다.
      • 1월 1일 실행이 처음에 실패하고 다음날 다시 실행되더라도 여전히 1월 1일 실행이다.(보통 1월 1일 실행이 1월 1일 데이터를 처리한다는 의미).
      • 따라서 각 JobInstance는 여러 번의 실행을 가질 수 있으며, 특정 JobParameters를 식별하는 JobInstance 하나만 한 번에 실행할 수 있다.
  • JobInstance는 로드할 데이터와 전혀 관련이 없다.
    • 데이터가 로드되는 방법을 결정하는 것은 전적으로 ItemReader 구현에 달려 있다.
  • 예를 들어 EndOfDay 시나리오에서는 데이터에 데이터가 속한 ‘유효 날짜’ 또는 ‘예약 날짜’를 나타내는 컬럼이 있을 수 있다.
    • 위 케이스의 경우, 1월 1일 런은 1월 1일 데이터만 로드하고 1월 2일 런은 2일 데이터만 로드한다.
    • 이러한 결정은 비즈니스 관점에서의 결정일 가능성이 높기 때문에, ItemReader의 구현에 달려 있다.
  • 동일한 JobInstance를 사용하면 이전 실행의 ‘상태’(ExecutionContext)가 사용되는지 여부가 결정된다.
  • 새로운 JobInstance를 사용하는 것은 ‘처음부터 시작’을 의미하며, 기존 인스턴스를 사용하는 것은 일반적으로 ‘멈춘 곳부터 시작’을 의미한다.

JobParameters


This post is licensed under CC BY 4.0 by the author.