Blog
pysparksparkconfigurationtuningaqedata-engineering

PySpark 설정 튜닝 종합 — 어떤 config를 언제 바꿔야 하는가

수많은 Spark 설정 중 실제로 효과 있는 것만 정리합니다. 익스큐터 사이징(코어·메모리·인스턴스), AQE, 셔플 파티션, 직렬화, 동적 할당, 그리고 "메모리부터 올리는" 안티패턴 대신 근거 기반으로 튜닝하는 순서를 다룹니다.

Data Dynamics2026年6月5日10 min read
This post is not yet translated. The original Korean version is shown below.

Spark 설정은 수백 개입니다. 그래서 많은 팀이 "느리면 executor.memory 올리고, 안 되면 인스턴스 늘리는" 추측성 튜닝을 반복합니다. 자원만 낭비하고 문제는 그대로입니다. 사실 실제로 효과 있는 설정은 십여 개이고, 무엇을 언제 바꿔야 하는지에는 명확한 순서가 있습니다.

이 글은 정말 중요한 Spark 설정만 골라, 무엇을 어떤 근거로 조정하는지 정리합니다. (개별 문제 — 스큐·OOM·작은 파일 — 의 상세 튜닝은 각 전용 글에서 다뤘으니, 여기서는 전체 그림과 우선순위를 봅니다.)

1. 튜닝의 황금률 — 측정하고, 가장 큰 병목부터

❌ 추측: 느리다 → memory 올린다 → 안 된다 → 인스턴스 늘린다 → 비용만↑
✅ 근거: Spark UI 측정 → 병목 식별(스큐/셔플/OOM) → 해당 설정만 조정

설정을 바꾸기 전에 Spark UI 로 병목을 먼저 식별하세요(별도 글 "PySpark 느린 잡 디버깅"). 스큐인데 메모리를 올리거나, 셔플 문제인데 인스턴스를 늘리는 건 효과가 없습니다.

2. 가장 먼저 — AQE 켜기

Spark 3.x 의 Adaptive Query Execution 은 런타임에 파티션 병합·스큐 조인·조인 전략을 자동 조정합니다. 가장 적은 노력으로 가장 큰 효과를 주는 설정입니다.

spark.conf.set("spark.sql.adaptive.enabled", "true")                      # 마스터 스위치
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")   # 작은 파티션 자동 병합
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")             # 스큐 조인 자동 분할
spark.conf.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "128MB")

최신 Spark 는 AQE 가 기본 활성이지만, 명시적으로 확인하세요. AQE 하나로 작은 파일·스큐·파티션 수 문제의 상당 부분이 자동 완화됩니다.

3. 익스큐터 사이징 — 가장 중요한 결정

자원 배분의 핵심은 세 값의 균형입니다: 익스큐터 코어, 메모리, 인스턴스 수.

spark.conf.set("spark.executor.cores", "4")          # 익스큐터당 동시 태스크
spark.conf.set("spark.executor.memory", "8g")        # JVM 힙
spark.conf.set("spark.executor.memoryOverhead", "2g")# 힙 밖(셔플·네이티브·Python)
spark.conf.set("spark.executor.instances", "20")     # 익스큐터 수

코어 수 — 4~5가 스위트스폿

코어 너무 적음(1~2) → 익스큐터 오버헤드 대비 비효율
코어 너무 많음(>5)  → HDFS/스토리지 I/O 처리량 저하, GC 경합
→ 익스큐터당 4~5 코어가 일반적 권장

메모리 — 힙과 오버헤드 구분

PySpark 는 Python 워커가 힙 밖 메모리를 쓰므로, memoryOverhead 를 넉넉히 둬야 "Container killed" 를 피합니다(별도 글 "PySpark Executor OOM 정복").

설정의미지침
executor.cores동시 태스크4~5
executor.memoryJVM 힙태스크당 충분히, GC 고려
executor.memoryOverhead힙 밖힙의 ~25%, PySpark 면 더
executor.instances익스큐터 수병렬도 = cores × instances

흔한 실수: "큰 익스큐터 몇 개" vs "작은 익스큐터 많이". 너무 큰 익스큐터는 GC pause 가 길고, 너무 작으면 오버헤드가 큽니다. 중간 크기(45코어, 816GB) 다수가 보통 안전합니다.

4. 셔플 파티션 — 200 기본값의 함정

spark.sql.shuffle.partitions 의 기본값 200 은 거의 모든 워크로드에 안 맞습니다. 데이터가 크면 파티션당 너무 커서 OOM, 작으면 빈 파티션만 200개입니다.

# AQE 가 켜져 있으면 런타임에 자동 조정되므로 기본값 신경 덜 써도 됨
spark.conf.set("spark.sql.adaptive.enabled", "true")
 
# AQE 없이 수동이라면: 파티션당 ~128MB 목표로
# num = 총 셔플 데이터 / 128MB
spark.conf.set("spark.sql.shuffle.partitions", "800")

AQE 의 coalescePartitions 가 켜져 있으면 이 값을 크게 잡아도(예: 2000) 런타임에 적정 수로 병합해주므로 안전합니다. AQE 시대에는 이 값을 일일이 맞추는 부담이 크게 줄었습니다.

5. 직렬화·압축

# Kryo 직렬화 (Java 기본보다 빠르고 작음)
spark.conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
 
# 셔플 압축(기본 켜짐) — 네트워크·디스크 절약
spark.conf.set("spark.shuffle.compress", "true")
spark.conf.set("spark.shuffle.spill.compress", "true")

Kryo 는 거의 항상 이득입니다. 압축은 기본 활성이며 대부분 그대로 두면 됩니다.

6. 동적 할당 — 자원 효율

고정 익스큐터 대신 부하에 따라 조절합니다(특히 공유 클러스터·K8s).

spark.conf.set("spark.dynamicAllocation.enabled", "true")
spark.conf.set("spark.dynamicAllocation.shuffleTracking.enabled", "true")  # K8s 필수
spark.conf.set("spark.dynamicAllocation.minExecutors", "2")
spark.conf.set("spark.dynamicAllocation.maxExecutors", "50")
spark.conf.set("spark.dynamicAllocation.executorIdleTimeout", "60s")

(K8s 에서의 셔플 추적·스팟 이슈는 별도 글 "PySpark on Kubernetes" 참고.)

7. 브로드캐스트·조인

# 자동 브로드캐스트 임계값 (기본 10MB) — 작은 차원 조인 최적화
spark.conf.set("spark.sql.autoBroadcastJoinThreshold", "50MB")
# -1 로 끄면 broadcast 안 함 (의도적 비활성 시)

너무 크게 잡으면 큰 테이블이 broadcast 돼 OOM 위험(별도 글 "Broadcast 변수와 대형 Lookup"). 50~100MB 정도가 보통 안전합니다.

8. 설정 우선순위 — 무엇부터 손대나

1. AQE 켜기 (skew/coalesce/skewJoin)        → 가장 큰 효과, 가장 적은 노력
2. 익스큐터 사이징 (cores 4~5, 메모리+오버헤드)
3. shuffle.partitions (AQE 있으면 크게 둬도 OK)
4. memoryOverhead (PySpark Container killed 대응)
5. 직렬화(Kryo), 동적 할당
6. autoBroadcastJoinThreshold
   ───
   그래도 안 되면 → 코드/데이터 문제 (스큐·UDF·작은파일) — 설정으로 못 고침

핵심: 설정 튜닝은 한계가 있습니다. 스큐, 잘못된 조인, 비싼 UDF, 작은 파일은 설정으로 못 고칩니다 — 코드·데이터 레이아웃을 바꿔야 합니다. 설정을 다 만져봐도 안 되면, 문제는 설정이 아니라 코드입니다.

9. 워크로드별 프로파일

워크로드강조 설정
대형 ETL 배치큰 익스큐터, shuffle.partitions↑, FTE
대화형/짧은 쿼리동적 할당, 작은 지연, broadcast
스트리밍트리거·백프레셔, 체크포인트, 적정 익스큐터
ML 학습메모리↑, 캐시, 적은 셔플
고동시성 공유동적 할당, 공정 스케줄러

10. 정리

영역핵심 설정효과
AQEadaptive.*자동 스큐·파티션·조인
사이징cores 4~5, 메모리, 오버헤드안정성·처리량
셔플shuffle.partitions(AQE면 덜 중요)파티션 크기
직렬화Kryo속도
동적 할당dynamicAllocation.*자원 효율
조인autoBroadcastJoinThreshold셔플 회피

Spark 설정 튜닝의 핵심은 "측정으로 병목을 찾고, 가장 효과 큰 것부터 조정하되, 설정의 한계를 인정하는 것"입니다. AQE 를 켜고 익스큐터를 합리적으로 사이징하는 것만으로 대부분의 잡이 안정화됩니다. 그래도 느리다면 — 설정을 더 만지기 전에 스큐·UDF·작은 파일 같은 코드·데이터 문제를 의심하세요. 좋은 설정은 좋은 코드를 빠르게 하지만, 나쁜 코드를 고쳐주지는 못합니다.


이 글은 Spark 3.5 기준으로 작성되었습니다. Spark 클러스터·잡의 종합 성능 튜닝이 필요하시면 언제든 문의해 주세요.

— Data Dynamics 엔지니어링 팀