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.mode
를 FAIR
로 설정하면
- 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이나 쓰지 말고 성능 저하의 주요 원인에 집중할 것
- 완벽한 튜닝을 하기란 거의 불가능하다
'Spark' 카테고리의 다른 글
Chapter 2. 스파크는 어떻게 동작하는가? (0) | 2022.01.31 |
---|---|
Chapter 1. 고성능 처리를 위한 스파크 시작하기 (0) | 2022.01.31 |
[DATA+AI SUMMIT 2021] Monitor Apache Spark 3 on Kubernetes using Metrics and Plugins 요약 (0) | 2021.12.31 |
[Spark] Spark 3.x Structured Streaming의 checkpoint는 k8s환경에서 어떻게 관리해야할까? (0) | 2021.12.31 |
[Spark] repartition vs coalesce (feat. orderBy) (0) | 2021.12.31 |