pile·
백엔드·티몬티몬·

Java8 Parallel Stream, 성능장애를 조심하세요!

티몬 개발팀이 Java 8 Parallel Stream의 동작 방식과 잠재적 위험을 정리한 글이다. parallelStream() 한 줄로 병렬 처리를 얻는 대신 공유된 스레드 풀을 쓰기 때문에 심각한 성능 장애를 일으킬 수 있다는 점이 핵심이다. ExecutorService와 비교하며 스레드 수를 제어하는 두 가지 방법, ForkJoinPool의 동작, 병렬화가 오히려 손해가 되는 조건을 차례로 짚는다.

핵심 포인트
  • Java 8 이전에는 주로 ExecutorService로 스레드 수를 지정했지만 parallelStream()은 같은 일을 한 줄로 대신한다
  • Parallel Stream은 common ForkJoinPool을 쓰고 프로세서당 스레드 하나를 기본으로 잡는다. 4코어 맥북에서는 메인 포함 4개가 돌았다
  • 풀 크기는 ForkJoinPool.common.parallelism 프로퍼티를 바꾸거나 별도 ForkJoinPool 인스턴스에 submit 하는 두 방법으로 조절한다
  • 공용 풀을 쓴다는 것은 같은 풀을 쓰는 다른 스레드에 영향을 주고 또 받는다는 뜻이다
  • 분할 비용이 크거나 작업이 독립적이지 않으면 순차 실행이 더 빠를 수 있다
  • 문제가 없을지 추측하지 말고 순차 연산과 결과값·처리시간을 비교하는 테스트로 판단해야 한다
상세 정리
  • 비교 대상: 기존 방식은 Executors.newFixedThreadPool(5)를 만들고 반복문에서 submit 하는 형태이고, Parallel Stream은 컬렉션에 parallelStream().forEach()를 붙이는 것으로 끝난다
  • 기본 병렬도: 내부적으로 common fork join pool을 사용하며 1 프로세서당 1 스레드라 16코어 장비면 16개까지 만들 수 있다
  • 실측 근거: 예제 실행 결과 메인 스레드를 포함해 4개가 돌았고, 이는 실행 환경인 맥북이 4코어였기 때문이다
  • 제어 방법 1: java.util.concurrent.ForkJoinPool.common.parallelism 프로퍼티를 6으로 설정하니 메인 포함 6 스레드로 늘었다
  • 제어 방법 2: new ForkJoinPool(5)를 만들어 그 안에서 parallelStream을 submit 하면 지정한 수를 넘겨 새 스레드가 생기지 않는다
  • ForkJoinPool 정체: Java 7에 처음 소개된 스레드 풀 서비스로 분할정복과 비슷하게 fork로 작업을 나누고 join으로 합친다
  • 작업 훔치기: ExecutorService 구현체지만 스레드마다 개별 큐를 갖고, 자기 큐가 비면 다른 스레드의 작업을 가져와 CPU 자원이 놀지 않게 한다
  • 분할 균등성: Parallel Stream은 Spliterator의 trySplit()으로 작업을 쪼개는데, 분할 단위가 균등하고 분할 비용이 낮아야 순차 방식보다 이득이다
  • 자료구조 영향: 전체 크기를 아는 array나 ArrayList는 분할이 빠르고 저렴하지만 LinkedList는 별다른 효과를 찾기 어렵다
  • 상태 공유 연산: sorted()나 distinct() 같은 중간 연산은 내부에서 상태 변수를 공유하므로 순차 실행이 더 효과적일 수 있다
  • 적합 조건: 분할이 잘 되는 자료구조이면서 작업이 독립적이고 CPU 사용이 높은 경우에 어울린다
  • 위험 요약: 독립적으로 실행되는 애플리케이션이 아니라면 공용 풀 점유가 다른 기능의 지연으로 번지므로 사용에 신중해야 한다
  • 판단 기준: 결과값에 차이가 없는지, 처리시간 단축이 병렬화 비용보다 큰지를 테스트로 확인하라고 정리한다
왜 읽나parallelStream()이 편한 만큼 왜 위험한지, 손댈 수 있는 지점은 어디까지인지 코드 수준에서 확인할 수 있다.
티몬
티몬 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·twilio-engTwilio Engineering·

    Programmable Messaging에서 Verify API로 마이그레이션하기

    Twilio의 Programmable Messaging API로 자체 OTP 솔루션을 운영하던 서비스가 Verify API로 전환하는 방법을 코드 예시와 함께 설명한다. Verify는 OTP 전송·검증을 위한 전용 API로, 전화번호 구매, 토큰 생성, DB 저장·만료 관리를 내부에서 처리해 개발자가 직접 구현할 코드를 크게 줄인다.

    요약 이어보기
    #authentication#twilio#sms+2
  2. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 2부

    포스타입 백엔드 엔지니어가 벡터 기반 개인화 추천 시스템을 실제 운영하며 맞닥뜨린 성능 장애와 용량 문제를 해결한 과정을 담은 2부다. 수백만 개의 벡터 KNN 검색이 피크 시간대에 전체 Elasticsearch 검색 성능을 흔드는 문제부터 클러스터 OOM 사태까지, 쿼리 최적화와 인프라 분리 두 가지 경로로 근본 해결에 이른다.

    요약 이어보기
    #elasticsearch#vector-search#recommendation-system+2
  3. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 1부

    포스타입이 태그 기반 추천의 한계를 극복하고 벡터 임베딩 기반 개인화 추천 시스템을 구축한 과정을 담은 1부다. 유사한 콘텐츠가 다른 용어를 쓰거나 동일한 태그가 전혀 다른 톤의 콘텐츠를 가리키는 문제를 임베딩 벡터로 해결하고, OpenSearch의 HNSW ANN 검색으로 수백만 벡터를 실시간 검색하는 시스템을 구축해 구매율 15% 향상을 달성했다.

    요약 이어보기
    #opensearch#vector-search#recommendation-system+2