스프링배치의 ItemReader 중에서는 Paging 처리를 지원하는 것들이 있다. 대표적으로 JdbcPagingItemReader와 최근에 추가된 것으로 보이는 JpaPagingItemReader가 있다. 이외에 Spring Data repository를 사용할 수 있게 해주는 RepositoryItemReader도 Paging처리를 지원한다.
해당 ItemReader들을 활용해 Paging 처리를 할시, 데이터가 Skip현상이 발생할 수 있다. 아래와 같이 유저 상태가 '01'인 데이터들을 읽어들여 '02'로 업데이트 해주는 배치 애플리케이션이 있다고 가정해보자.
실제로는 select 쿼리에 페이징 처리도 들어가게 된다. 이렇게 페이징 처리를 했을때의 문제는 where 절에 조건 컬럼을 업데이트한다는 것이다. 첫번째 페이지를 읽어온 뒤, STATE가 '01'인 컬럼을 모두 '02'로 업데이트한다. 때문에 두번째 Read를 할때는 첫번째 Read 때와는 페이지 Numbering이 달라지고 때문에 데이터 Skip이 발생한다.
그림에서와 같이 row 3개 단위로 Chunking을 한다고 가정해보자. 첫번째 루프에서 읽어들인 데이터들을 Update 해버리는데, 두번째 루프에서는 페이지 1을 읽어들일 차례이므로 1개 Chunk 단위를 건너뛰게 되는 것이다.
결국 이런 현상의 원인은 데이터 처리시마다 정렬 상태가 계속 바뀌기 때문인데, 인덱싱을 해두고 Read시에 해당 인덱스를 타도록 유도하면 해결이 되지 않을까? 결론부터 말하면 안된다. 데이터를 업데이트하면 자동으로 인덱스도 업데이트 되기 때문이다. (적어도 오라클은 그렇다)
해결 방법
Read시 무조건 0페이지를 읽도록 Item Reader의 코드를 수정해주는 방법이 있다. 사실 완전한 해결책이라고는 할 수 없고 workaround 겠지만 이 문제를 다루는 대부분의 포스팅에서 제안하는 방법이다.
RepositoryItemReader를 기준으로 보면 doPageRead() 메서드를 재정의하면 된다.
@SuppressWarnings("unchecked")
protected List<T> doPageRead() throws Exception {
//Pageable pageRequest = PageRequest.of(page, pageSize, sort);
Pageable pageRequest = PageRequest.of(0, pageSize, sort);
MethodInvoker invoker = createMethodInvoker(repository, methodName);
List<Object> parameters = new ArrayList<>();
JpaPagingItemReader도 마찬가지로 0번 페이지를 읽도록 override 해주면 된다.
JpaPagingItemReader<UserState> jpaPagingItemReader = new JpaPagingItemReader<UserState>() {
@Override
public int getPage() {
return 0;
}
};
Spring batch는 아직 QueryDslReader를 공식적으로 지원하지 않는데, 그래서 그 유명한 창천향로님이 개발하여 공개하신 QueryDslReader가 있다(https://github.com/jojoldu/spring-batch-querydsl). 여기서도 Readme를 읽어보면 같은 문제를 다루고 있으며 파라미터로 optional하게 offset을 주는 방법을 제공하는 것 같다.
끝으로 이 방법이 workaround인 이유는 가능하다면 애초에 이런 방식의 업데이트를 유발하지 말아야하기 때문이다. 데이터를 업데이트할 때마다 인덱스도 업데이트되면 오버헤드가 발생한다. 당연히 데이터가 많아질수록 오버헤드도 커지고 성능 이슈로 이어질 수가 있는 것이다. 자세한 것은 하단 참고에 링크해둔 글을 읽어보고 이를 잘 유의하여 배치 설계를 하자.
- 끝 -
참고
https://use-the-index-luke.com/sql/dml/update
https://docs.spring.io/spring-batch/docs/current/reference/html/appendix.html
'IT > 개발지식' 카테고리의 다른 글
Spring @Cacheable을 내부 메서드에 쓰면 안되는 이유 (0) | 2022.03.12 |
---|---|
Spark 개발 환경 구축하기 - Scala, IntelliJ, SBT (0) | 2021.10.04 |
Springboot yaml 파일에 List 세팅하기 (0) | 2021.08.05 |
객체지향 의존 역전 원리(DIP) 제대로 알기 (0) | 2021.08.02 |
Redis의 다양한 구성을 빠르게 따라해보자 (0) | 2021.06.14 |