LLM WikiAccess-protected knowledge portal
← 스터디 홈
6편 · 약 16분

운영과 성능 모니터링

느린 Spark 작업 앞에서 어디서 시작해야 하는가

Spark 작업이 예상보다 오래 걸리거나 중간에 멈추면, 처음 드는 반응은 설정값을 올리거나 코드를 다시 쓰는 것이다. 하지만 원인을 모르고 메모리를 두 배 늘리거나 파티션 수를 바꾸면 우연히 나아지거나, 아무것도 바뀌지 않거나, 오히려 나빠지는 세 경우 중 하나가 된다.

Spark 성능 튜닝의 첫 단계는 측정이다. Spark는 실행 중인 모든 정보를 여러 경로로 노출한다. Spark UI, 메트릭 시스템, REST API, 이벤트 로그가 그것이다. 이 중에서 가장 먼저 손에 잡히는 것이 Spark Web UI다. UI에서 어느 stage가 오래 걸리는지, 어느 task에서 skew가 있는지, 어느 executor에서 GC 압박이 있는지 먼저 파악한 뒤에야 "무엇을 고쳐야 하는가"가 좁혀진다.

드라이버 / Executor Spark Web UI (port 4040) 외부 메트릭 시스템 SparkContext DAGScheduler Executor Task Metrics SparkListener Event Bus Jobs Stages Executors SQL Streaming Prometheus Grafana History Server 이벤트 로그 REST API /api/v1/... Custom SparkListener 작업 완료 후에는 History Server에서 동일한 UI를 확인할 수 있다 (이벤트 로그 기반)
Spark 모니터링 계층 구조

Jobs 탭: 전체 흐름을 가장 빠르게 보는 방법

Jobs 탭은 SparkContext에서 실행된 모든 job의 목록과 상태를 보여준다. 각 job은 하나의 Action(예: count(), show(), write.save())에 대응하고, 그 안에 여러 Stage가 있다.

Jobs 탭에서 먼저 확인하는 것은 두 가지다.

  1. Failed job: 실패한 job의 Exception이 드라이버 로그에 기록되고, UI에서 Stage → Task 단계까지 드릴다운해 어떤 Task에서 실패했는지 볼 수 있다.
  2. 비정상적으로 긴 duration: 예상 시간보다 훨씬 긴 job이 있으면 해당 job의 Stage detail을 열어야 한다.

Stages 탭: 병목을 구체적으로 찾는 곳

Stage detail은 성능 분석의 핵심 화면이다. 한 Stage 안의 모든 Task에 대한 요약 통계와 Task별 세부 지표를 볼 수 있다.

메트릭확인하는 것이상 신호
DurationTask 실행 시간중앙값 대비 최댓값이 5배 이상 → 데이터 스큐
GC TimeJVM 가비지 컬렉션 시간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   ← 스큐 + Spill

Executors 탭: 클러스터 리소스 상태 한눈에

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 ExecutorExecutor가 죽었다 재시작됨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 탭에서 확인하는 세 가지:

  1. Exchange 위치와 크기: Exchange가 많거나 크면 셔플 비용이 크다. 이전 장에서 다룬 파티셔닝 최적화를 검토할 지점이다.
  2. BroadcastExchange vs SortMergeJoin: join에 BroadcastHashJoin이 아닌 SortMergeJoin이 찍혔는데 테이블이 작다면 통계 부족 또는 broadcast threshold 설정 문제다.
  3. 필터/프로젝션 위치: FilterFileScan 바로 위에 있으면 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의 메모리는 크게 네 영역으로 나뉜다.

spark.executor.memory (JVM Heap)
Execution Memory
셔플·정렬·해시 등 연산 중 임시 사용
Storage Memory
cache/persist RDD·DataFrame
통합 메모리 풀: 두 영역이 동적으로 빌려 씀
User Memory
UDF, 사용자 자료구조
Reserved
~300 MB 고정
spark.executor.memoryOverhead
Off-Heap: Python 프로세스, 네이티브 라이브러리
Spark Executor 메모리 구조

운영에서 자주 조정하는 메모리 설정:

설정기본값의미
spark.executor.memory1gExecutor JVM heap 크기
spark.executor.memoryOverheadmax(384MB, 10% of executor.memory)Off-heap 영역 (Python, 네이티브)
spark.memory.fraction0.6Heap의 60%를 Execution + Storage에 사용
spark.memory.storageFraction0.5Execution+Storage 풀에서 Storage 초기 비율
spark.sql.shuffle.partitions200셔플 후 파티션 수 (AQE 없이는 직접 조정 필요)
spark.sql.adaptive.enabledtrueSpark 3.x 기본 활성화
spark.sql.adaptive.advisoryPartitionSizeInBytes64MBAQE가 병합할 때 목표 파티션 크기

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.0AQE 기본 활성화, 동적 파티션 병합
3.1Structured Streaming: availableNow 트리거 추가
3.2Spark UI 개선: SQL tab task 지표 강화, Session window 지원
3.3Prometheus metrics 강화, ANSI mode 기본 활성화
3.4Spark Connect(원격 클라이언트 연결), PySpark UDF 성능 개선
3.5Structured 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