Spark SQL 성능 튜닝
Spark SQL 성능 튜닝 (Performance Tuning)
DataFrame이나 SQL 워크로드가 느릴 때 먼저 점검할 수 있는 기법을 한자리에 모은 페이지예요. 데이터 캐싱, 파티션 조정, 조인 전략 선택, 그리고 옵티마이저에 실행 계획을 더 잘 짤 수 있는 추가 정보를 주는 순서로 살펴볼게요. 실제 설정값과 기본값은 원문 그대로 보존했으니 그대로 따라가면 돼요.
데이터 캐싱 (Caching Data)
Spark SQL은 인메모리 칼럼 형식으로 테이블을 캐시할 수 있어요. spark.catalog.cacheTable("tableName") 또는 dataFrame.cache()를 호출하면 됩니다. 그러면 Spark SQL은 필요한 컬럼만 스캔하고, 메모리 사용과 GC 부담을 줄이도록 압축을 자동 조정해요.
- 캐시 해제:
spark.catalog.uncacheTable("tableName")또는dataFrame.unpersist() - 캐시 여부 확인:
spark.catalog.isCached("tableName") - 특정 Dataset의 저장 수준 확인:
storageLevel속성 (캐시되지 않으면StorageLevel.NONE반환) - 실행 중인 애플리케이션 전체의 캐시 개요: Web UI의 Storage 탭 (액션이 실행돼 구체화된 뒤 저장 수준·크기·파티션 표시)
Spark는 두 가지 캐시 포맷을 지원해요.
- 기본 캐시 포맷: 표준 인메모리 칼럼 캐시 (기본 사용)
- Arrow 캐시 포맷: Apache Arrow 기반 캐시로 칼럼 워크로드의 읽기 성능을 높이고 Arrow 생태계 상호 운용을 지원
캐시 관련 설정은 spark.conf.set 또는 SQL의 SET key=value로 조정합니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
spark.sql.inMemoryColumnarStorage.compressed |
true | true면 각 컬럼의 데이터 통계에 따라 압축 코덱을 자동 선택 |
spark.sql.inMemoryColumnarStorage.batchSize |
10000 | 칼럼 캐싱 배치 크기 제어. 배치가 클수록 메모리 활용·압축이 좋아지지만 OOM 위험이 커짐 |
파티션 튜닝 (Tuning Partitions)
파일 기반 소스(Parquet, JSON, ORC)와 셔플 파티션을 조정하면 성능 차이가 큽니다. 주요 설정은 다음과 같아요.
| 설정 | 기본값 | 의미 |
|---|---|---|
spark.sql.files.maxPartitionBytes |
134217728 (128 MB) | 파일을 읽을 때 단일 파티션에 담을 최대 바이트 수 (파일 기반 소스에서만 유효) |
spark.sql.files.openCostInBytes |
4194304 (4 MB) | 파일을 여는 데 드는 추정 비용. 여러 파일을 한 파티션에 넣을 때 사용하며 과대 추정이 나음 |
spark.sql.files.minPartitionNum |
Default Parallelism | 분할 파일 파티션의 권장(보장 아님) 최소 수. 미설정 시 spark.sql.leafNodeDefaultParallelism 값 |
spark.sql.files.maxPartitionNum |
None | 분할 파일 파티션의 권장 최대 수 (3.5.0+). 초기 파티션이 이 값을 넘으면 이에 가깝게 재조정 |
spark.sql.shuffle.partitions |
200 | 조인·집계 시 데이터를 셔플할 때 사용할 파티션 수 |
spark.sql.sources.parallelPartitionDiscovery.threshold |
32 | 입력 경로가 이 임계값보다 많으면 Spark 분산 잡으로 병렬 목록화 |
spark.sql.sources.parallelPartitionDiscovery.parallelism |
10000 | 입력 경로 목록화의 최대 병렬도 |
Coalesce 힌트
Coalesce 힌트는 Dataset API의 coalesce, repartition, repartitionByRange와 비슷하게 출력 파일 수를 제어하고 파티션을 조정하는 데 쓰여요.
COALESCE: 파티션 수만 인자로 받음REPARTITION: 파티션 수, 컬럼, 또는 둘 다/둘 다 없이 인자로 받음REPARTITION_BY_RANGE: 컬럼 필수, 파티션 수는 선택REBALANCE: 초기 파티션 수, 컬럼, 또는 둘 다/둘 다 없이 인자로 받음REBALANCE_BY_SIZE: 권장 파티션 크기 필요, 선택적으로 컬럼 뒤에 붙임
SELECT /*+ COALESCE(3) */ * FROM t;
SELECT /*+ REPARTITION(3) */ * FROM t;
SELECT /*+ REPARTITION(c) */ * FROM t;
SELECT /*+ REPARTITION(3, c) */ * FROM t;
SELECT /*+ REPARTITION_BY_RANGE(c) */ * FROM t;
SELECT /*+ REPARTITION_BY_RANGE(3, c) */ * FROM t;
SELECT /*+ REBALANCE */ * FROM t;
SELECT /*+ REBALANCE(3, c) */ * FROM t;
SELECT /*+ REBALANCE_BY_SIZE(134217728) */ * FROM t;
통계 활용 (Leveraging Statistics)
Spark는 실행 계획의 각 노드(읽기, 필터, 조인 등)가 내보낼 행 수를 추정해 최선의 실행 계획을 고르는데, 그 추정은 여러 경로로 얻어지는 통계에 기반합니다.
- 데이터 소스: Parquet 메타데이터의 개수·min/max 값처럼 Spark가 데이터 소스에서 직접 읽는 통계
- 카탈로그: Hive Metastore 같은 카탈로그에서 읽는 통계.
ANALYZE TABLE을 실행할 때마다 수집/갱신 - 런타임: 쿼리 실행 중 Spark가 직접 계산하는 통계 (적응형 쿼리 실행 AQE의 일부)
통계가 없거나 부정확하면 최적 계획 선택이 어려워져 쿼리 성능이 나빠질 수 있어요. 다음과 같이 확보된 통계를 점검할 수 있습니다.
- 데이터 객체 통계:
DESCRIBE EXTENDED로 테이블/컬럼 통계 확인 - 쿼리 계획 추정:
EXPLAIN COST또는DataFrame.explain(mode="cost")로 최적화된 계획의 비용 추정 확인 - 런타임 통계: 쿼리 실행 중 SQL UI의 Details 섹션에서
Statistics(..., isRuntime=true)확인
조인 튜닝
작은 테이블을 브로드캐스트 조인하면 셔플을 피하고 성능이 크게 좋아져요. hint("broadcast") 또는 SQL 힌트로 지정할 수 있습니다.
-- BROADCAST, BROADCASTJOIN, MAPJOIN 모두 허용
SELECT /*+ BROADCAST(r) */ * FROM src s JOIN records r ON s.key = r.key
spark.table("src").join(spark.table("records").hint("broadcast"), "key").show()
적응형 쿼리 실행 (Adaptive Query Execution, AQE)
AQE는 Spark SQL의 최적화 기법으로, 런타임 통계를 활용해 가장 효율적인 실행 계획을 고르는 방식이에요. Apache Spark 3.2.0부터 기본 활성화되어 있으며, spark.sql.adaptive.enabled가 총괄 설정입니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
spark.sql.adaptive.enabled |
true | true면 쿼리 실행 중간에 정확한 런타임 통계로 쿼리 계획을 재최적화 |
AQE가 하는 주요 최적화는 다음과 같습니다.
- 포스트 셔플 파티션 병합 (Coalescing Post Shuffle Partitions):
spark.sql.adaptive.coalescePartitions.enabled(기본 true)와 함께 작동해 너무 많은 작은 task를 피하도록 파티션을 병합. 초기 파티션 수는spark.sql.adaptive.coalescePartitions.initialPartitionNum(미설정 시spark.sql.shuffle.partitions)으로 크게 잡아두고, Spark가spark.sql.adaptive.advisoryPartitionSizeInBytes(기본 64 MB)에 맞춰 런타임에 조정합니다.parallelismFirst(기본 true)는 목표 크기를 무시하고minPartitionSize(기본 1 MB)만 지켜 병렬도를 최대화. - 스큐 셔플 파티션 분할 (Splitting Skewed Shuffle Partitions):
spark.sql.adaptive.optimizeSkewsInRebalancePartitions.enabled(기본 true)로 데이터 스큐를 피하려고 커진 파티션을 목표 크기(advisoryPartitionSizeInBytes)에 따라 분할. - 소트-머지 조인 → 브로드캐스트 조인 변환: 런타임 통계에서 어느 한 조인 측이
spark.sql.adaptive.autoBroadcastJoinThreshold보다 작으면 소트-머지 조인을 브로드캐스트 해시 조인으로 전환. 처음부터 브로드캐스트를 계획하는 것만큼 효율적이진 않지만, 소트를 피하고 셔플 파일을 로컬에서 읽는 등 네트워크 트래픽을 줄입니다.
이 외에도 원문에는 부분 집계 최적화(Adaptive Partial Aggregation), 서브플랜 병합(Merging Subplans) 등 추가 기법을 다루니, 운영에서 미세 조정이 필요하면 원문 페이지를 함께 참고하세요.