- [스파크 완벽 가이드] Spark 성능 튜닝 가이드2024년 10월 14일
- 31514
- 작성자
- 2024.10.14.:16
이 글에서는 잡의 실행 속도를 높이기 위한 몇 가지 방법을 알아보겠습니다.
스파크의 잡을 최적화할 수 있는 방법은 크게 다음과 같이 두 가지가 존재합니다.
- 속성 값을 설정하거나 런타임 환경을 변경하는 간접적인 방법
- 개별 스파크 잡, 스테이지, 태스크를 튜닝하거나 코드 설계를 변경하는 직접적인 방법
그리고 이를 확인하기 위해 좋은 모니터링 도구와 잡 이력 추적 환경을 구성하는 것이 중요합니다.
간접적인 성능 향상 기법
1. 좋은 설계 방안
- 구조적 API로 만들 수 없는 사용자 정의 트랜스포메이션(RDD 트랜스포메이션/UDF)을 사용해야 한다면, 해당 부분만 파이썬과 R을 피하자.
- 직접 RDD를 작성하면 Spark SQL 엔진의 최적화 혜택을 받을 수 없다.
- UDF 사용은 최대한 자제하는 것이 좋다.
- UDF는 데이터를 JVM 객체로 변환하고 쿼리에서 레코드당 여러 번 수행되므로 많은 자원을 소모하기 때문이다.
2. RDD 객체 직렬화
- Kyro를 사용하면 간결하고 효율적인 직렬화를 할 수 있다.
3. 클러스터 설정
- 하드웨어와 사용 환경에 따라 변화하는 클러스터에 대응하기 위해 머신 자체의 성능을 모니터링 하자.
- 애플리케이션이 더이상 사용하지 않는 자원을 클러스터에 반환하고, 필요할 때 다시 요청하는 기능을 고려하자.
- spark.dynamicAllocation.enabled 속성 값을 True로 변경
4. 스케줄링
- spark.scheduler.mode 속성 값을 FAIR로 변경하면 여러 사용자가 자원을 더 효율적으로 공유할 수 있다.
- --max-executor-cores 인수를 사용하면 애플리케이션에 필요한 익스큐터의 코어 수를 조절할 수 있다.
5. 보관용 데이터
효율적인 읽기를 위해 저장소 시스템과 데이터 포맷을 적절히 선택해야 한다.
<파일 기반 장기 데이터 저장소>
- 데이터를 바이너리 형태로 저장하려면 구조적 API를 사용하자.
- CSV 파일보다 Parquet를 사용하자.
<분할 가능한 파일 포맷과 압축>
- 포맷 선택 시 분할 가능한지 확인하자.
- 여러 태스크가 파일의 서로 다른 부분을 동시에 읽어서 모든 코어를 활용할 수 있다.
- 분할 불가 : JSON, ZIP, TAR
- 분할 가능 : gzip, bzip2, lz4
<테이블 파티셔닝>
- 데이터의 날짜 필드 같은 키를 기준으로 개별 디렉터리에 파일을 저장하여 파티셔닝하자.
<버켓팅>
- 데이터를 버켓팅하면 사용자가 조인이나 집계를 수행하는 방식에 따라 데이터를 사전 분할할 수 있다.
- 전체 파티션에 데이터를 균등하게 복사하여 Skew를 방지할 수 있다.
<파일 수>
- 데이터를 파티션이나 버켓으로 구성하려면 파일 수와 저장하려는 파일 크기도 고려해야 한다.
- 작은 파일을 많이 저장하는 경우 - 네트워크와 잡의 스케줄링 부하
- 큰 파일을 조금 저장하는 경우 - 병렬성 증가 및 태스크 수행 시간 증가
- maxRecordPerFile 옵션을 적용하면 write 메서드를 사용할 때 파일당 저장 레코드 수를 지정할 수 있다.
<데이터 지역성>
- 데이터가 위치한 물리적 위치와 그 데이터를 처리하는 컴퓨팅 자원의 물리적 위치가 가까울수록 성능이 향상된다.
<통계 수집>
- 쿼리 옵티마이저에게 정보를 제공하기 위해서 통계(테이블 수준 + 컬럼 수준)을 수집해야 한다.
- 테이블 수준 통계 수집 명령어
ANAYLYZE TABLE table_name COMPUTE STATISTICS
- 컬럼 수준 통계 수집 명령어
ANAYLYZE TABLE table_name COMPUTE STATISTICS FOR COLUMNS col1, col2, ...
6. 셔플 설정
- Spark의 외부 셔플 서비스를 설정하면 머신에서 실행되는 익스큐터가 바쁜 상황에서도 원격 머신에서 셔플 데이터를 읽을 수 있다.
- 하지만 코드가 복잡해지고 유지가 어려워져서 운영 환경에는 부적합하다.
7. 메모리 부족과 가비지 컬렉션
메모리 부족의 원인은 다음과 같습니다.
- 메모리를 너무 많이 사용하는 경우
- 가비지 컬렉션이 자주 수행되는 경우
- JVM 내에 많은 객체가 있어서 가비지 컬렉션이 미사용 객체를 정리하는 경우
3번의 경우 구조적 API를 사용하면 JVM 내에 객체를 생성하지 않으므로 해결할 수 있습니다.
<가비지 컬렉션 통계 모으기>
spark.executor.extraJavaOptions 속성에 스파크 JVM 옵션으로 -verbose:gc -XX:+printGCDetails -XX:+printGCTimeStamps 값을 추가합니다.
<JVM 메모리 관리 기초 지식>
자바 힙 공간은 Young 영역과 Old 영역으로 나누어진다.
Young 영역은 수명이 짧은 객체, Old 영역은 수명이 긴 객체를 유지한다.
Young 영역은 Eden, Survivor1, Survivor2로 나누어진다.
<가비지 컬렉션 수행 절차>
- Eden 영역이 가득 차면 Eden 영역에 대한 마이너 가비지 컬렉션 실행
- Eden 영역에서 살아남은 객체와 Survivor1 영역의 객체는 Survivor2로 복제
- Survivor1과 Survivor2 영역 교체
- 객체가 오래되었거나 Survivor2가 가득 차면 Old 영역으로 이동
- Old 영역이 거의 가득 차면 풀 가비지 컬렉션 발생
풀 가비지 컬렉션은 가장 느린 가비지 컬렉션 연산입니다.
<가비지 컬렉션 튜닝을 위한 목표>
- 수명이 긴 캐시 데이터 셋을 Old 영역에 저장한다.
- Young 영역의 충분한 공간을 유지한다.
<튜닝 방법>
- 가비지 컬렉션 통계 수집 후 발생 빈도를 확인하자.
- 풀 가비지 컬렉션이 자주 발생하면 spark.memory.fraction을 사용해서 캐싱에 사용되는 메모리 양을 줄이자.
- 마이너 가비지 컬렉션은 빈번한데, 메이저 가비지 컬렉션은 드물다면 Eden 영역에 메모리를 더 할당하자.
- 사용자 태스크가 HDFS에서 데이터를 읽는다면 데이터 블록 크기로 사용할 메모리 양을 추정할 수 있다.
- G1GC 가비지 컬렉터는 가비지 컬렉션이 병목 현상을 일으키고 각 영역의 크기를 늘려도 더 이상 부하를 줄일 수 없는 상황에서 사용하자.
직접적인 성능 향상 기법
1. 병렬화
- 특정 스테이지의 처리 속도를 높이려면 병렬성을 높여야한다.
- spark.default.parallelism과 spark.sql.shuffle.partitions 값을 클러스터 코어 수에 따라 설정한다.
- 처리해야 할 데이터의 양이 많다면 코어 당 최소 2~3개의 태스크를 할당한다.
2. 향상된 필터링
- 스파크 잡에서 필터링을 가장 먼저 수행하거나, 파티셔닝과 버켓팅 기법을 최대한 이른 타이밍에 활용하는 것이 가장 좋다.
3. 파티션 재분배와 병합
- 파티션 재분배는 셔플을 수행하지만, 데이터가 클러스터 전체에 균등하게 분배되므로 잡의 전체 실행 단계를 최적화할 수 있다.
- 가능한 적은 양의 데이터를 셔플하는 것이 좋다.
- 따라서 셔플 대신 동일 노드의 파티션을 하나로 합치는 coalesce 메서드 실행해 파티션 수를 줄인다.
- 파티션 재분배는 부하를 유발하지만, 애플리케이션의 전체적인 성능과 스파크 잡의 병렬성을 높일 수 있다.
- 잡이 여전히 느리다면 사용자 정의 파티셔닝 기법을 활용하자.
4. 임시 데이터 저장소 - 캐싱
- 만약 같은 데이터 셋을 계속 사용한다면 캐싱으로 최적화할 수 있다.
- 하지만 캐싱을 사용할 때 직렬화, 역직렬화, 저장소 자원 소모를 고려해야 한다.
- 캐싱은 cache 메서드를 사용하여 수행할 수 있지만, 지연 처리이기 때문에 적용하기 위해 액션이 필요하다.
- persist 메서드를 사용하면 캐시 영역을 지정할 수 있어, 보다 정교한 제어가 가능하다.
<캐싱 예제>
DF1 데이터프레임에 GROUP BY를 하여 3개의 데이터프레임을 생성해보자.
이때 소요되는 시간은 7초인걸 확인할 수 있다.
하지만 캐시를 적용하면 어떨까?
실행 시간이 2초로 줄어든 것을 확인할 수 있다.
지금은 데이터의 양이 많지 않아서 별로 체감되지 않지만, 대용량 데이터를 다룰 때는 유용할 거 같다.
5. 조인
- 동등 조인은 최적화하기 쉬우므로 우선적으로 사용하자.
- 조인 순서를 변경하는 것도 성능을 높이기 쉽다.
- 내부 조인을 사용해 필터링하는 것과 동일한 효과를 볼 수 있다.
- 브로드캐스트 조인 힌트를 사용하면 스파크카 쿼리 실행 계획을 생성할 때 지능적으로 세울 수 있다.
- 카데시안 조인과 전체 외부 조인은 자제하고 테이블 통계와 버켓팅을 통해 조인 성능을 높이자.
6. 집계
- 집계 전에 충분히 많은 수의 파티션을 가질 수 있도록 데이터를 필터링하자.
7. 브로드캐스트 변수
- 사용자 애플리케이션에서 사용되는 다수의 UDF에서 큰 데이터 조각을 사용한다면, 이 데이터 조각을 개별 노드에 전송해 읽기 전용 복사본으로 저장하자.
- 잡마다 데이터 조각을 재전송하는 과정을 건너뛸 수 있다.
성능 최적화 우선순위
- 파티셔닝과 효율적인 바이너리 포맷을 사용해 가능하면 작은 데이터를 읽는다.
- 충분한 병렬성을 보장하고, Skew 현상을 방지한다.
- 구조적 API를 최대한 활용한다.
Spark Monitoring Tool을 사용하여 가장 오래 실행되는 Stage를 최적화
'Book > 스파크 완벽 가이드' 카테고리의 다른 글
[스파크 완벽 가이드] 15장 - 스파크 애플리케이션의 생애주기 (0) 2024.10.21 [스파크 완벽 가이드] 18장 - 모니터링 (0) 2024.10.17 [스파크 완벽 가이드] 17장 - 스파크 배포 환경 (0) 2024.10.16 [스파크 완벽 가이드] 9장 - 데이터소스 (0) 2024.10.15 [스파크 완벽 가이드] 스파크 간단히 살펴보기 (0) 2024.10.14 다음글이전글이전 글이 없습니다.댓글