운영과 성능 모니터링
느린 Spark 작업 앞에서 어디서 시작해야 하는가
Spark 작업이 예상보다 오래 걸리거나 중간에 멈추면, 처음 드는 반응은 설정값을 올리거나 코드를 다시 쓰는 것이다. 하지만 원인을 모르고 메모리를 두 배 늘리거나 파티션 수를 바꾸면 우연히 나아지거나, 아무것도 바뀌지 않거나, 오히려 나빠지는 세 경우 중 하나가 된다.
Spark 성능 튜닝의 첫 단계는 측정이다. Spark는 실행 중인 모든 정보를 여러 경로로 노출한다. Spark UI, 메트릭 시스템, REST API, 이벤트 로그가 그것이다. 이 중에서 가장 먼저 손에 잡히는 것이 Spark Web UI다. UI에서 어느 stage가 오래 걸리는지, 어느 task에서 skew가 있는지, 어느 executor에서 GC 압박이 있는지 먼저 파악한 뒤에야 "무엇을 고쳐야 하는가"가 좁혀진다.
Jobs 탭: 전체 흐름을 가장 빠르게 보는 방법
Jobs 탭은 SparkContext에서 실행된 모든 job의 목록과 상태를 보여준다. 각 job은 하나의 Action(예: count(), show(), write.save())에 대응하고, 그 안에 여러 Stage가 있다.
Jobs 탭에서 먼저 확인하는 것은 두 가지다.
- Failed job: 실패한 job의 Exception이 드라이버 로그에 기록되고, UI에서 Stage → Task 단계까지 드릴다운해 어떤 Task에서 실패했는지 볼 수 있다.
- 비정상적으로 긴 duration: 예상 시간보다 훨씬 긴 job이 있으면 해당 job의 Stage detail을 열어야 한다.
Stages 탭: 병목을 구체적으로 찾는 곳
Stage detail은 성능 분석의 핵심 화면이다. 한 Stage 안의 모든 Task에 대한 요약 통계와 Task별 세부 지표를 볼 수 있다.
| 메트릭 | 확인하는 것 | 이상 신호 |
|---|---|---|
| Duration | Task 실행 시간 | 중앙값 대비 최댓값이 5배 이상 → 데이터 스큐 |
| GC Time | JVM 가비지 컬렉션 시간 | Task time의 10% 초과 → 메모리 압박 |
| Shuffle Read Size | 이 Stage에서 읽은 셔플 데이터 | 한 Task만 비정상적으로 큼 → 키 스큐 |
| Shuffle Write Size | 다음 Stage로 보낼 셔플 데이터 | 총량이 클수록 다음 Stage 비용 증가 |
| Spill (Memory) | 메모리 초과로 디스크에 임시 저장된 양 | Spill이 크면 executor memory 부족 신호 |
| Input Size | 파일/소스에서 읽은 데이터 | 파티션 간 크기 차이 크면 입력 불균형 |
데이터 스큐 진단 패턴: Task Duration 분포를 보면 대부분 task는 수 초인데 특정 task만 수 분이 걸린다. 이때 Shuffle Read의 Max가 Median의 10배 이상이면 스큐 가능성이 높다.
Task Summary (Stage 12):
Duration GC Time Shuffle Read Spill(Mem)
25th pct 3.2s 0.1s 28 MB 0
Median 3.5s 0.1s 31 MB 0
75th pct 4.0s 0.2s 35 MB 0
Max 42.1s 1.8s 412 MB 180 MB ← 스큐 + SpillExecutors 탭: 클러스터 리소스 상태 한눈에
Executors 탭은 각 Executor의 메모리, 디스크, CPU, GC 상태를 보여준다.
| 확인 항목 | 의미 | 대응 |
|---|---|---|
| GC Time % (>10%) | JVM이 너무 자주 또는 오래 GC를 실행 | spark.executor.memory 증가, 메모리 사용 최적화 |
| Storage Memory Used | 캐시/persist로 점유된 메모리 | 불필요한 cache 해제, Storage Fraction 조정 |
| Shuffle Spill (Disk) | shuffle 중 디스크로 넘친 데이터 | executor memory 증가 또는 파티션 수 증가 |
| Dead Executor | Executor가 죽었다 재시작됨 | OOM, 네트워크 단절, 호스트 장애 |
| Task Time vs GC Time | 전체 task 시간 대비 GC 비율 | GC가 30% 이상이면 심각한 메모리 문제 |
Spark UI는 GC 시간이 Task 시간의 10%를 넘으면 Executor를 빨간색으로 표시한다. 이 상태에서는 처리량 자체보다 GC 일시 정지 때문에 latency가 급격히 올라갈 수 있다.
SQL/DataFrame 탭: 실행 계획과 연산자 비용
SQL 탭은 DataFrame/SQL 쿼리 단위로 실행 계획 DAG를 시각화한다. 각 연산자 노드에 처리된 행 수, 출력 행 수, 소요 시간이 표시된다.
실무에서 SQL 탭에서 확인하는 세 가지:
- Exchange 위치와 크기: Exchange가 많거나 크면 셔플 비용이 크다. 이전 장에서 다룬 파티셔닝 최적화를 검토할 지점이다.
- BroadcastExchange vs SortMergeJoin: join에
BroadcastHashJoin이 아닌SortMergeJoin이 찍혔는데 테이블이 작다면 통계 부족 또는 broadcast threshold 설정 문제다. - 필터/프로젝션 위치:
Filter가FileScan바로 위에 있으면 pushdown이 됐다는 뜻이다.Filter가 Scan 위가 아니라 먼 곳에 있다면 pushdown이 안 된 것이다.
== SQL Plan (Stage 4) ==
*(2) HashAggregate (keys=[region], functions=[sum(revenue)])
Output rows: 10 <- 집계 후 10개 지역
+- Exchange hashpartitioning(region, 200), REPARTITION_BY_COL
+- *(1) HashAggregate (partial sum)
+- *(1) Project [region, revenue]
+- *(1) Filter (date >= 2026-01-01)
<- Filter가 Scan 바로 위에 있으면 pushdown 성공
+- *(1) FileScan parquet [region, revenue, date]
PartitionFilters: [] <- 파티션 프루닝 없음 → 주의핵심 설정값과 메모리 구조
Spark Executor의 메모리는 크게 네 영역으로 나뉜다.
셔플·정렬·해시 등 연산 중 임시 사용
cache/persist RDD·DataFrame
UDF, 사용자 자료구조
~300 MB 고정
Off-Heap: Python 프로세스, 네이티브 라이브러리
운영에서 자주 조정하는 메모리 설정:
| 설정 | 기본값 | 의미 |
|---|---|---|
spark.executor.memory | 1g | Executor JVM heap 크기 |
spark.executor.memoryOverhead | max(384MB, 10% of executor.memory) | Off-heap 영역 (Python, 네이티브) |
spark.memory.fraction | 0.6 | Heap의 60%를 Execution + Storage에 사용 |
spark.memory.storageFraction | 0.5 | Execution+Storage 풀에서 Storage 초기 비율 |
spark.sql.shuffle.partitions | 200 | 셔플 후 파티션 수 (AQE 없이는 직접 조정 필요) |
spark.sql.adaptive.enabled | true | Spark 3.x 기본 활성화 |
spark.sql.adaptive.advisoryPartitionSizeInBytes | 64MB | AQE가 병합할 때 목표 파티션 크기 |
PySpark를 쓸 때는 Python 워커 프로세스가 Executor JVM과 별개로 메모리를 쓴다. 대규모 pandas UDF나 Arrow 직렬화를 쓰면 memoryOverhead를 충분히 늘려야 OOM을 막을 수 있다.
공통 성능 문제와 진단-해결 패턴
데이터 스큐
증상: 대부분 task가 수 초인데 한두 개만 수십 분, Stage duration이 그 task에 끌려감.
원인: join key나 groupBy key 분포가 불균등. 예: user_id = NULL이나 특정 VIP 사용자에 이벤트 집중.
해결:
- AQE 스큐 처리 활성화:
spark.sql.adaptive.skewJoin.enabled=true - 스큐 키에 salt 추가: key에
concat(key, floor(rand() * N))붙여 분산 후 집계 - Broadcast join: 작은 쪽 테이블이 충분히 작으면
broadcast(smallDf)hint
작은 파일 문제
증상: 수천~수만 개의 작은 파일을 읽는 Stage에서 스케줄링 오버헤드로 느림.
해결: spark.sql.files.maxPartitionBytes로 파일을 더 큰 파티션으로 묶거나, AQE coalescing이 post-shuffle에서 자동으로 파티션을 합치도록 둔다.
과도한 셔플
증상: SQL Plan에서 Exchange가 많고, Shuffle Write/Read가 GB~TB 수준.
해결:
- 동일 key로 이미 파티션된 경우
repartition제거 - join 전 filter로 데이터 양 줄이기
- 작은 dimension table은 broadcast
GC 압박
증상: Executor tab에서 GC time이 task time의 10~30%.
해결:
- executor memory 증가
spark.serializer=org.apache.spark.serializer.KryoSerializer로 직렬화 크기 감소- 불필요한 cache 제거, 큰 중간 DataFrame을 변수에 계속 보관하지 않기
- PySpark에서는 Arrow 기반 UDF(
pandas_udf)로 직렬화 비용 감소
History Server와 이벤트 로그
Spark Web UI는 작업이 종료되면 사라진다. History Server는 이벤트 로그를 읽어 종료된 작업의 UI를 다시 제공한다.
# 이벤트 로그 활성화 설정
spark.eventLog.enabled=true
spark.eventLog.dir=hdfs:///spark-logs # 또는 s3://my-bucket/spark-logs
# History Server 실행
$SPARK_HOME/sbin/start-history-server.sh
# 기본 포트 18080으로 UI 접근이벤트 로그 파일에는 모든 job, stage, task 이벤트가 JSON으로 기록된다. 로그가 매우 클 수 있으므로 운영 환경에서는 보존 기간과 최대 파일 수를 제한하는 것이 좋다.
Prometheus와 Grafana 연동
Spark 3.x부터 메트릭 시스템에 Prometheus sink가 내장됐다. $SPARK_CONF_DIR/metrics.properties에 다음을 추가하면 된다.
# metrics.properties
*.sink.prometheusServlet.class=org.apache.spark.metrics.sink.PrometheusServlet
*.sink.prometheusServlet.path=/metrics/prometheus
master.sink.prometheusServlet.path=/metrics/master/prometheus
applications.sink.prometheusServlet.path=/metrics/applications/prometheus이후 Prometheus가 http://<driver>:4040/metrics/prometheus를 스크레이핑하면 executor 메모리, GC 시간, 셔플 I/O, 태스크 수 등 수십 가지 메트릭을 수집할 수 있다. Grafana에서 "Spark 대시보드" 템플릿을 가져오면 기본 대시보드를 빠르게 구성할 수 있다.
Kubernetes 환경에서는 spark.metrics.conf ConfigMap을 Pod에 마운트하거나, Spark Operator에서 sparkConf로 설정을 전달하는 방식이 일반적이다.
운영 체크리스트
실제 Spark 작업을 처음 프로덕션에 올리거나 성능 문제를 진단할 때 따라볼 순서다.
| 단계 | 확인 항목 |
|---|---|
| 1. 계획 확인 | EXPLAIN EXTENDED로 Catalyst 최적화, filter pushdown, join 전략 확인 |
| 2. Stage 분석 | UI Jobs → Stages: 가장 오래 걸리는 Stage 파악 |
| 3. Skew 확인 | Stage detail의 Task Duration 분포: Max/Median 비율 |
| 4. Shuffle 확인 | Shuffle Read/Write 크기, Exchange 개수 |
| 5. GC 확인 | Executors 탭: GC time % 확인 |
| 6. Spill 확인 | Stage task: Spill(memory), Spill(disk) 존재 여부 |
| 7. AQE 효과 확인 | Physical Plan에서 AQE가 적용한 최적화 내용 |
| 8. 리소스 효율 | Executor CPU/Memory 이용률: 과다 할당 또는 부족 여부 |
Structured Streaming 작업이라면 추가로:
| 항목 | 확인 방법 |
|---|---|
| 처리 지연(latency) | Streaming 탭의 batchDuration 추이 |
| 입력 속도 vs 처리 속도 | inputRowsPerSecond vs processedRowsPerSecond |
| State Store 크기 | numRowsTotal per stateful operator |
| 워터마크 진행 | onQueryProgress의 eventTime watermark 값 |
Spark 버전별 모니터링 개선 이력
Spark가 버전을 거치면서 모니터링과 운영 편의가 많이 좋아졌다.
| 버전 | 주요 개선 |
|---|---|
| 3.0 | AQE 기본 활성화, 동적 파티션 병합 |
| 3.1 | Structured Streaming: availableNow 트리거 추가 |
| 3.2 | Spark UI 개선: SQL tab task 지표 강화, Session window 지원 |
| 3.3 | Prometheus metrics 강화, ANSI mode 기본 활성화 |
| 3.4 | Spark Connect(원격 클라이언트 연결), PySpark UDF 성능 개선 |
| 3.5 | Structured Streaming State Store 성능 개선, RocksDB State Store GA |
RocksDB 기반 State Store는 Spark 3.2부터 preview로 추가됐다. 기본 HDFS 기반 State Store 대신 RocksDB를 쓰면 대용량 상태를 다루는 Structured Streaming에서 메모리 효율과 체크포인트 성능을 크게 개선할 수 있다. spark.sql.streaming.stateStore.providerClass=org.apache.spark.sql.execution.streaming.state.RocksDBStateStoreProvider로 활성화한다.
마무리
Spark 운영과 성능 튜닝은 "설정값 최적화"보다 "올바른 문제 진단"이 먼저다. Spark UI의 Jobs → Stages → Tasks → Executors 순서로 병목을 찾고, SQL Plan에서 Exchange와 join 전략을 확인하고, GC와 Spill 신호를 읽는 습관이 쌓이면 대부분의 성능 문제는 원인을 좁힐 수 있다.
History Server와 Prometheus 연동으로 작업이 끝난 뒤에도 데이터를 분석하고, 장기 추세를 대시보드에서 관찰할 수 있게 하면 문제가 터지기 전에 이상 신호를 잡을 수 있다. Spark 성능 이슈의 대부분은 반복 패턴이다. 데이터 스큐, 지나친 셔플, GC 압박, 작은 파일 문제. 이 네 가지 패턴을 UI에서 빠르게 식별하는 것이 Spark 엔지니어의 핵심 운영 능력이다.
References
- Monitoring and Instrumentation — Apache Spark 4.1 Documentation
- Web UI — Apache Spark 4.1 Documentation
- A Deep Dive into Spark UI for Job Optimization — Microsoft Community Hub
- Why Data Skew and Garbage Collection Causes Spark Apps To Slow or Fail — Unravel Data
- Apache Spark Performance Tuning: 7 Optimization Tips — Flexera
- Mastering Spark Metrics: A Complete Guide — Ultra Tendency Academy
- Spark UI Debugging Techniques — Medium, May 2026