욱'S 노트

Spring Batch - Scaling, Parallel Processing 본문

Programming/Spring Batch

Spring Batch - Scaling, Parallel Processing

devsun 2015. 1. 28. 17:46

많은 배치 처리 문제는 single 스레드, single 프로세스 작업으로 해결할 수 있다. 그리고 복잡한 구현을 생각하기 전에 단순하게 처리할 수 있는지 체크하는 것은 좋은 생각이다. 실제작업이 성능을 측정하고 가장 단순한 방법으로 처리 할 수 있다는 그것이 베스트이다. 수백 메가바이트의 파일을 읽고 쓰는데도 1분이면 충분할 것이다.  우리는 이번에 작업을 병렬로 수행하는 방법에 대해 알아볼 것이다. 가장 크게 나누어 본다면 single process에서 멀티 스레드로 작업을 수행하는 것과 멀티 프로세스에서 처리하는 방법에 대해서 알아볼 것이다.


Multi-threaded step


병렬처리를 시작하는 가장 단순한 방법은 Step 설정에 TaskExecutor를 추가하는 것이다.


<step id="loading"> 

<tasklet task-executor="taskExecutor" throttle-limit="20">...</tasklet>

</step>


이 예제에서 TaskExceutor는 다른 빈 정의를 참조한다. TaskExecutor는 표준적인 스프링의 인터페이스이다. 가장 단순한 멀티스레드 TaskExecutor는SimpleAsyncTaskExecutor이다. 위 설정의 결론은 Step 실행시 Chunk(각 commit interval에 구성된 아이템리스트)를 읽고 처리하고 출력할 때, 개별의 스레드에서 수행된다는 것이다. 주의할 점은 이렇게 처리된 아이템들의 순서는 보장할 수 없다는 것이다. 게다가 TaskExecutor의 스레드풀의 한계에 도달할 수 있으므로, 스레드풀 설정을 잘 확인해야 할 것이다.

Multi-threaded step에는 몇가지 현실적인 제약이 있다. 스텝에는 여러가지 reader나 writer가 참가하게 되는데 이런 것들이 stateful하다. 만약 state가 스레드로 분리될 수 없다면 해당 컴포넌트들은 멀티 스레드 스텝에서 사용될 수 없다. 특히나 Spring Batch에서 제공하는 많은 reader나 writer가 멀티스레드를 고려하여 설계되지 않았다. 하지만 reader나 writer를 stateless하거나 thread safe하게 동작하기 위해 process indicator를 이용한 예제를 제공한다. 데이터베이스 입력 테이블에 아이템의 처리 상태를 기록하는 방식이다.

멀티 스레드 스텝을 사용하기 전에 꼭 javadoc을 확인하여 해당 reader나 writer가 thread safe한지 확인하도록 하자.

Parallel Steps

병렬 처리가 필요한 어플리케이션 로직은 특정한 응답으로 분류될 수 있어야 하며 개별적인 스텝에 할당 될 수 있다는 것이다. 병렬 스텝의 쉽게 정의되고 사용될 수 있다. 아래 예에서 보면 (step1,step2)와 step3는 동시에 수행되며, 해당 처리는 step4로 전달된다.

<job id="job1">
<split id="split1" task-executor="taskExecutor" next="step4">
<flow>
<step id="step1" parent="s1" next="step2"/> 
<step id="step2" parent="s2"/>
            </flow>
            <flow>
<step id="step3" parent="s3"/> 
</flow>
</split>
<step id="step4" parent="s4"/> 
</job>

<beans:bean id="taskExecutor" class="org.spr...SimpleAsyncTaskExecutor"/>


Remote Chunking


리모트 청크처리는 스텝처리를 다수의 프로세스로 분할하는것을 의미한다. 미들웨어를 통해서 커뮤니케이션을 수행한다.




마스터 컴포넌트는 하나의 프로세스로 슬레이브는 멀티 리모트 프로세스이다. 이러한 패턴이 잘 동작하기 위해서는 마스터는 bottlenack이 없어야 한다. 마스터는 스프링 배치의 스텝의 구현체이고 ItemWriter는 chunk를 미들웨어에 메시지로 전달한다. 슬레이브는 미들웨어로 부터 메시지를 받아서 ItemWriter나 ItemProcessor를 거쳐 chunk를 처리한다. 이러한 패턴을 썼을때 하나의 이점은 reader와 processor, writer 컴포넌트가 분리될 수 있다는 것이다. 아이템이 자동으로 분할되고 미들웨어를 통해 작업이 공유된다면 리스너는 컨슈머 역할을 하게 되므로, 자연스럽게 로드 밸런싱을 할 수 있다. 미들웨어는 견고해야 한다. 각 메시지의 전달과 하나의 컨슈머에 의해 사용되는 것을 보장해야 한다. JMS는 후보가 될 수 있고, 다른 옵션으로는 그리드나 메모리 공유 제품이 될 수 있다. 


Partitioning


스프링 배치에서는 또한 스텝 수행을 파티셔닝하고 리모트로 실행할 수 있는 SPI를 제공한다. 이 경우 리모트 참가자들은 단순한 스텝 인스턴스이다. 쉽게 정의될 수 있으며, 로컬 프로세싱으로 이용된다.



왼쪽에 있는 작업은 스텝의 순서에 따라 수행되며 그리고 Master라고 표시된 스텝은 현재 수행중이다. 슬레이브는 마스터의 결과를 가지고 동일한 출력을 작업에 전달하는 모두 스텝의 인스턴스들이다. 슬레이브는 일반적으로 리모트 서비스 일 수 있다. 그러나 프로세스의 스레드라도 무방하다. 마스터로부터 슬레이브로 전달된 메시지는 견고할 필요는 없으며 전송만 보장되면 된다. 스프링 배치의 메타데이터인 JobRepository가 각 작업 수행에 대해 각 슬레이브가 한번 실행 됨을 보장할 것이다.


스프링배치의 SPI는 step의 특별한 구현체(PartitionStep)과 두개의 전략적인 인터페이스로 구성된다. 전략 인터페이스는 PartionHandler와 StepExecutionSplitter이다. 



멀티 스레드 스텝의 throttle-limit 속성과 비슷하게 grid-size는 스텝으로부터 요청이 포화되는 것을 방지해준다. 심플한 설정의 예제는 Spring Batch Samples에 포함되어 있으니 참고하기 바란다. 스프링 배치는 "step1:partition0"으로 불리는 step execution을 생성하고, 마스터 스텝으로 언급된 "step1:master"를 일관성 유지를 위해 생성한다. 스프링 3.0부터는 step의 alias를 사용할 수 있다.


파티셔 핸들러는 리모팅이나 그리드 환경의 구조를 알고 있는 컴포넌트이다. 이것은 리모트 스텝으로 StepExecution을 전송할 수 있다. 이때 StepExecution은 DTO와 같은 구조에 특화된 포맷에 포함될 수 있다. 이것은 입력 데이터가 어떻게 분할 되는지는 모르고, 다수의 StepExecution의 결과가 어떻게 조합되는지만 알고 있다. 일반적으로 많은 경웅에 복원이나 failover에 대해 알 필요도 없다. 어쨌든 스프링 배치는 구조로부터 독립된 방식의 재시작을 지원한다. 실패한 작업은 항상 재시작할 수 있으며 실패한 스텝만 재시작 될 것이다.


파티션 핸들러 인터페이스는 다양한 형태의 특별한 구현이 될 수 있다. (RMI remoting, EJB remoting, custom web service, JMS, Java Spaces, shared memory grids (like Terracotta or Coherence), grid execution fabrics (like GridGain).) 스프링 배치는 그리드나 리모팅에 대한 구현체를 포함하고 있지는 않다.


하지만 스프링 배치는 PartitionHandler의 유용한 구현체를 제공한다. 스텝을 TaskExecutor를 이용하여 여러개의 스레드로 스텝을 분할 시켜서 수행한다. 이 구현체의 이름은 TaskExecutorPartitionHandler이며 xmlnamespace에 의해 스텝에 기본적으로 정의된다. 상세한 정의는 아래와 같다.


<step id="step1.master">

<partition step="step1" handler="handler"/>

</step>

<bean class="org.spr...TaskExecutorPartitionHandler"> 

<property name="taskExecutor" ref="taskExecutor"/> 

<property name="step" ref="step1" />

<property name="gridSize" value="10" />

</bean>


그리드 사이드는 생성될 step execution의 수를 결정한다. 이것은 thread pool의 사이즈와 매치될 수 있다. TaskExecutorPartionHandler는 IO가 중요한 스텝에 꽤 유용하다. 또한 리모트 수행을 위해 이용될 수 있다. 리모트 실행에 대한 프록시로서 step의 구현체로 제공될 수 있다.


파티셔너는 더 단순한 의무를 가진다. 새로운 StepExecution을 위한 ExecutionContext를 생성하기 위한 입력 파라미터로서의 의무만 가진다.


public interface Partitioner {

Map<String, ExecutionContext> partition(int gridSize);

}


Comments