Spring Batch Partitioning으로 수억 건 데이터, OOM 없이 맛있게!
수억 건의 대량 데이터를 Spring Batch로 처리하며 OOM(Out of Memory) 문제 발생
Partitioning을 통해 데이터를 월, 일 단위로 분할하여 병렬 처리(Parallel Processing)를 수행
Cursor 기반 ItemReader와 Bulk Write를 적용하여 메모리 사용량 감소 및 쓰기 성능 향상
MongoPagingItemReader의 skip() 연산의 성능 저하로 인해 MongoCursorItemReader 채택
Partitioning은 학습 곡선과 설정 복잡도가 높으므로, 기본 최적화 기법을 먼저 시도 권장
Partitioning 아키텍처: 대용량 데이터 처리의 핵심
본문에서는 Spring Batch의 Partitioning을 활용하여 대량 데이터를 효율적으로 처리하는 방법을 제시한다. Partitioning(파티셔닝)은 데이터를 여러 개의 파티션으로 나누어 각 파티션을 독립적인 스레드에서 병렬 처리하는 기술이다.
Manager Step(Master): 전체 작업을 관리하고 데이터 분할 결정
Worker Step(Slave): 할당받은 데이터 조각을 독립적으로 처리
Partitioner: 데이터 분할 기준 정의, ExecutionContext 생성
PartitionHandler: Partitioner의 작업 명세서를 받아 Worker Step 병렬 실행
이러한 구조를 통해 데이터 경합 없이 병렬 처리 성능을 극대화하고, OOM 문제를 해결한다.
MongoCursorItemReader: Cursor 기반 스트리밍의 장점
글에서는 MongoCursorItemReader를 사용하여 MongoDB에서 데이터를 효율적으로 읽어오는 방법을 설명한다. MongoCursorItemReader는 MongoDB의 서버 사이드 커서를 활용하여 데이터를 스트리밍 방식으로 처리한다.
Paging 방식의 skip() 오버헤드: 페이지가 뒤로 갈수록 응답 시간 선형 증가
Cursor 방식: skip() 없이 다음 배치를 즉시 반환, O(1)의 일정한 비용 유지
CursorBatchSize: 한 번에 가져올 데이터 묶음 설정
NoCursorTimeout: 배치 작업 중 커서 자동 닫힘 방지
이러한 특징 덕분에 MongoCursorItemReader는 대량 데이터 처리 시 메모리 효율성과 안정성을 확보한다.
Bulk Operations: 쓰기 성능 최적화 전략
본문에서는 Bulk Operations를 사용하여 MongoDB에 데이터를 효율적으로 쓰는 방법을 소개한다. Bulk Operations는 여러 개의 쓰기 연산을 묶어 단일 네트워크 요청으로 처리하여 I/O 오버헤드를 줄인다.
네트워크 효율성: 단일 일괄 INSERT 수행
메모리 관리: 쓰기 속도 향상으로 ItemProcessor 객체 메모리 해제
UNORDERED 모드: 병렬 쓰기 최적화, 일부 실패 시 나머지 작업 진행
saveAll() vs Bulk Operations: saveAll()은 개별 save() 호출, Bulk Operations는 일괄 처리
이러한 Bulk Operations를 통해 쓰기 성능을 향상시키고, OOM 문제를 해결한다.
ThreadPoolTaskExecutor와 GridSize 튜닝
글에서는 Spring Batch Partitioning의 성능을 최적화하기 위한 ThreadPoolTaskExecutor와 gridSize 설정에 대해 설명한다. ThreadPoolTaskExecutor는 스레드 풀을 관리하고, gridSize는 동시에 실행할 Worker Step의 개수를 결정한다.
corePoolSize: 기본 스레드 수, gridSize와 동일하게 설정 권장
maxPoolSize: 최대 스레드 수, corePoolSize + 2~4
queueCapacity: 작업 대기열 크기, Partitioning에서는 0으로 설정
gridSize 결정: CPU 코어 수 × 1.5로 시작, CPU 사용률, 메모리 사용량, DB 커넥션 등을 고려하여 조정
적절한 설정을 통해 병렬 처리의 효율을 극대화하고, 시스템 자원을 효율적으로 활용한다.
성능 비교 분석: 최적화 전략의 효과
본문에서는 Partitioning, Cursor Reader, Bulk Operations를 적용한 최적화 방식과 기본 방식의 성능을 비교 분석한다. 테스트 결과, 데이터 건수가 증가할수록 최적화 방식의 성능이 압도적으로 우수함을 보여준다.
10,000건: 기본 방식 3초, 최적화 방식 2초 (1.5배)
100,000건: 기본 방식 33초, 최적화 방식 15초 (2.2배)
1,000,000건: 기본 방식 11분 22초, 최적화 방식 1분 53초 (약 6배)
5,000,000건: 기본 방식 1시간 37분 25초, 최적화 방식 8분 55초 (약 10.9배)
최적화 방식은 데이터가 많아질수록 기본 방식 대비 압도적인 성능 향상을 보이며, 대량 데이터 처리의 효율성을 입증한다.