Spark-AI SUMMIT 2020 에서 발표한 Fine Tuning And Enhancing Performance Of Apache Spark Jobs 세션을 나름대로 정리해보았습니다. 잘못된 정보나 오류가 있다면 언제나 지적 부탁드립니다 :)

튜닝에 영향을 줄 수 있는 요소들

모든 요소는 무조건적으로 많을수록 좋아지지 않고 적을수록 좋아지지 않는다.

각 요소들은 다른 요소의 performance에 영향을 준다.

Stage를 skip하는 경우는 좋다. Tuning을 잘 한 결과물.

Core 개수

많을 경우

  • 병렬성을 증가시킬 수 있음
  • 스케쥴링 시간이 늘어남

메모리

많을 경우

  • GC 시간이 늘어남

파티션

너무 많을 경우

  • 스케쥴링 issue 발생

너무 적을 경우

  • 병렬성 감소

데이터 쏠림 현상 (skew)

  • 성능 저하 발생
  • 클러스터에서의 불균형 발생

확인 방법

  • spark UI에서는 모든 task를 보여주지 않음
  • 메모리를 과도하게 사용하는 경우 의심 가능
    • 첫 job을 시작할 때는 data ingestion을 수행하느라 메모리를 많이 사용하기 때문에 skew로 판단하지 말 것
  • executor에 heartbeat가 끊긴 경우
  • RDD의 파티션 크기를 확인

해결 방법

repartition은 별게없으면서도 가장 효과적인 방법

  • 파티션을 narrow하게 만들기 좋음
  • 파티션의 개수를 늘려야 할 때는 repartition
  • 파티션의 개수를 줄여야 할 때는 repartition대신 coalesce를 사용

ingestion

파티션을 읽을 때 JDBC를 통해 spark option 사용

  • partitionColumn
    • 쏠린 파티션이 아니어야 함 (primary key 처럼)
    • MOD를 활용해보는 것도 좋음
  • lower/upperBound
  • numPartitions
    • 파티션의 최대 개수 지정

Cache / Persist

  • transformation한 뒤 DF를 재사용
  • cache를 사용하지 않으면 DF가 매번 생성됨
  • 너무 많이 cache를 사용하게 되면
    • 메모리 performance 저하
    • GC가 커짐
    • 오히려 성능 감소 가능성이 있음

그 외의 trick들

seq.par.foreach 사용하기

  • seq.foreach 보다 나은 성능
  • 병렬성 증가
  • race condition 또는 비결정적 결과(non-deterministic results)가 도출될 수 있음
    • accumulator 또는 synchronization을 사용해서 방지

UDF의 사용은 가능한 자제하기

  • UDF의 동작 원리
    • 모든 row를 object로 비직렬화
    • lambda 적용
    • 다시 직렬화
  • 더 많은 garbage 생성

스케쥴링

Round Robin

  • 쓰레드를 통해 하나의 application에서 여러 job을 동시에 돌릴 수 있음
  • 리소스 utilization에 더 좋음
  • 디버그와 튜닝을 더 어렵게 만듦

spark.scheduler.modeFAIR 로 설정하면

  • job들을 priority에 따라 pool로 묶음
  • pool에 속하지 않은 thread로부터 생성된 job은 default pool로 들어감
  • pool, weight, minShare의 config는 fairscheduler.xml에 있음

직렬화

Java Serializer

  • 대부분의 타입에서의 default로 사용됨
  • 모든 class와 호환 가능
  • 매우 유연한 편
  • 느림

Kyro Serializer

  • RDD shuffling과 간단한 type의 default로 사용됨
  • 빠름
  • SparkConf 에서 org.apache.spark.serializer.KyroSerializer 설정으로 사용 가능
  • 사용하고 싶은 class를 등록해서 원하는 만큼 적용 가능

GC 튜닝

GC 튜닝이 필요할 때

  • 너무 많은 객체가 할당된 경우
  • task가 끝나기 전에 Full GC 발생하는 경우
  • 너무 많은 RDD 캐시가 있는 경우
  • GC가 너무 빈번히 일어나는 경우
  • GC에 너무 많은 시간이 소요되는 경우
  • heartbeat가 끊긴 경우

Parrallel GC (spark의 default GC)

  • Heap size가 Young / Old generation으로 나뉨

각 상황 별 추천 해결 방법

  • minor GC가 자주 발생하는 경우
    • Eden과 Survivor size를 늘려줌
  • major GC가 자주 발생하는 경우
    • 너무 많은 객체가 생성된 경우 young size를 늘려줌
    • old size를 늘려줌
    • spark.memory.fraction 을 줄여줌
  • task가 끝나기 전에 Full GC가 발생하는 경우
    • GC를 통해 young space를 clear하는 것을 추천
    • memory를 늘려줌

G1GC

Appilication scale이 크거나 Heap size가 8GB보다 큰 경우 추천

  • 다음 옵션으로 설정 가능
    • -XX:ParrallelGCThreads = n
    • -XX:ConcGCThreads = [n, 2n]
    • -XX:InitiatingHeapOccupancyPercent = 35

마치며..

  • 성능 튜닝은 계속 반복되는 것이 정상
  • 튜닝은 상황에 따라 매번 달라짐
  • Spark UI, 로그, 사용가능한 모니터링 툴 등을 반드시 활용할 것
  • 대충 아무 trick이나 쓰지 말고 성능 저하의 주요 원인에 집중할 것
  • 완벽한 튜닝을 하기란 거의 불가능하다

+ Recent posts