[스프링] 스프링 배치 - 도메인 용어 및 개념 정리

배치 처리

  • 배치 처리는 일반적으로 스케줄러에 의해 특정 시간대에 주기적으로 작동하는 것이 일반적이지만 요청을 통해서도 시작될 수 있다.
  • 보통 대용량 데이터를 대상으로 수행된다.
  • 로우 당 처리 시간이 일정하면 전체 배치 처리 시간도 예상 가능하다.


스프링 배치

  • 스프링 배치: 스프링에 특화된 배치 처리 솔루션
  • 스프링 배치는 JSR-352(자바 플랫폼을 위한 배치 애플리케이션) 명세의 기준 구현체이다.
  • 스프링 배치의 잡 시동 메커니즘 종류: 명령줄, 유닉스 크론, 쿼츠, ESB 등


배치 처리를 위한 아키텍쳐 프레임워크

  • 잡(job)은 하나 또는 여러 스텝(step)을 가질 수 있다.
  • 각각의 스텝에는 정확히 하나의 ItemReader(항목 입력기), 하나의 ItemProcessor(항목 처리기) 및 하나의 ItemWriter(항목 출력기)가 존재한다.
  • 잡은 JobLauncher(잡 런처)를 통해 실행되어야 하며, 현재 실행 중인 프로세스에 대한 메타데이터를 JobRepository(잡 리포지토리)에 저장해야 한다.
  • https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png


Job

  • Job(잡)은 배치 전체 프로세스를 캡슐화하는 엔티티다.
  • 다른 스프링 프로젝트와 마찬가지로 잡은 XML 구성 파일 또는 자바 기반 구성과 연결되어 있다. 이 구성을 ‘잡 구성(job configuration)’이라고 한다.
  • 잡은 전체 계층 구조의 최상위에 해당한다.
  • 잡은 단순히 스텝의 컨테이너이다.
  • 잡은 논리적으로 함께 속해 있는 여러 스텝을 결합하고 재시작 가능성(restartability)과 같은, 모든 스텝에 대한 전역 속성을 구성할 수 있게 해준다.
  • 잡 구성이 포함하는 세 가지
    1. 잡의 단순한 이름
    2. 스텝 인스턴스들의 정의와 순서 정의
    3. 잡이 재시작 가능한지 아닌지에 대한 정의


JobInstance

  • JobInstance(잡 인스턴스)는 논리적인 잡 실행 개념을 말한다.
    • 하나의 잡에 대한 논리적인 잡 인스턴스가 존재한다.
  • 잡이 실행될 때마다 JobInstance가 생성된다.
  • 하나의 잡에 대해 잡 각각의 개별적인 실행은 서로 분리되어 추적되어야 한다.
  • 하루에 한 번 실행되는 잡의 예
    • 잡이 어제와 오늘 실행된다고 하면 Job은 하나이며 JobInstance는 두 개이다.
  • JobInstance는 여러 번 실행할 수 있다.
  • JobInstanceJobParameter를 식별하며, 주어진 시간에 하나만 실행할 수 있다.
  • JobInstance의 정의는 읽어올 데이터와 전혀 관련이 없다.
    • 데이터를 불러오는 방식을 결정하는 것은 전적으로 ItemReader 구현에 달려있다.
    • 잡이 어떤 데이터를 불러와서 처리할지에 대한 결정은 비즈니스 결정일 가능성이 높기 때문에 ItemReader에 달려있다.
  • 잡 실행 시 잡의 이전 실행의 ‘상태’인 ExecutionContext(실행 컨텍스트)가 사용되는지 여부는 동일한 JobInstance를 사용할지에 따라 달려있다.
    • 새로운 JobInstance를 사용한다는 것은 ‘처음부터 시작’을 의미하고, 기존 JobInstance를 사용하는 것은 일반적으로 ‘중단한 곳에서 시작’을 의미한다.
  • JobInstanceJob의 차이: Job의 실행을 구별짓고 각각의 Job 실행을 개별적으로 추적하기 위한 것이 JobInstance이다.
  • 하나의 JobInstance가 다른 JobInstance와 어떻게 구별되는지는 JobParameters(잡 파라미터)에 달려있다.


JobParameters

  • JobParameters는 배치 잡을 시작하는 데 사용되는 매개변수 집합을 가지고 있다.
  • JobParameters는 실행 중인 Job의 참조 데이터 또는 JobInstance의 식별 데이터로 사용할 수 있다.
    • JobParameters를 사용하여 현재 실행 중인 잡이 어떤 잡인지 구별한다.
    • JobParameters를 사용하여 서로 다른 JobInstance를 구별한다.
  • 하루에 한 번 실행되는 잡의 예
    • 하나의 잡은 두 개의 JobParameter 객체를 가진다. 어제 날짜와 오늘 날짜가 매개변수가 된다.
  • 따라서 ‘Job = JobInstance + 식별 JobParameters’ 관계를 가진다.
  • 전달되는 매개변수를 제어함으로써 JobInstance를 정의하고 서로 구별하는 방법을 제어할 수 있다.
  • JobInstance 식별을 위해 잡에 대해 매개변수가 항상 필요한 것은 아니다.


JobExecution

  • JobExecution(잡 실행)은 잡을 실행하려는 단일 시도에 대한 개념이다.
  • 잡의 실행은 성공하거나 실패할 수 있으며, 잡 실행이 성공적으로 완료되지 않은 경우 주어진 잡 실행에 해당하는 JobInstance는 완료된 것으로 간주되지 않는다.
  • 잡 실행의 성공 실패 여부를 떠나 잡이 실행될 때마다(실패한 잡의 재시도 실행도 포함된다) JobExecution은 새로 생성된다.
    • JobInstance를 한 번에 성공하는 경우 생성되는 JobExecution 개수는 하나이다. JobInstance를 여러 번 시도하여 성공하는 경우 잡 실행 횟수만큼 JobExecution가 생성된다.
  • 파라미터에 의해 서로 구별되는 JobInstance는 해당 파라미터를 통해 실행한 잡이 성공해야 완료된 것으로 간주된다.
  • 하루에 한 번 실행되는 잡의 예
    • 어제 실행되어야 하는 잡이 실패한 경우 동일한 식별 매개변수(어제 날짜)로 잡을 다시 실행하면 새로운 JobExecution이 생성되지만 식별 매개변수는 동일하기 때문에 JobInstance는 여전히 하나만 존재한다. 잡이 성공적으로 실행 완료되면 JobInstance는 완료된 것으로 간주된다.
  • Job은 수행해야 할 잡이 무엇이고 어떻게 실행될지를 정의하고, JobInstance는 올바른 재시작 의미 체계를 위해 잡의 실행들을 함께 그룹화하는 조직적 객체이다.
  • JobExecution은 잡이 실행되는 도중에 실제로 어떤 일이 일어났는지에 대한 주요 저장 메커니즘이며, 많은 제어가능한 속성들을 포함하고 있다.
  • JobExecution 속성
    1. Status: 잡 실행 상태를 나타내는 BatchStatus 객체
      • 실행 중인 경우 STARTED
      • 도중에 실패한 경우 FAILED
      • 성공적으로 완료된 경우 COMPLETE
    2. startTime: 실행이 시작된 때의 현재 시스템 시간을 나타내는 java.util.Date
      • 잡이 시작되지 않은 경우 빈 값이다.
    3. endTime: 실행 성공 여부와 관계 없이 실행이 종료된 때의 현재 시스템 시간을 나타내는 java.util.Date
      • 잡이 완료되지 않은 경우 빈 값
    4. exitStatus: 잡 실행 결과를 나타내는 ExitStatus 객체
      • 호출자로부터 반환되는 종료 코드를 포함하고 있어 가장 중요하다.
      • 잡이 완료되지 않은 경우 빈 값이다.
    5. createTime: JobExecution이 처음 지속되기 시작한 현재 시스템 시간을 나타내는 java.util.Date
      • 아직 시작되지 않은 잡(시작 시간이 없음)도 잡 수준의 ExecutionContexts를 관리하기 위해 항상 createTime은 가지고 있다.
    6. lastUpdated: JobExecution이 지속된 마지막 시간을 나타내는 java.util.Date
      • 잡이 아직 시작되지 않은 경우 빈 값이다.
    7. executionContext: 잡의 실행 사이에 지속되어야 하는 유저 데이터를 포함하는 속성 저장 객체
    8. failureExceptions: 잡을 실행하는 동안 발생한 예외 목록
      • 잡이 실패하는 동안 하나 이상의 예외가 발생한 경우 유용하다.
  • 이러한 속성들은 잡이 실행되는 동안 지속되고 잡의 실행 상태를 결정하는 데 사용되기 때문에 중요하다.
  • 두 잡이 동일한 데이터에 접근하려고 시도함으로써 데이터베이스 수준에서 락 관련된 문제를 일으킬 가능성이 없는 한, JobInstance를 하나씩 차례로 실행해야 한다는 요구 사항은 없다.
  • 작업을 언제 실행해야 하는지 결정하는 것은 전적으로 스케줄러에 달려 있다.
  • 서로 다른 별도의 JobInstance에 대해서 스프링 배치는 동시 실행을 중지하려는 시도를 하지 않는다.
    • 이미 실행 중인 JobInstance와 동일한 JobInstance을 실행하려고 하는 경우 JobExecutionAlreadyRunningException 예외가 발생한다.


Step

Step(스텝)은 배치 잡의 독립적이고 순차적인 단계를 캡슐화하는 도메인 객체이다. 모든 잡은 전적으로 하나 이상의 스텝들로 구성되어 있다. 스텝에는 실제 배치 처리를 정의하고 제어하는데 필요한 모든 정보가 포함되어 있다.

  • 주어진 스텝의 내용은 개발자가 작성하는 방법에 달려 있으므로 스텝은 개발자가 원하는 만큼 간단하거나 복잡할 수 있다.
  • JobJobExection의 관계처럼 Step은 각각 개별적인 StepExecution을 가지고 있다.
  • 스텝은 반복 실행 가능한 Tasklet으로 구성되거나 청크 지향 스텝으로 구성될 수 있다.
  • 스텝에는 트랜잭션을 적용할 수 있다. 해당 트랜잭션은 스프링의 선언적 트랜잭션 기능이 제공하는 것이 아닌 스프링 배치가 자체적으로 제공한다.


Tasklet

  • Tasklet(태스크릿)은 스텝 내에서 일어나는 반복 가능한 프로세스이다.
  • Tasklet 인터페이스는 하나의 execute() 메서드를 제공한다.
  • 기본적으로 execute() 메서드는 트랜잭션이 적용된다. 즉, 스텝이 Tasklet으로 구성되는 경우 execute() 메서드에 트랜잭션이 적용된다.
    • exexute() 메서드는 반복 실행 가능하며 각각의 실행은 독립적인 트랜잭션으로 처리된다.
    • exexute() 메서드 호출 시마다 새로운 트랜잭션이 생성된다.
  • Tasklet은 반복가능하다. 스프링 배치는 execute() 메서드가 RepeatStatus.CONTINUABLE를 반환하는 동안에는 execute() 메서드를 연속적(반복적)으로 호출한다. 이때 각각의 execute() 메서드 호출은 자신의 트랜잭션 내에서 수행된다. execute() 메서드가 RepeatStatus.FINISHED 또는 null을 반환하는 경우 스프링 배치는 execute() 메서드 호출을 중지하며 다음 스텝을 실행한다.
  • execute() 메서드가 실행될 때마다 새로운 트랜잭션이 생성되기 때문에 트랜잭션 적용이 필요하지 않은 경우 트랜잭션 전파 수준을 PROPAGATION_NEVER으로 변경할 수 있다.


StepExecution

  • StepExecution(스텝 실행)은 스텝을 실행하려는 단일 시도를 나타낸다.
  • JobExecution과 마찬가지로 스텝이 실행될 때마다 새로운 StepExecution 객체가 생성된다.
    • 그러나, 이전 스텝이 실패하면 스텝 실행은 지속되지 않으며 StepExecution 객체도 생성되지 않는다.
    • StepExecutionStep이 실제로 시작될 때만 생성된다.
  • 스텝 실행은 StepExecution 클래스의 객체로 표현된다.
  • 각각의 StepExecution에는 해당 스텝과 JobExecution에 대한 참조와 커밋 및 롤백 수, 시작 및 종료 시간과 같은 트랜잭션 관련 데이터가 포함되어 있다.
  • 또한, 각 StepExecution에는 잡을 다시 시작하는 데 필요한 통계나 상태 정보와 같이 개발자가 잡 실행에서 유지해야 하는 데이터를 가지고 있는 ExecutionContext가 포함되어 있다.
  • StepExecution 속성

ExecutionContext

ExecutionContext(실행 컨텍스트)는 StepExecution 객체 또는 JobExecution 객체 스코프 내에서 배치 처리와 관련된 영속 상태를 저장할 수 있도록 해주는 키/값 쌍의 컬렉션을 나타낸다. 컨텍스트라는 단어가 의미하듯 잡 실행 상태 및 환경과 관련된 객체이다. 쿼츠에 익숙한 사람들에게는 JobDataMap과 매우 유사하다.

ExecutionContext을 사용하는 예 중 하나는 잡의 재시작을 위해 사용하는 것이다. 예를 들어, 플랫 파일의 입력 레코드의 개별 라인을 처리하는 동안, 프레임워크는 주기적으로 커밋 지점에서 ExecutionContext 객체를 영속화한다. 이렇게 하면 잡 실행 중에 치명적인 오류가 발생하거나 시스템의 전원이 꺼지는 경우에도 ItemReader는 상태(데이터를 어디까지 읽고 커밋하였는지)를 저장할 수 있다. 이를 위해 필요한 것은 다음과 같이 ItemReader가 읽은 현재 라인의 수를 컨텍스트 객체에 값으로 넣는 것이며, 나머지 작업은 프레임워크가 수행한다.

executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());


파일의 예에서 스텝이 처리한 레코드 수와 관련된 데이터는 BATCH_STEP_EXECUTION_CONTEXT 테이블의 SHORT_CONTEXT 컬럼에 {piece.count=40321}의 키/값 형태로 저장되며 이 값은 각각의 커밋 시점에 갱신된다.

실패한 잡이 재시작될 때 마지막 잡 실행의 ExecutionContext의 값은 데이터베이스에서 재구성된다. 다음과 같이 ItemReader가 레코드를 다시 읽는 처리를 할 때 컨텍스트에 저장된 상태가 있는지 확인함으로써 스텝이 마지막으로 처리한 레코드의 다음 레코드부터 읽도록 초기화할 수 있다.

if (executionContext.containsKey(getKey(LINES_READ_COUNT))) {
  log.debug("Initializing for restart. Restart data is: " + executionContext);

  long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT));

  LineReader reader = getReader();

  Object record = "";
  while (reader.getPosition() < lineCount && record != null) {
    record = readLine();
  }
}


잡 실행 자체에 대한 통계 정보 저장을 위해 ExecutionContext를 사용할 수도 있다. 예를 들어, 플랫 파일에 여러 라인에 걸쳐 처리해야하는 주문 정보 데이터가 존재하는 경우, 스텝이 종료될 때 처리된 총 주문 수 정보를 이메일에 담아 보낼 수 있도록 처리된 주문 수(읽은 라인 수와는 다를 것이다)를 저장해야 할 수도 있다.

스프링 배치 프레임워크는 개발자가 개별 JobInstance로 올바르게 스코프를 지정하기 위해 컨텍스트 정보를 저장하는 것을 처리한다. 잡 실행 시 이전 잡 실행에 관한 컨텍스트 정보를 담고 있는 기존 ExecutionContext를 사용해야 하는지 여부를 판단하는 것은 매우 어려울 수 있다. 예를 들어, EndOfDay를 사용하여 01-01 날짜에 잡 실행이 두 번째로 다시 시작되는 경우 첫 번째 실행의 StepExecution과는 다른 StepExecution으로 인식하지만 JobJobParameter는 동일하므로 프레임워크는 동일한 JobInstance로 인식한다. 동일한 JobInstance인 경우 첫 번째 잡(실패한 잡) 실행 시 데이터를 어디까지 처리했는지와 같은 정보를 담고 있는 동일한 ExecutionContext 객체를 데이터베이스에서 꺼내 스텝에 전달한다. 반대로, 01-02 날짜의 잡 실행의 경우, 프레임워크는 해당 JobInstance는 01-01 날짜의 잡 실행과 다른 JobInstance로 인식하므로 아무런 키/값 데이터가 없는 빈 컨텍스트가 스텝에 전달되어야 한다. 올바른 시점에 상태(컨텍스트 정보)가 주어지도록 하기 위해 이러한 유형의 결정이 많이 있다.

잡 실행 시점에 StepExecution 당 정확히 하나의 ExecutionContext가 존재한다는 점을 유의해야 한다. ExecutionContext의 메서드를 사용하여 ExecutionContext에 값을 저장하는 코드 작성은 공유된 키 공간을 만들기 때문에 주의해야 한다. 결과적으로, 데이터를 덮어쓰지 않도록 키에 대한 값을 넣을 때 주의를 기울여야 한다. 그러나, 스텝은 자동으로 컨텍스트에 어떤 데이터를 전혀 저장하지 않으므로 프레임워크에 부정적인 영향을 미치는 경우는 없다.

모든 StepExecution에 대해 ExecutionContext가 하나씩 존재하며, 따라서 JobExecution 당 적어도 하나의 ExecutionContext가 존재한다. StepExecution으로부터 얻은 ExecutionContextJobExecution으로부터 얻은 ExecutionContext는 다르다. 스텝의 스코프로 지정된 ExecutionContext는 한 스텝 내의 모든 커밋 시점에 저장되는 반면, 잡의 스코프로 지정된 ExecutionContext는 모든 스텝 실행 사이에 저장된다.


JobRepository

JobRepository(잡 저장소)는 스프링 배치 작업에 대한 메타데이터의 영속성을 위한 객체이다. JobLauncher, JobStep 구현을 위한 CRUD 작업을 제공한다.

잡이 처음 실행되면 JobRepository로부터 JobExecution 객체를 얻을 수 있다. 잡 실행 도중 JobExecutionStepExecution 구현체가 JobRepository에 전달되면 스텝 및 잡 실행에 관련된 데이터가 데이터베이스에 저장된다.


참고

Comments