토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 배치 공식 reference를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

목차


엔터프라이즈 도메인 어플리케이션에선 비지니스 운영에 필수적인 작업을 종종 벌크 프로세싱으로 개발한다. 예를 들어,

스프링 배치는 포괄적인 경량 배치 프레임워크로, 엔터프라이즈 시스템 운영에 일상적으로 꼭 필요한 견고한 배치 어플리케이션 개발을 위해 설계되었다. 스프링 배치는 사람들이 기대하는 Spring Framework의 특성(생산성, POJO 기반 접근, 사용 편의성)을 기반으로 만들어져, 개발자가 다루기 편하며 필요하다면 고급 기술 또한 쉽게 활용할 수 있다. 스프링 배치는 스케줄링 프레임워크가 아니다. 상업용이나 오픈 소스로 공개된 엔터프라이즈 수준 스케줄러는 여러 가지가 있다 (Quartz, Tivoli, Control-M 등). 스프링 배치는 스케줄러를 대체하는 개념이 아니라, 스케줄러와 함께 동작하도록 설계되었다.

스프링 배치는 로깅/추적, 트랜잭션 관리, job 프로세싱 통계, job 재시작, 스킵, 리소스 관리 같은 대용량 데이터 처리에 필수적인 기능을 재사용할 수 있는 형태로 제공한다. 다른 고급 기술도 제공하는데, 최적화나 파티셔닝 같은 기법을 사용하면 극단적으로 큰 데이터 처리나 고성능 배치도 쉽게 구현할 수 있다. 스프링 배치는 단순한 유스케이스(데이터베이스로 파일을 읽거나 저장 프로시저(stored procedure)를 실행하는 일)와 복잡한 대용량 처리(데이터베이스 간 대용량 데이터를 이동시키고 변형하는 일 등)를 모두 지원한다. 프레임워크를 활용하면 배치 작업을 손쉽게 확장할 수 있으므로 많은 양의 데이터도 처리할 수 있다.


1.1. Background

엔터프라이즈 IT 환경에서 이전부터 배치 처리를 사용해 왔는데도, 오픈 소스 프로젝트와 관련 커뮤니티에선 웹 기반 프레임워크와 마이크로 서비스 아키텍처에 관심을 쏟는 반면 자바 기반 배치 처리에서 요구되는 재사용 가능한 아키텍처 프레임워크에는 관심이 부족했다. 재사용 가능한 배치 아키텍처는 표준이 없어서 일회성으로 사내 솔루션을 개발하는 경우가 많았다.

이를 바꿔보고자 SpringSource(현재는 Pivotal)와 Accenture가 뭉쳤다. Accenture의 hands-on industry와 배치 아키텍처를 구현하면서 쌓은 기술적인 경험, 그리고 SpringSource의 깊이 있는 기술적 기반과 스프링의 입증된 프로그래밍 모델이 모여 자연스럽고 강력한 파트너십을 만들어냈고, 엔터프라이즈 자바와의 격차를 메우는 것을 목표로 하는, 시장 상황에 맞는 고품질 소프트웨어를 만들었다. 두 회사 모두 스프링 기반 배치 아키텍처 솔루션으로 유사한 이슈를 해결하고 있는 여러 클라이언트와 함께 했다. 이를 통해 유용한 세부 구현과 현실의 제약조건을 제공할 수 있었고, 이 솔루션으로 클라이언트가 실제로 겪는 문제를 해결할 수 있었다.

Accenture는 이전에 소유했던 배치 처리 아키텍처 프레임워크를 Spring Batch 프로젝트에 기증하면서 프로젝트를 지원하고 개선시킬 수 있는 커미터 리소스도 함께 넘겼다. Accenture가 기증한 프로젝트는 지난 몇 세대의 플랫폼(COBOL/Mainframe, C++/Unix, 지금은 Java/anywhere)으로 배치 아키텍처를 만들며 쌓은 수십 년 간의 경험을 바탕으로 만들어졌다.

Accenture와 SpringSource는 엔터프라이즈 수준의 배치 어플리케이션에 지속적으로 활용할 수 있는 소프트웨어 처리 방법, 프레임워크, 툴 표준화를 공동 목표로 삼았다. 엔터프라이즈 IT 환경에 신뢰할 수 있는 표준 솔루션을 제공하고자 하는 회사, 정부 기관은 Spring Batch가 도움이 될 것이다.


1.2. Usage Scenarios

전형적인 배치 프로그램은 보통:

스프링 배치는 유사한 트랜잭션을 하나로 묶어 처리해줌으로써 (특히 사용자 인터랙션 없는 오프라인 환경) 이런 반복적인 작업을 자동화한다. IT 프로젝트라면 대부분 배치 처리를 사용하는데, 스프링 배치는 강력한 엔터프라이즈 스케일 솔루션을 제공하는 유일한 오픈 소스 프레임워크다.

비지니스 시나리오(Business Scenarios)

기술 목표(Technical Objectives)


1.3. Spring Batch Architecture

스프링 배치는 확장성과 다양한 사용자 유형을 고려해 설계했다. 아래 그림은 확장성과 편의성을 지원하기 위한 계층 구조를 보여준다.

Spring Batch Layered Architecture

이 계층 구조는 세 주요 컴포넌트가 있다: Application, Core, Infrastructure. Application은 스프링 배치를 사용하는 개발자가 만드는 모든 배치 job과 커스텀 코드를 포함한다. Batch Core는 job을 실행하고 제어하는 데 필요한 핵심 런타임 클래스를 포함한다. 여기엔 JobLauncher, Job,Step 구현체도 포함된다. Application, Core 모두 공통 Infrastructure 위에서 빌드한다. 이 Infrastructure는 공통 reader와 writer, 서비스(RetryTemplate 같은)를 포함하는데, 어플리케이션 개발자도 사용하고(ItemReader, ItemWriter 등의 reader와 writer), 코어 프레임워크 자체에서 활용하기도 한다(자체 라이브러리인 retry).


1.4. General Batch Piplelines and Guidlines

배치 솔루션은 아래 있는 핵심 원칙, 가이드라인, 그 외 일반적인 것들을 함께 고려해 개발해야 한다.


1.5. Batch Processing Strategies

설계자의 배치 시스템 설계, 프로그래머의 구현을 돕기 위해 기본적인 배치 애플리케이션 구성 요소와 패턴 샘플을 구조 차트와 코드 쉘로 제공한다. 배치 job 설계를 시작하려면 비즈니스 로직을 다음과 같은 표준 구성 요소를 사용하는 일련의 step으로 나눠야 한다:

또한 앞에서 언급한 구성 요소로는 만들 수 없는 비즈니스 로직을 위한 기본 애플리케이션 쉘을 제공한다.

어플리케이션은 메인 구성 요소 외에도, 아래 같은 표준 유틸리티 step을 하나 이상 사용할 수 있다:

배치 애플리케이션을 입력 소스별로 분류할 수도 있다:

모든 배치 시스템은 프로세싱 전략이 토대가 된다. 사용할 전략은 예상 배치 시스템 볼륨, 온라인 시스템 또는 다른 배치 시스템과의 동시성, 사용 가능한 배치 윈도우 같은 요소를 고려해 선택한다. (무중단으로 가동하려는 기업이 많을수록 배치 윈도우를 확보하기 힘들어진다는 점에 주의해라).

배치를 위한 일반적인 처리 옵션은 다음과 같다 (구현 복잡도가 낮은 순) :

상업용 스케줄러는 이 옵션 중 일부 혹은 전체를 지원한다.

밑에서는 위 옵션을 좀 더 자세히 다룬다. 경험상으로는, 배치 처리 유형마다 채택해야 할 커밋과 잠금(locking) 전략이 다르며 온라인 잠금 전략도 같은 원칙을 사용해야 한다. 따라서 전반적인 배치 아키텍처를 설계하는 것은 간단하지만은 않다.

잠금 전략은, 일반적인 데이터베이스 lock만을 사용하거나 아키텍처에서 추가로 커스텀 잠금 서비스를 구현하는 전략이 있다. 잠금 서비스는 데이터베이스 lock을 추적하고 (예를 들어 전용 db 테이블에 필요한 정보를 저장함으로써), db 작업을 요청하는 어플리케이션에 권한을 부여하거나 거부하는 서비스다. 잠금 상태에서 배치 job이 중단되는 걸 막기 위해 아키텍처가 재시도 로직을 구현할 수도 있다.

1. Normal processing in a batch window 온라인 사용자나 다른 배치 프로세스에서 데이터를 수정하지 않는, 즉 분리된 배치 윈도우 환경의 간단한 배치 프로세스라면 동시성 문제는 발생하지 않으며, 배치 실행이 끝나면 단일 커밋을 수행하면 된다.

하지만 대부분 이보다는 더 견고한 방법을 써야 한다. 배치 시스템은 시간이 흐르면 복잡도와 처리해야 할 데이터 볼륨이 커지는 경향이 있다는 걸 기억해둬라. 잠금 전략이 없이 시스템이 단일 커밋에만 의존하고 있다면 나중에 수정하기가 매우 어렵다. 따라서 가장 간단한 배치 시스템이라고 해도 아래에 설명한 복잡한 케이스와 재시작, 복구와 관련한 커밋 로직을 고려하는 게 좋다.

2. Concurrent batch or on-line processing 온라인 사용자가 동시에 수정할 수 있는 데이터를 처리하는 배치 어플리케이션이라면 사용자가 수 초 이상 필요할 수도 있는 데이터를 잠가서는 안 된다 (데이터베이스나 파일 모두 마찬가지). 또한 트랜잭션이 몇 번(few) 끝나면 나면 수정 내역을 데이터베이스에 커밋해야 한다. 이렇게 하면 다른 프로세스에서 사용할 수 없는 데이터 양도 최소화하고, 데이터를 사용하지 못하는 시간도 최소화할 수 있다.

물리적 잠금을 최소화하는 또 다른 방법은 논리적인 로(row) 레벨 잠금을 구현하는 것으로, 낙관적 잠금 패턴(Optimistic Locking Pattern)과 비관적 잠금 패턴(Pessimistic Locking Pattern)이 있다.

두 패턴은 배치 처리에 완전히 적합하진 않지만 동시에 실행되는 배치나 온라인 처리에 응용할 수 있다 (데이터베이스가 로(row) 레벨 잠금을 지원하지 않는 경우). 일반적으로는 낙관적 잠금이 온라인 어플리케이션에 더 잘 맞고 비관적 잠금은 배치 처리에 더 적합하다. 논리적 잠금을 사용한다면 논리적 잠금으로 보호되는 데이터 엔터티에 접근하는 모든 어플리케이션에 동일한 스키마를 적용해야 한다.

두 솔루션 모두 단일 레코드 단위 잠금만 지원한다는 점에 주의해라. 때로는 논리적으로 관련된 레코드 그룹을 잠가야 할 때도 있다. 물리적 잠금을 사용한다면 잠재적인 교착상태에 빠지지 않게 매우 조심해야 한다. 논리적 잠금을 사용한다면 보호하고 싶은 논리적인 레코드 그룹을 잘 이해하는 논리적 잠금 매니저를 사용해 일관성을 유지하고 교착상태를 방지하는 게 가장 좋다. 이 논리적인 잠금 매니저는 일반적으로 잠금 관리, 경합 리포팅, 타임아웃 매키너즘 등을 위해 자체 테이블을 사용한다.

3. Parallel Processing 여러 배치을 실행하거나 job을 병렬로 실행하면 총 배치 소요 시간을 줄일 수 있다. 여러 job이 같은 파일, db 테이블, 인덱스 공간을 공유하지만 않는다면 문제 될 건 없다. 이런 경우는 파티션된 데이터를 이용해 구현한다. 다른 방법은 컨트롤 테이블을 사용해 상호 의존성을 관리하는 아키텍처 모델을 구축하는 것이다. 컨트롤 테이블은 각 공유 자원으로 쓰일 로(row) 정보와 어플리케이션에서 사용 중인지 여부를 저장해야 한다. 병렬 job을 사용하는 배치 아키텍처나 어플리케이션은 이 테이블로부터 정보를 읽어와 필요한 리소스에 접근할 수 있는지 판단한다.

데이터 접근에 문제가 없다면 여러 쓰레드를 사용해 병렬로 처리하면 된다. 중앙 컴퓨터(mainframe) 환경이라면, 모든 프로세스에 CPU 타임을 적절히 분배하기 위해 보통 병렬 job 클래스를 많이 사용한다. 그럼에도 이 솔루션은 실행 중인 모든 프로세스에 시간 분할을 보장 할 수 있을 만큼 견고해야 한다.

병렬 처리에서 발생할 수 있는 또 다른 주요 이슈는 로드 밸런싱과 파일, 데이터베이스 버퍼 풀 등의 공통 시스템 자원 가용성이다. 또한 컨트롤 테이블 자체가 문제가 되는 경우도 많다.

4. Partitioning 파티셔닝을 이용하면 각기 다른 큰 규모의 배치 어플리케이션을 동시에 실행할 수 있다. 이는 긴 배치 job을 실행하는 데 필요한 시간을 줄이는 것을 목표로 한다. 입력 파일이나 메인 데이터베이스 테이블을 분할하여 각 어플리케이션을 각기 다른 데이터 셋과 함께 실행할 수만 있다면 파티셔닝을 이용할 수 있다.

파티션된 작업은 지정된 데이터 셋만 처리하도록 설계해야 한다. 파티셔닝 아키텍처는 데이터베이스 설계와 데이터베이스 파티셔닝 전략과 연관이 깊다. 데이터베이스 파티셔닝이 반드시 데이터베이스의 물리적인 분할을 의미하지는 않지만 대부분은 이것을 권장한다.

아래 그림은 분할 방식을 도식화했다:

Partitioned Process

아키텍처는 파티션 수를 동적으로 설정할 수 있어야 하며 자동 설정, 수동 설정 모두 고려해야 한다. 자동 설정은 입력 파일 크기나 입력 레코드 수 같은 파라미터 기반으로 이루어진다.

4.1 Partitioning Approaches 어떤 분할 방식을 사용할지는 케이스마다 다르다. 아래 리스트는 가능한 분할 방식 몇 가지를 보여준다:

1. Fixed and Even Break-Up of Record Set

입력 레코드 셋을 균등하게 나눈다 (예를 들어 10은 정확하게 1/10씩 나눈다). 그다음 batch/extract application의 각 인스턴스가 나눠진 각 레코드 셋을 처리한다.

이 방법을 사용하려면 데이터 셋을 나누기 위한 사전 처리가 필요하다. 분할 결과는 하한/상한값인데, 이는 batch/extract application이 할당된 부분만 처리하도록 제한하기 위한 입력값으로 사용된다.

이 전처리 작업은 나눠진 레코드 셋의 경계를 계산하기 때문에 오버헤드가 커질 수도 있다.

2. Break up by a Key Column

location code 같은 키 컬럼으로 레코드 셋을 나눠서 데이터를 각 값에 따라 배치 인스턴스에 할당하는 방법도 있다. 컬럼 값을 할당할 때는 아래 두 가지 방법 모두 가능하다:

1번 방법에서는 새 값이 추가되면 batch/extract application에 수동으로 설정해줘야 추가된 값도 특정 인스턴스에 할당된다.

2번 방법은 모든 값이 배치 job 인스턴스에 반영됨을 보장한다. 하지만 하나의 인스턴스에서 처리되는 값의 갯수는 해당 값들의 분포에 따라 다르다 (0000-0999 범위에 값이 많이 몰려있고 1000-1999 범위에는 값이 적을 수도 있다). 이 방법을 사용하려면 데이터 범위는 파티셔닝을 고려해 설계해야 한다.

두 방법 모두 레코드를 각 배치 인스턴스에 완전히 동일하게 분배할 수는 없다. 배치 인스턴스가 사용할 레코드 수를 변경해주는 동적인 설정은 없다.

3. Breakup by Views

기본적으로 키 컬럼으로 분할한다는 점은 같지만 이번에는 데이터베이스 레벨에서 분할한다. 레코드 셋을 뷰로 쪼개고, 각 배치 어플리케이션 인스턴스는 이 뷰를 조회한다. 여기선 데이터를 그룹화해서 분할한다.

이 방법을 사용한다면 각 배치 어플리케이션 인스턴스가 특정 뷰를 참조하도록(마스터 테이블이 아닌) 설정해줘야 한다. 또한 새 데이터가 추가되면 이 새 그룹을 뷰에 추가해야 한다. 인스턴스 수를 변경하면 뷰도 바뀌기 때문에 동적인 설정은 불가능하다.

4. Addition of a Processing Indicator

이번에는 식별자 역할을 하는 새 컬럼을 입력 테이블에 추가하는 방법이다. 전 처리 단계에서 모든 식별자는 처리되지 않음으로 마킹된다. 배치 어플리케이션에서 처리되지 않음으로 마킹된 레코드만 읽으며, 한번 읽으면 (잠금과 함께), 처리 중으로 다시 마킹한다. 처리가 완료된 레코드는 식별자를 완료나 에러로 업데이트한다. 별도 컬럼만으로 레코드가 한 번만 처리된다는 걸 보장할 수 있으므로 아무 변경 없이 여러 배치 어플리케이션 인스턴스를 시작할 수 있다.

이 방법을 쓰면 테이블의 I/O가 증가하는데, 업데이트를 수행하는 배치 애플리케이션이라면 어차피 write가 항상 있으므로 이 단점을 피해갈 수 있다.

5. Extract Table to a Flat File

테이블을 추출해서 파일로 만드는 방법이다. 그다음 파일을 여러 조각(segment)으로 분할해서 배치 인스턴스의 입력으로 사용한다.

이 방법을 사용하면 테이블을 파일로 추출하고 분할하는 추가적인 오버헤드가 발생해 멀티 파티셔닝의 이점을 상쇄시켜버릴 수도 있다. 파일을 나누는 스크립트를 수정하는 방식을 활용하면 동적인 설정도 가능하다.

6. Use of a Hashing Column

드라이버 레코드를 검색할 때 쓰는 데이터베이스 테이블에 해시 컬럼(key/index)을 추가한다. 이 컬럼은 특정 로(row)를 어떤 배치 어플리케이션 인스턴스가 처리하는지를 결정하는 식별자를 가진다. 예를 들어 배치 인스턴스 세 개를 실행한다면 식별자 값이 ‘A’면 인스턴스 1에서, ‘B’면 인스턴스 2에서, ‘C’면 인스턴스 3에서 실행한다.

따라서 레코드를 조회할 때 사용하는 프로시저는 특정 식별자로 마킹된 모든 로(row)를 조회하기 위한 WHERE 절을 따로 추가한다. 이 테이블에 추가되는 데이터는 마커로 사용할 필드와 함께 저장되는데, 각 인스턴스 중 하나를 디폴트로 사용할 수도 있다 (‘A’ 같은).

인스턴스 부하를 재분배하고 싶으면 간단한 배치 어플리케이션을 활용해 식별자를 수정하면 된다. 새로 추가된 로(row)가 충분히 많다면 새 데이터를 다른 인스턴스에 재분배하기 위해 이 배치를 실행한다 (배치 윈도우 밖에서라면 언제든지).

배치 애플리케이션에 인스턴스를 추가하고 싶은 경우, 앞에서 설명한 이 배치 애플리케이션을 실행하기만 하면 추가된 인스턴스까지 고려해 식별자를 재분배한다.

4.2 Database and Application Design Principles

멀티 파티셔닝 어플리케이션에서 키 컬럼 접근법으로 분할된 데이터베이스를 사용하려면 파티션 파라미터를 저장하기 위한 중앙 파티션 레포지토리가 필요하다. 이를 통해 유연성을 제공할 수 있으며 유지보수 또한 보장된다. 이 레포지토리는 일반적으로 파티션 테이블로 알려진 테이블 한 개를 저장한다.

파티션 테이블에 저장된 정보는 정적이며, 보통 DBA가 관리한다. 이 테이블에 있는 로(row) 하나는 하나의 파티션 정보를 담고 있다. 또한 이 테이블은 프로그램 ID Code, 파티션 넘버 (파티션의 논리 ID), 파티션을 위한 db key 컬럼의 하한값, 상한값을 저장할 컬럼이 필요하다.

프로그램을 시작하려면 이 아키텍처에 있는(특히, 중앙 처리 Tasklet) 프로그램 id와 파티션 넘버를 어플리케이션에 넘겨줘야 한다. 키 컬럼 접근법을 사용한다면, 어플리케이션이 처리할 데이터 범위를 결정하기 위해 이 변수들로 파티션 테이블을 조회한다. 추가로 다음 작업을 수행할 때도 파티션 넘버를 사용해야 한다:

4.3 Minimizing Deadlocks

어플리케이션을 병렬로 실행하거나 파티셔닝을 사용한다면 데이터베이스 자원에 대한 경합이 생기고 교착상태에 빠질 수도 있다. 데이터베이스 설계 팀에게는 경합이 발생할 수 있는 상황을 가능한 줄이는 것도 데이터베이스 설계만큼 중요한 일이다.

또한 개발자는 데이터베이스의 인덱스 테이블이 교착상태와 성능을 모두 고려해 설계됐는지 확인해야 한다.

교착상태는 로그 테이블이나 관리 테이블, 잠금 테이블 등의 어드민성 테이블나 아키텍처 관련 테이블에서 발생하는 경우가 많다. 어드민이나 아키텍처 테이블의 영향도 고려해야 한다. 아키텍처에서 가능한 병목을 찾으려면 현실이 반영된 부하 테스트는 필수다.

데이터 충돌로 인한 부작용을 최소화하기 위해 아키텍처는 데이터베이스를 연결하거나 교착상태가 발생했을 때 wait-and-retry 간격을 조정하는 등의 서비스를 제공해야 한다. 이는 특정 데이터베이스 리턴 코드에 대응하고 즉시 오류를 발생시키는 대신 미리 정해진 시간 동안 기다렸다가 데이터베이스 연산을 재시도하는 내장 메커니즘을 뜻한다.

4.4 Parameter Passing and Validation

파티션 아키텍처는 애플리케이션 개발자에게 비교적 투명하게 제공돼야 한다. 아키텍처는 파티셔닝 모드에서 실행하는 어플리케이션과 연관있는, 아래 내용을 포함한 모든 작업을 지원해야 한다:

검증은 아래 내용을 포함해야 한다:

데이터베이스를 분할한다면 파티션 하나가 데이터베이스의 여러 파티션에 걸쳐있지는 않는지도 별도로 검증해야 한다.

아키텍처는 파티션 통합도 고려해야 한다. 주로 고려해야 할 내용은 다음과 같다:


전체 목차는 여기에 있습니다.

<< >>