디버깅과 모니터링: Spark UI, Spill, 파티션 크기, 작은 파일 문제
60 Databricks Interview Questions Senior DEs Actually Ask (2026) — 미상 (DataVidhya 블로그, 2026) Performance Tuning & Optimization, Q36-Q39, Q47, Q49-Q50 (pp.11-17)
이 모듈을 다 읽으면
- Spark UI에서 어떤 정보를 확인해 성능 병목을 진단하는지 설명할 수 있다
- Spill이 왜 발생하고 왜 성능에 치명적인지, 파티션 개수를 어떻게 조정해 대응하는지 설명할 수 있다
- Data Skipping과 Dynamic File Pruning이 각각 어떤 시점에 어떤 방식으로 읽을 데이터 양을 줄이는지 구분할 수 있다
Spark UI는 Job·Stage·Task 단위로 실행을 들여다보는 1차 디버깅 도구로, SQL 탭에서 실행 계획을, 개별 태스크의 실행 시간·GC 시간·셔플/스필 여부를 확인할 수 있다. 캐시(`df.cache()`)로 반복 사용하는 DataFrame의 재계산을 막고, executor 메모리를 초과해 데이터가 디스크로 밀려나는 'Spill'은 executor 크기를 늘리거나 파티션 수를 늘려 대응한다. Delta Lake의 Data Skipping(파일별 min/max 통계 활용)과 Dynamic File Pruning(조인 시 작은 테이블의 필터 결과로 큰 테이블 파일을 런타임에 건너뛰기)은 클라우드 스토리지에서 읽는 데이터 양 자체를 줄여주는 상호보완적인 최적화다.
Spark UI, 캐싱, Spill
Spark UI는 Spark 애플리케이션을 디버깅·모니터링하는 1차 도구다. Job, Stage, Task에 대한 상세 뷰를 제공하며, 'SQL' 탭에서 실행 계획을 보고 어떤 단계가 가장 오래 걸리는지 파악할 수 있다. 또한 'Straggler'(다른 태스크보다 훨씬 오래 걸리는 태스크), 높은 가비지 컬렉션(GC) 시간, 과도한 셔플이나 디스크 스필을 탐지하는 데도 도움을 준다.
캐싱은 DataFrame을 클러스터의 메모리(RAM)나 디스크에 저장해두는 것이다. 같은 DataFrame을 여러 다른 액션에 반복해 쓸 계획(예: 행 수를 세고 나서 다시 조인에 쓰는 경우)이라면 `df.cache()`를 쓴다. 캐싱이 없으면 Spark의 지연 평가(lazy evaluation) 특성상 액션이 호출될 때마다 DataFrame의 전체 계보(lineage)가 소스부터 다시 계산된다. 더는 필요 없어지면 `df.unpersist()`로 클러스터 리소스를 반환해야 한다.
'Spill to Disk'는 특정 태스크에 할당된 데이터가 Spark executor의 실행/저장 메모리 영역을 초과할 때 발생한다. 이 경우 Spark는 넘치는 데이터를 워커 노드의 로컬 디스크에 쓸 수밖에 없다. 디스크 I/O가 RAM보다 훨씬 느리기 때문에, 스필은 작업을 10배 이상 느리게 만들 수 있다. 보통 executor 크기를 늘리거나, 파티션 수를 늘려(각 태스크가 처리할 데이터를 더 작게 만들어) 해결한다.
핵심 포인트
- Spark UI의 SQL 탭에서 실행 계획을 확인하고, Stragglers·GC 시간·셔플/스필 여부로 병목을 진단한다
- 같은 DataFrame을 여러 액션에서 반복 사용할 때 `df.cache()`로 지연 평가에 의한 재계산을 막고, 끝나면 `df.unpersist()`한다
- Spill은 태스크 메모리 초과로 디스크에 쓰기가 발생하는 현상으로 작업을 10배 이상 느리게 만들 수 있으며, executor 크기 증가나 파티션 수 증가로 대응한다
파티션 크기 가이드라인과 Ganglia/Compute 탭
파티션 개수를 정하는 표준적인 어림값은 파티션당 약 128MB~200MB를 목표로 하는 것이다. '클러스터 CPU 코어 수의 2~4배'라는 경험칙도 있지만, Delta Lake와 AQE 같은 최신 기능이 이를 상당 부분 동적으로 관리해주는 경우가 많다. 매우 큰 데이터셋에서는 `spark.sql.shuffle.partitions`를 2000 이상으로 늘리기도 하고, 작은 데이터셋에서는 기본값 200이 오히려 과할 수 있다. — 참고: 이 수치들은 절대적인 규칙이 아니라 출발점으로 삼을 경험칙이며, AQE가 활성화된 환경에서는 수동 튜닝의 필요성이 점점 줄어드는 추세다.
Ganglia는 클러스터 가상 머신이 실제로 사용하는 물리적 리소스(CPU 사용률, OS 레벨 메모리 사용량, 네트워크 I/O)를 보여주는 도구로, Spark UI가 소프트웨어 레벨 지표(태스크, 스테이지)에 집중하는 것과 대비된다. — 정정: Ganglia UI는 이후 Databricks Runtime 버전에서 완전히 제거되었고, 그 기능은 클러스터 상세 화면의 'Metrics'(구 'Compute') 탭에 통합된 새로운 네이티브 모니터링 UI로 대체되었다. 즉 원문이 괄호로 남긴 '최신 DBR에서는 Compute 탭에 통합되어 있다'는 부연은 방향은 맞지만, 정확히는 '통합'이 아니라 'Ganglia 자체가 폐기되고 새 UI로 완전히 대체'된 것이다.
핵심 포인트
- 파티션당 128MB~200MB, 코어 수의 2~4배는 출발점 경험칙일 뿐 절대 규칙이 아니며 AQE가 이를 상당 부분 자동화한다
- Ganglia UI는 최신 DBR에서 단순히 '통합'된 것이 아니라 완전히 폐기되고 클러스터의 새 Metrics 탭으로 대체되었다 — 정정
작은 파일 문제, Data Skipping, Dynamic File Pruning
S3/ADLS 같은 클라우드 스토리지 시스템은 큰 파일을 읽는 데 최적화되어 있다. 수백만 개의 10KB짜리 작은 파일이 있으면 문제가 되는데, Spark가 파일을 읽을 때마다 클라우드 API에 네트워크 요청을 보내야 해 오버헤드가 크고, Spark Driver의 메타데이터도 비대해지기 때문이다. Databricks는 이를 세 가지로 해결한다: `OPTIMIZE`(작은 파일을 주기적으로 ~1GB 파일로 병합), Auto-Compaction(쓰기 작업 도중 자동으로 '미니 옵티마이즈'를 수행하는 Delta Lake 설정), Optimized Writes(초기 쓰기 단계에서 더 지능적으로 셔플링해 더 적고 큰 파일을 쓰려 시도).
Data Skipping은 Delta Lake가 매 파일마다 각 컬럼의 min/max 값을 저장해두고, `WHERE` 절이 있는 쿼리를 실행할 때 이 통계를 확인해 요청 범위를 물리적으로 포함할 수 없는 파일 자체를 읽지 않고 건너뛰는 최적화다. Dynamic File Pruning(DFP)은 여기서 한 걸음 더 나아가, 큰 테이블을 필터링된 작은 테이블과 조인할 때 작은 테이블의 필터 결과를 이용해 런타임에 큰 테이블의 파일을 '가지치기(prune)'해, 스토리지에서 읽는 데이터 양을 크게 줄인다.
핵심 포인트
- 작은 파일 문제는 클라우드 API 요청 오버헤드와 Driver 메타데이터 비대화를 유발하며, OPTIMIZE·Auto-Compaction·Optimized Writes로 대응한다
- Data Skipping은 파일별 min/max 통계로 쿼리와 무관한 파일 자체를 건너뛰는 최적화다
- Dynamic File Pruning은 조인 시 작은 테이블의 필터 결과를 런타임에 활용해 큰 테이블의 파일을 추가로 가지치기한다