스프링 배치에서 chunk와 JPA pageSize 설정의 관계성
배치 코드를 작성하면서 chunk와 JPA setPageSize 개념이 헷갈려 해당 내용을 자세히 정리해 봤다.
1. 스프링 배치의 setPageSize()와 chunk()의 차이점
스프링 배치에서 setPageSize()와 chunk() 설정은 종종 혼동될 수 있는데, 이 둘은 매우 다른 목적으로 사용된다.
1-1. setPageSize() 설정 (JpaPagingItemReader)
setPageSize() 메서드는 JpaPagingItemReader를 사용할 때, 데이터베이스에서 한 번에 읽어올 레코드의 수를 설정한다. 이 설정은 데이터베이스와의 상호작용과 메모리 관리에 직접적인 영향을 미친다.
데이터베이스 효율성 측면에서 페이지 크기를 작게 설정하면, 데이터베이스에 더 자주 접근하게 되지만 한 번에 처리해야 할 데이터 양은 줄어든다. 반대로 큰 페이지 크기는 데이터베이스 접근 횟수를 줄이지만 한 번에 처리해야 할 데이터 양이 많아진다.
메모리 관리 측면에서 페이지 크기가 크면 한 번의 호출로 많은 양의 데이터를 메모리에 로드하게 되므로 메모리 사용량이 증가할 수 있다.
1-2. chunk() 설정 (Step)
chunk() 메서드는 처리 과정에서 한 번의 트랜잭션으로 처리될 아이템의 수를 정의한다. 이 설정은 트랜잭션 관리와 성능 최적화에 중요하다.
트랜잭션 관리 측면에서 chunk() 설정은 각 트랜잭션 내에서 처리될 아이템의 수를 정한다. 청크 크기가 10이면, 10개의 아이템이 성공적으로 처리될 때까지 트랜잭션이 유지된다. 오류 발생 시 해당 청크의 처리는 롤백된다.
성능 최적화 측면에서 청크 크기가 크면 한 번에 많은 데이터를 처리할 수 있지만, 메모리 사용량이 증가하고 실패 시 더 많은 데이터를 롤백해야 한다. 작은 청크 크기는 이러한 문제를 줄이지만 처리 속도가 느려질 수 있다.
1-3. 작업 범위
setPageSize()는 데이터베이스와의 상호작용을 다루며, 데이터를 얼마나 자주 그리고 얼마나 많이 읽을지 결정한다. 반면, chunk()는 메모리에 로드된 후의 데이터 처리에 초점을 맞추고 있다.
1-4. 메모리와 트랜잭션
setPageSize()는 주로 데이터베이스와의 효율적인 상호작용과 메모리 관리에 관련이 있다. 반면, chunk()는 트랜잭션 관리와 처리 효율성에 더 많은 영향을 미친다.
1-5. 혼동 방지
이 두 설정이 서로 다른 목적을 가진다는 것을 이해하면, 혼동을 피할 수 있다. setPageSize()는 "데이터베이스에서 얼마나 많이 읽을까?"에 대한 답이고, chunk()는 "읽어온 데이터를 어떻게 처리할까?"에 대한 답이다.
이렇게 두 설정의 차이점을 이해하면, 스프링 배치에서 효율적인 데이터 처리 전략을 수립하는 데 도움이 된다. 데이터베이스와의 상호작용, 메모리 관리, 트랜잭션 범위 등 다양한 요소를 고려하여 적절한 설정을 선택해야 한다.
2. 다양한 사용 예시
2-1. setPageSize설정은 1000인데 chunk 설정은 3000이면 어떻게 처리할까?
페이지 사이즈와 청크의 개념
먼저, setPageSize(1000)은 JpaPagingItemReader를 사용하여 데이터베이스에서 한 번에 읽어올 레코드의 최대 개수가 1000개임을 의미한다. chunk(3000)은 한 번의 트랜잭션 내에서 처리할 아이템의 수가 3000개임을 의미한다. 즉, 3000개의 아이템이 처리될 때까지 트랜잭션이 유지된다.
데이터 읽기 동작
JpaPagingItemReader는 데이터베이스와의 연결을 맺고, 설정된 페이지 크기인 1000개 단위로 데이터를 읽기 시작한다. 이는 JpaPagingItemReader가 첫 번째 페이지에서 1000개의 레코드를 읽어오고, 다음 페이지를 읽기 위해 다시 데이터베이스에 요청하는 과정을 반복한다는 것을 의미한다.
청크 처리 동작
읽어온 데이터는 chunk 설정에 따라 처리된다. 여기서는 chunk 크기가 3000개이므로, JpaPagingItemReader는 총 3000개의 레코드를 읽어올 때까지 데이터를 계속 읽는다. 이때, JpaPagingItemReader는 세 번의 페이지 읽기 작업을 수행한다(1000개씩 세 번, 총 3000개).
트랜잭션 관리
3000개의 아이템이 모두 읽히고 나면, 이들은 하나의 큰 트랜잭션으로 처리된다. 처리 과정에서 오류가 발생하면, 해당 트랜잭션에 포함된 모든 3000개의 아이템 처리가 롤백된다.
결론적으로, setPageSize가 1000이고 chunk 크기가 3000인 경우, JpaPagingItemReader는 총 3번의 페이지 읽기를 통해 3000개의 데이터를 읽어오고, 이들을 하나의 큰 트랜잭션 내에서 처리한다. 이러한 설정은 메모리 사용량과 트랜잭션 관리 측면에서 신중하게 고려해야 하는 요소들을 포함한다.
2-2. 읽어올 데이터는 10개일 때 setPageSize설정은 100이고, chunk 설정은 2로 되어있을 때는 어떻게 처리할까?
데이터 읽기 동작
setPageSize(100)는 JpaPagingItemReader가 데이터베이스에서 한 번의 호출로 최대 100개의 레코드를 읽어올 수 있음을 의미한다. 하지만 실제로 데이터베이스에 10개의 레코드만 존재한다면, JpaPagingItemReader는 이 10개의 레코드만 읽어온다. 페이지 크기가 100이더라도 실제 데이터가 10개밖에 없으면, 10개만 읽히고, 읽기 작업은 여기서 종료된다.
청크 처리 동작
chunk(2) 설정은 한 번의 트랜잭션에서 처리할 레코드의 수가 2개임을 의미한다. 스프링 배치는 읽어온 10개의 데이터를 2개씩 나누어 처리한다. 즉, 2개의 레코드를 처리할 때마다 한 번의 트랜잭션이 커밋되며, 총 5번의 트랜잭션이 발생한다(10개의 데이터를 2개씩 처리).
트랜잭션 관리
각 트랜잭션은 2개의 레코드를 처리한다. 처리가 성공적으로 완료되면, 해당 트랜잭션은 커밋되고, 실패할 경우 롤백된다.
결론적으로, setPageSize가 100이고 chunk가 2인 설정에서 데이터베이스에 10개의 데이터만 있으면, JpaPagingItemReader는 데이터베이스에서 한 번에 10개의 레코드를 모두 읽어오고, 이후 스프링 배치는 이 데이터를 2개씩 나누어 총 5번의 트랜잭션을 통해 처리한다. 여기서 중요한 점은 setPageSize가 실제 데이터보다 크더라도 실제로 존재하는 데이터만큼만 읽힌다는 것이다.
3. itemReader, ItemProcessor, ItemWriter와 setPageSize, chunk와의 관계
읽어올 데이터가 100개, pageSize가 25, chunk가 50인 경우를 기준으로 설명해 보겠다.
3-1. Reader(JpaPagingItemReader와 setPageSize 관계)
reader는 pageSize에 따라 데이터를 읽어온다. 여기서 pageSize는 25이므로, reader는 한 번에 25개의 데이터를 읽어온다. 그러나 reader가 실제로 몇 번 작동할지는 전체 데이터 수 (이 경우 100개)와 더불어, 데이터 소스가 더 이상 데이터를 제공하지 않을 때까지 계속 데이터를 요청한다는 점에 달려있다.
총 데이터가 100개이고 pageSize가 25이면, 이론적으로 reader는 4번 호출될 것이다.
3-2. Processor & Writer (chunk 설정의 영향)
processor와 writer는 chunk 크기에 따라 동작한다. 이 경우 chunk 크기는 50이다. 이 말은 processor와 writer가 각각 50개의 아이템을 처리한 후에 한 번씩 호출된다는 의미다. 따라서, 100개의 아이템에 대해 processor와 writer는 각각 2번씩 호출될 것이다.
- reader가 25개의 아이템을 읽는다.
- 다시 reader가 25개의 아이템을 읽는다 (총 50개).
- processor가 이 50개의 아이템을 처리한다.
- writer가 처리된 50개의 아이템을 기록한다.
- reader가 다시 25개의 아이템을 읽는다.
- 다시 reader가 25개의 아이템을 읽는다 (두 번째 50개).
- processor가 이 50개의 아이템을 처리한다.
- writer가 처리된 50개의 아이템을 기록한다.
따라서, 전체적으로 reader는 4번, processor와 writer는 각각 2번씩 작동한다. 이는 Spring Batch가 chunk 단위로 처리를 반복하는 방식에 기반한다.
💡 실제 데이터가 100개인지, 103개인지 ItemReader는 모르는 상황에서 reader는 어떻게 동작할까?
reader는 데이터가 더 이상 없을 때까지 데이터를 계속 읽으려고 시도한다. 이 경우, 실제 데이터가 100개인지 102개인지 모르는 상황에서, reader는 다음과 같이 작동한다:
1. reader는 데이터 소스에서 pageSize에 지정된 수 (이 경우 25개) 만큼의 데이터를 읽는다.
2. 데이터가 여전히 남아있으면 reader는 계속해서 pageSize만큼의 데이터를 읽는다.
3. 전체 데이터가 100개라면, reader는 총 4번 호출됩니다 (25개씩 4번).
4. 만약 데이터가 102개라면, reader는 5번째 호출에서 마지막 2개의 데이터를 읽고, 더 이상 데이터가 없음을 확인한 후 종료한다.
reader가 더 이상 읽을 데이터가 없음을 확인한 후에는 processor와 writer가 나머지 데이터를 처리한다. 따라서, writer는 chunk 크기에 따라 호출되며, 마지막 chunk가 작으면 그에 맞게 조정된 작은 크기의 데이터 세트를 처리한다.
마지막 chunk 처리 후에 reader가 다시 호출되는 것은 스프링 배치의 표준 동작이다. 이 호출은 더 이상 읽을 데이터가 없다는 것을 확인하기 위한 것으로, 이때 reader는 더 이상 데이터를 반환하지 않고, 배치 프로세스는 종료된다.
따라서, 예시에서 설명한 대로 100개의 데이터가 있는 경우, reader는 마지막으로 한 번 더 호출되어 더 이상 데이터가 없음을 확인하고 프로세스는 그 후 종료된다.
4. 마무리
이 글을 작성하며 Spring Batch에서 pagesize와 chunk의 차이점을 더 명확하게 이해하게 되었다. 그리고 이 설정들이 각각 reader, processor, writer를 어떻게 작동시키는지까지 알아봤다.
이 글이 스프링 배치를 사용하는 다른 개발자 분들에게도 도움이 되기를 바라며, 실제 프로젝트에서 이러한 개념들을 효과적으로 적용해 보길 권장한다.
👇 스프링 부트 3에서 스프링 배치 적용하기 👇
[Spring Boot] Spring Boot 3.X 버전에서 Spring Batch 적용하기
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)
'Spring > Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링 배치와 JPA를 활용해 누락된 SNS 이벤트 재발행 (0) | 2023.11.29 |
---|---|
[Spring Boot] SNS MessageAttributes를 이용한 분산 시스템 추적용 Trace Id 전달 방법 (0) | 2023.11.28 |
[Spring Boot] 스프링 부트 3.X 버전에서 Spring Batch 테이블 자동 생성 안되는 에러 (0) | 2023.11.27 |
[Spring Boot] Spring Boot 3.X 버전에서 Spring Batch 적용하기 (0) | 2023.11.26 |
[Spring Boot] 분산 시스템에서의 SNS/SQS 메시지 처리: 단일 책임 원칙과 Zipkin의 Trace ID 에러 핸들링을 중심으로 (0) | 2023.11.24 |