Java 8 Stream API 병렬 처리 성능 측정
문제 상황
매일 밤 실행되는 배치 작업에서 약 50만 건의 데이터를 가공하는 로직이 있었다. 기존 for-loop 방식으로는 처리 시간이 15분 정도 소요되었고, 이를 개선할 필요가 있었다.
Stream parallel() 적용
Java 8부터 제공되는 Stream API의 병렬 처리를 적용해봤다.
// 기존 코드
List<Result> results = new ArrayList<>();
for (Data data : dataList) {
Result result = processData(data);
results.add(result);
}
// 변경 코드
List<Result> results = dataList.parallelStream()
.map(data -> processData(data))
.collect(Collectors.toList());
성능 측정 결과
데이터 크기별로 측정한 결과는 다음과 같았다.
- 1,000건: sequential 120ms, parallel 180ms
- 10,000건: sequential 1.2s, parallel 0.8s
- 100,000건: sequential 12s, parallel 4.5s
- 500,000건: sequential 15m, parallel 6.2m
작은 데이터셋에서는 오히려 병렬 처리가 느렸다. 스레드 생성 및 컨텍스트 스위칭 비용 때문으로 보인다.
주의사항
-
공유 자원 접근: parallelStream 내부에서 공유 변수에 접근하면 race condition이 발생할 수 있다. 불변 객체를 사용하거나 적절한 동기화가 필요하다.
-
ForkJoinPool 크기: 기본적으로
Runtime.getRuntime().availableProcessors()만큼의 스레드를 사용한다. 서버 환경에서는 이를 고려해야 한다. -
순서 보장: 순서가 중요한 경우
forEachOrdered()를 사용해야 한다.
// 순서 보장 필요 시
results.parallelStream()
.map(this::process)
.forEachOrdered(result -> saveToDb(result));
결론
최종적으로 데이터 크기가 1만 건 이상일 때만 parallelStream을 사용하도록 분기 처리했다. 무조건적인 병렬화보다는 실측을 통한 최적화가 중요하다는 것을 배웠다.