[스프링] 스프링 배치 - 도메인 용어 및 개념 정리
배치 처리
- 배치 처리는 일반적으로 스케줄러에 의해 특정 시간대에 주기적으로 작동하는 것이 일반적이지만 요청을 통해서도 시작될 수 있다.
- 보통 대용량 데이터를 대상으로 수행된다.
- 로우 당 처리 시간이 일정하면 전체 배치 처리 시간도 예상 가능하다.
스프링 배치
- 스프링 배치: 스프링에 특화된 배치 처리 솔루션
- 스프링 배치는 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)과 같은, 모든 스텝에 대한 전역 속성을 구성할 수 있게 해준다.
- 잡 구성이 포함하는 세 가지
- 잡의 단순한 이름
- 스텝 인스턴스들의 정의와 순서 정의
- 잡이 재시작 가능한지 아닌지에 대한 정의
JobInstance
JobInstance
(잡 인스턴스)는 논리적인 잡 실행 개념을 말한다.- 하나의 잡에 대한 논리적인 잡 인스턴스가 존재한다.
- 잡이 실행될 때마다
JobInstance
가 생성된다. - 하나의 잡에 대해 잡 각각의 개별적인 실행은 서로 분리되어 추적되어야 한다.
- 하루에 한 번 실행되는 잡의 예
- 잡이 어제와 오늘 실행된다고 하면
Job
은 하나이며JobInstance
는 두 개이다.
- 잡이 어제와 오늘 실행된다고 하면
- 각
JobInstance
는 여러 번 실행할 수 있다. JobInstance
는JobParameter
를 식별하며, 주어진 시간에 하나만 실행할 수 있다.JobInstance
의 정의는 읽어올 데이터와 전혀 관련이 없다.- 데이터를 불러오는 방식을 결정하는 것은 전적으로
ItemReader
구현에 달려있다. - 잡이 어떤 데이터를 불러와서 처리할지에 대한 결정은 비즈니스 결정일 가능성이 높기 때문에
ItemReader
에 달려있다.
- 데이터를 불러오는 방식을 결정하는 것은 전적으로
- 잡 실행 시 잡의 이전 실행의 ‘상태’인
ExecutionContext
(실행 컨텍스트)가 사용되는지 여부는 동일한JobInstance
를 사용할지에 따라 달려있다.- 새로운
JobInstance
를 사용한다는 것은 ‘처음부터 시작’을 의미하고, 기존JobInstance
를 사용하는 것은 일반적으로 ‘중단한 곳에서 시작’을 의미한다.
- 새로운
JobInstance
와Job
의 차이: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
속성Status
: 잡 실행 상태를 나타내는BatchStatus
객체- 실행 중인 경우
STARTED
- 도중에 실패한 경우
FAILED
- 성공적으로 완료된 경우
COMPLETE
- 실행 중인 경우
startTime
: 실행이 시작된 때의 현재 시스템 시간을 나타내는java.util.Date
- 잡이 시작되지 않은 경우 빈 값이다.
endTime
: 실행 성공 여부와 관계 없이 실행이 종료된 때의 현재 시스템 시간을 나타내는java.util.Date
- 잡이 완료되지 않은 경우 빈 값
exitStatus
: 잡 실행 결과를 나타내는ExitStatus
객체- 호출자로부터 반환되는 종료 코드를 포함하고 있어 가장 중요하다.
- 잡이 완료되지 않은 경우 빈 값이다.
createTime
:JobExecution
이 처음 지속되기 시작한 현재 시스템 시간을 나타내는java.util.Date
- 아직 시작되지 않은 잡(시작 시간이 없음)도 잡 수준의
ExecutionContexts
를 관리하기 위해 항상createTime
은 가지고 있다.
- 아직 시작되지 않은 잡(시작 시간이 없음)도 잡 수준의
lastUpdated
:JobExecution
이 지속된 마지막 시간을 나타내는java.util.Date
- 잡이 아직 시작되지 않은 경우 빈 값이다.
executionContext
: 잡의 실행 사이에 지속되어야 하는 유저 데이터를 포함하는 속성 저장 객체failureExceptions
: 잡을 실행하는 동안 발생한 예외 목록- 잡이 실패하는 동안 하나 이상의 예외가 발생한 경우 유용하다.
- 이러한 속성들은 잡이 실행되는 동안 지속되고 잡의 실행 상태를 결정하는 데 사용되기 때문에 중요하다.
- 두 잡이 동일한 데이터에 접근하려고 시도함으로써 데이터베이스 수준에서 락 관련된 문제를 일으킬 가능성이 없는 한,
JobInstance
를 하나씩 차례로 실행해야 한다는 요구 사항은 없다. - 작업을 언제 실행해야 하는지 결정하는 것은 전적으로 스케줄러에 달려 있다.
- 서로 다른 별도의
JobInstance
에 대해서 스프링 배치는 동시 실행을 중지하려는 시도를 하지 않는다.- 이미 실행 중인
JobInstance
와 동일한JobInstance
을 실행하려고 하는 경우JobExecutionAlreadyRunningException
예외가 발생한다.
- 이미 실행 중인
Step
Step
(스텝)은 배치 잡의 독립적이고 순차적인 단계를 캡슐화하는 도메인 객체이다. 모든 잡은 전적으로 하나 이상의 스텝들로 구성되어 있다. 스텝에는 실제 배치 처리를 정의하고 제어하는데 필요한 모든 정보가 포함되어 있다.
- 주어진 스텝의 내용은 개발자가 작성하는 방법에 달려 있으므로 스텝은 개발자가 원하는 만큼 간단하거나 복잡할 수 있다.
Job
과JobExection
의 관계처럼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
객체도 생성되지 않는다. StepExecution
은Step
이 실제로 시작될 때만 생성된다.
- 그러나, 이전 스텝이 실패하면 스텝 실행은 지속되지 않으며
- 스텝 실행은
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
으로 인식하지만 Job
과 JobParameter
는 동일하므로 프레임워크는 동일한 JobInstance
로 인식한다. 동일한 JobInstance
인 경우 첫 번째 잡(실패한 잡) 실행 시 데이터를 어디까지 처리했는지와 같은 정보를 담고 있는 동일한 ExecutionContext
객체를 데이터베이스에서 꺼내 스텝에 전달한다. 반대로, 01-02 날짜의 잡 실행의 경우, 프레임워크는 해당 JobInstance
는 01-01 날짜의 잡 실행과 다른 JobInstance
로 인식하므로 아무런 키/값 데이터가 없는 빈 컨텍스트가 스텝에 전달되어야 한다. 올바른 시점에 상태(컨텍스트 정보)가 주어지도록 하기 위해 이러한 유형의 결정이 많이 있다.
잡 실행 시점에 StepExecution
당 정확히 하나의 ExecutionContext
가 존재한다는 점을 유의해야 한다. ExecutionContext
의 메서드를 사용하여 ExecutionContext
에 값을 저장하는 코드 작성은 공유된 키 공간을 만들기 때문에 주의해야 한다. 결과적으로, 데이터를 덮어쓰지 않도록 키에 대한 값을 넣을 때 주의를 기울여야 한다. 그러나, 스텝은 자동으로 컨텍스트에 어떤 데이터를 전혀 저장하지 않으므로 프레임워크에 부정적인 영향을 미치는 경우는 없다.
모든 StepExecution
에 대해 ExecutionContext
가 하나씩 존재하며, 따라서 JobExecution
당 적어도 하나의 ExecutionContext
가 존재한다. StepExecution
으로부터 얻은 ExecutionContext
와 JobExecution
으로부터 얻은 ExecutionContext
는 다르다. 스텝의 스코프로 지정된 ExecutionContext
는 한 스텝 내의 모든 커밋 시점에 저장되는 반면, 잡의 스코프로 지정된 ExecutionContext
는 모든 스텝 실행 사이에 저장된다.
JobRepository
JobRepository
(잡 저장소)는 스프링 배치 작업에 대한 메타데이터의 영속성을 위한 객체이다. JobLauncher
, Job
및 Step
구현을 위한 CRUD 작업을 제공한다.
잡이 처음 실행되면 JobRepository
로부터 JobExecution
객체를 얻을 수 있다. 잡 실행 도중 JobExecution
및 StepExecution
구현체가 JobRepository
에 전달되면 스텝 및 잡 실행에 관련된 데이터가 데이터베이스에 저장된다.
참고
- 스프링 배치 공식 문서 - 배치 도메인 언어
- 스프링 5 레시피 (4판) : 스프링 애플리케이션 개발에 유용한 161가지 문제 해결 기법 (전2권) / 마틴 데니엄, 다니엘 루비오, 조시 롱 공저/이일웅 역
Comments