스프링 배치에 관하여
장점
자동화 : 매번 단순반복작업을 쉽고 빠르게 자동화시켜줍니다.
대용량 처리 : 그것이 대용량이라 할지라도 가장 최적화된 성능을 보장합니다.
견고성 : 예측하지 못한 상황이나 동작에 대해 예외처리도 정의할 수 있습니다.
재사용성 : 공통적인 작업을 단위별로 재사용할 수 있습니다.
고려사항
단순하게!
복잡한 구조와 로직을 피해야합니다.
안전하게!
정의한 단위수만큼 데이터를 불러와서 처리합니다.
처리해야 하는 데이터에 대한 예외적인 상황이 일어나지 않도록 데이터의 무결성이 보장되어야 합니다.
가볍게!
배치처리시 시스템에 입력(Input)과 출력(Output)의 사용을 최소화해야 합니다.
네트워크 비용이 커질수록 그만큼 성능에도 영향을 끼치기 때문이다.
최대한 적은 횟수로 데이터를 가져와서 -> 처리하고 -> 저장하는 것이 좋습니다.
스케줄러가 아닙니다!
착오하지 말아야 할 것이 스프링부트배치는 스케줄러가 아닙니다. 별도로 스케줄링 프레임워크에 구현된 스프링배 부트 배치를 적용하여 원하는 시간이나 이벤트 상황에 실행되게 하거나 이력관리를 할 수 있어야 합니다.
스프링 부트 배치의 기본구조
Read(가져와서) : 원하는 조건의 데이터 레코드를 DB에서 읽어옵니다.
Processing(처리하고) : 읽어온 데이터를 비즈니스로직을 따라 처리합니다.
Write(저장한다) : 처리된 데이터 DB에 업데이트(저장)합니다.
이해하기 매우 쉽다. 그저 '가져와서', '처리하고', '저장한다.' 이 기본구조를 스프링 부트 배치에서 제공하는 인터페이스 관계도를 나타내면 다음과 같습니다.
전 단계는 잠시 무시하고 방금 전 언급한 부분(파란색으로 색칠한 부분)만 살펴보겠습니다.
가져와서, 처리하고, 저장한다는 각각 다음의 인터페이스에서 정의합니다.
ItemReader : 배치데이터를 읽어오는 인터페이스입니다. DB 뿐 아니라 File, XML 등 다양한 타입에서 읽어올 수 있습니다.
ItemProcessor : 읽어온 데이터를 가공/처리합니다. 즉, 비즈니스 로직을 처리합니다.
ItemWriter : 처리한 데이터를 DB(또는 파일)에 저장합니다.
ItemProcessor VS ItemWriter
지금도 단순하지만 더 단순하게 생각하면
- 가져와서(ItemReader)
처리한다.(ItemProcessor)- 처리하고 저장한다.(ItemWriter)
이렇게 생각해 볼 수 도 있겠죠. 굳이 ItemProcessor와 ItemWriter 단계를 별도로 분리하지 않고 그냥 ItemWriter에서 처리하고 저장하는 구조가 더 간결해 보일 수 도 있습니다. 하지만 분리해야하는데는 다 이유가 있다!!!
-
각각의 역할을 단순 명료하게 분리하기 위함입니다.
앞서 설명드린 바와 같이 스프링 배치 부트는 배치 처리의 특성상 그 구조가 단순해야 합니다.
이미 역할이 정해져 있는 인터페이스에 본연의 역할 외 다른 역할까지 정의하여 사용하면 처리가 이루어지는 분기 단위가 모호해질 수 밖에 없습니다.
꼭 스프링부트 배치 프레임워크라서가 아니라 대용량 데이터에 대한 배치 처리는 100건이면 100건 1000건이면 1000건과 같이 일정한 처리 단위로 최대한 단순하게 읽고 처리하고 저장되게 하는 것이 좋습니다.
개발자의 입장에서도 비즈니스 로직을 살펴볼 때에 어디가 데이터를 읽어오고, 어디에서 데이터를 처리하며, 어디가 데이터를 저장소(DB, File) 저장하는 영역인지를 명확하게 구분 지어 설계하는 것이 견고한 서비스를 만들어가는데 좋은 밑바탕이 될 수 있겠죠.
-
읽어오는 데이터와 저장하는 데이터의 타입을 분리하기 위함입니다.
ItemReader(데이터를 읽어올 때)에선 DB 뿐 아니라 File에서도 읽어올 수 있습니다.
마찬가지로 ItemWriter(데이터를 저장할 때)에서도 역시 DB 뿐 아니라 File에 쓰이게 할 수도 있습니다.
스프링부트 배치 프레임워크에서 제공하는 2개의 인터페이스를 살펴보겠습니다.
인터페이스 : ItemReader (데이터를 읽어올 때)
package org.springframework.batch.item;
import org.springframework.lang.Nullable;
public interface ItemReader<T> {
T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;
}
ItemReader는 배치데이터를 읽어오는 인터페이스입니다. Read메서드를 반환 타입을 볼까요?
제네릭(Generic) 타입입니다. 제네릭이기 때문에 우리가 직접 타입을 정할 수 있습니다. 즉 ItemReader 를 통해 읽어오는 타입은 우리가 직접 지정할 수 있습니다.
인터페이스 : ItemWriter (데이터를 저장할 때)
package org.springframework.batch.item;
import java.util.List;
public interface ItemWriter<T> {
void write(List<? extends T> items) throws Exception;
}
ItemWriter의 write 메소드의 매개변수를 살펴볼까요? 제네릭(Generic)입니다.
아까 ItemReader 인터페이스의 반환타입이던 바로 그 제네릭(Generic)입니다.
매개변수가 우리가 원하는 타입을 리스트(List) 형태로 받습니다.
그렇다면 데이터를 한번에 몇 건씩 처리해야 할까요?
단순하게 ItemReader에서 반환된 모든 데이터를 한꺼번에 받아서 처리하면 될까요? 만약 읽어온 데이터가 1만건, 아니 100만 건이 될 수도 있을 텐데 그렇게 많은 양의 데이터가 한꺼번에 메모리에 올려지면 실제 서비스에 어떤 영향을 주게 될까요?
스프링부트 배치는 한 번의 트랜잭션 안에 처리(commit)되는 수를 정의할 수 있습니다. 그 역할을 하는 것이 바로 chunk라는 단위입니다. 이것이 스프링부트프레임의 가장 핵심적인 키워드라 할 수 있습니다.
마지막으로 ItemReader와 ItemWriter 중간에 있는 ItemProcessor를 살펴볼까요?
ItemProcessor (데이터를 처리할 때)
package org.springframework.batch.item;
import org.springframework.lang.Nullable;
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
이 인터페이스의 존재의 이유는 방금전에 소개드렸다시피 각각의 역할을 분리하기 위해 그리고 읽어올 때와 저장할 때의 데이터 타입이 다른 경우, DB에서 레코드를 읽어와서 파일 형태로 저장할 때 혹은 그 반대일 때를 대응하기 위함입니다.
이 인터페이스의 제네릭의 매개변수가 2개인데요. 앞에 있는 는 input을 의미하고 뒤에 있는 는 Output을 의미합니다. 말 그대로 인풋과 아웃풋 타입을 정의하여 process 매서드 안에서 우리가 필요한 비즈니스 로직을 구현합니다.
지금까지 설명한 내용을 하나의 그림으로 표현하면 다음과 같습니다.
참고 블로그
'JAVA > 지식' 카테고리의 다른 글
[접근 지정자] protected, default 의 차이 (0) | 2021.03.01 |
---|---|
[Spring Boot annotion] @Bean, @Configuration, @Component (0) | 2021.03.01 |
ActiveMQ vs kafka vs RabbitMQ (0) | 2021.02.28 |
뮤텍스(Mutex)와 세마포어(Semaphore)의 차이 (0) | 2021.02.09 |
[AOP] AOP 정의, 용어정리, 활용방법 (0) | 2021.02.08 |