Spark 완벽 가이드 ch18. 모니터링과 디버깅

Q·2023년 1월 30일
0

Spark 완벽 가이드

목록 보기
19/24

모니터링 범위

  • 스파크 잡의 어느 지점에서 오류가 발생했는지 파악하려면 스파크 잡을 모니터링해야함

모니터링 대상 컴포넌트

스파크 애플리케이션과 잡

  • 클러스터에서 사용자 애플리케이션이 실행되는 상황을 파악하거나 디버깅하려면 가장 먼저 스파크 모니터링 도구를 사용해야함
  • 스파크 모니터링 도구
    • 스파크 UI
    • 스파크 로그
  • 스파크 UI와 스파크 로그는 실행 중인 애플리케이션의 RDD와 쿼리 실행 계획 같은 개념적 수준의 정보를 제공함

JVM

  • 스파크는 모든 익스큐터를 개별 자바 가상 머신(JVM)에서 실행
  • 저수준 디버깅이 필요하다면 JVM도구가 스파크 모니터링 도구보다 유용
  • JVM 도구
    • jstack: 스택 트레이스 제공
    • jmap: 힙 덤프 생성
    • jstat: 시계열 통계 리포트 제공
    • jconsole: JVM 속성변수를 시각화된 방식으로 탐색 가능하게 함
    • etc.

OS와 머신

  • JVM은 호스트 OS에서 실행되므로 머신의 상태를 모니터링해서 정상 작동 중인지 확인하는 것은 매우 중요
  • 자원(CPU, 네트웤, I/O 등)에 대한 모니터링도 함께 해야함
  • 이러한 모니터링 요소들은 클러스터 수준 모니터링 솔루션에서 확인 가능
    • dstat, iostat, iotop 같은 명령을 사용하면 세밀한 모니터링 가능

클러스터

  • 스파크 애플리케이션이 실행되는 클러스터도 모니터링해야함
  • 모니터링 대상: YARN, 메소스, 스탠드얼론 클러스터 매니저
  • 클러스터 모니터링 솔루션을 활용하면 클러스터가 동작하지 않는 상황을 빠르게 알 수 있음
    • Ganglia, Prometheus 등

모니터링 대상

  • 모니터링 대상은 크게 두 가지로 나눌 수 있음
    • 실행 중인 사용자 애플리케이션의 프로세스(CPU, 메모리 사용률 등)
    • 프로세스 내부에서의 쿼리 실행 과정(ex) 잡과 태스크)

드라이버와 익스큐터 프로세스

  • 스파크 애플리케이션을 모니터링할 땐 드라이버를 유심히 관찰해야함
  • 드라이버에는 모든 애플리케이션의 상태가 보관되어 있으며 안정적으로 실행 중인지 확인 가능
  • 익스큐터의 상태를 파악하는 것도 매우 중요
    • 스파크는 수월한 모니터링을 지원하기 위해 드롭위자드 메트릭 라이브러리 기반의 메트릭 시스템을 갖추고 있음

쿼리, 잡, 스테이지, 태스크

  • 특정 쿼리에서 무슨 일이 일어나기 위해 다음 각각의 정보를 알아야함
    • 쿼리
    • 스테이지
    • 테스크

스파크 로그

  • 스파크를 가장 상세하게 모니터링하는 방법 중 하나는 로그 파일을 살펴보는 것
  • 스파크 애플리케이션의 로그나 스파크 자체의 로그에서 발견된 이상한 이벤트는 잡의 실패 지점이나 원인 파악을 도움
  • 클러스터에서 스파크를 실행한다면 클러스터 매니저로 파일에 로그를 저장할 수 있음

스파크 UI

  • 스파크 UI는 실행 중인 애플리케이션과 스파크 워크로드에 대한 평가지표를 모니터링할 수 있는 화면 제공
  • 스파크 UI 탭
    • Jobs: 스파크 잡에 대한 정보 제공
    • Stages: 개별 스테이지(스테이지의 태스크를 포함)와 관련된 정보 제공
    • Storage: 스파크 애플리케이션에 캐싱된 정보와 데이터 정보 제공
    • Environment: 스파크 애플리케이션의 구성과 설정 관련 정보 제공
    • Executors: 애플리케이션에서 사용 중인 익스큐터의 상세 정보 제공
    • SQL: SQL과 DataFrame을 포함한 구조적 API 쿼리 정보 제공
path = '/FileStore/tables/bin/2010-summary.parquet'
df = spark.read.parquet(path)  
display(df.limit(10))
DEST_COUNTRY_NAMEORIGIN_COUNTRY_NAMEcount
United StatesRomania1
United StatesIreland264
United StatesIndia69
EgyptUnited States24
Equatorial GuineaUnited States1
United StatesSingapore25
United StatesGrenada54
Costa RicaUnited States477
SenegalUnited States29
United StatesMarshall Islands44
df.repartition(2).groupby('dest_country_name').count().explain()
== Physical Plan == 
*(3) HashAggregate(keys=[dest_country_name#330], functions=[finalmerge_count(merge count#348L) AS count(1)#343L]) 
+- Exchange hashpartitioning(dest_country_name#330, 200) 
+- *(2) HashAggregate(keys=[dest_country_name#330], functions=[partial_count(1) AS count#348L]) 
+- Exchange RoundRobinPartitioning(2) 
+- *(1) FileScan parquet [DEST_COUNTRY_NAME#330] Batched: true, DataFilters: [], Format: Parquet, Location: InMemoryFileIndex[dbfs:/FileStore/tables/bin/2010-summary.parquet], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<DEST_COUNTRY_NAME:string>
df.repartition(2).groupby('dest_country_name').count().collect()
Out[4]: [Row(dest_country_name='Russia', count=1), 
Row(dest_country_name='Anguilla', count=1), 
Row(dest_country_name='Paraguay', count=1), 
Row(dest_country_name='Senegal', count=1), 
Row(dest_country_name='Sweden', count=1), 
Row(dest_country_name='Kiribati', count=1), 
Row(dest_country_name='Guyana', count=1), 
Row(dest_country_name='Philippines', count=1), Row(dest_country_name='Singapore', count=1), 
Row(dest_country_name='Malaysia', count=1), 
Row(dest_country_name='Fiji', count=1), 
Row(dest_country_name='Turkey', count=1), 
Row(dest_country_name='Germany', count=1), Row(dest_country_name='Afghanistan', count=1), 
Row(dest_country_name='Jordan', count=1), 
Row(dest_country_name='Palau', count=1), 
Row(dest_country_name='France', count=1), 
Row(dest_country_name='Turks and Caicos Islands', count=1), Row(dest_country_name='Greece', count=1), 
Row(dest_country_name='Taiwan', count=1), 
Row(dest_country_name='British Virgin Islands', count=1), Row(dest_country_name='Dominica', count=1), 
Row(dest_country_name='Equatorial Guinea', count=1), Row(dest_country_name='Slovakia', count=1), 
Row(dest_country_name='Argentina', count=1), 
Row(dest_country_name='Angola', count=1), 
Row(dest_country_name='Belgium', count=1), 
Row(dest_country_name='Qatar', count=1), 
Row(dest_country_name='Ecuador', count=1), 
Row(dest_country_name='Finland', count=1), 
Row(dest_country_name='Ghana', count=1), 
Row(dest_country_name='Nicaragua', count=1), 
Row(dest_country_name='Peru', count=1), 
Row(dest_country_name='China', count=1), 
Row(dest_country_name='India', count=1), 
Row(dest_country_name='Curacao', count=1), 
Row(dest_country_name='United States', count=131), Row(dest_country_name='Malta', count=1), 
Row(dest_country_name='Kuwait', count=1), 
Row(dest_country_name='Marshall Islands', count=1), Row(dest_country_name='Chile', count=1), 
Row(dest_country_name='Martinique', count=1), 
Row(dest_country_name='Cayman Islands', count=1), Row(dest_country_name='Nigeria', count=1), 
Row(dest_country_name='Bolivia', count=1), 
Row(dest_country_name='Italy', count=1), 
Row(dest_country_name='Suriname', count=1), 
Row(dest_country_name='Netherlands Antilles', count=1), Row(dest_country_name='Norway', count=1), 
Row(dest_country_name='Spain', count=1), 
Row(dest_country_name='Cuba', count=1), 
Row(dest_country_name='Guadeloupe', count=1), 
Row(dest_country_name='Denmark', count=1), 
Row(dest_country_name='Barbados', count=1), 
Row(dest_country_name='Ireland', count=1), 
Row(dest_country_name='Thailand', count=1), 
Row(dest_country_name='Morocco', count=1), 
Row(dest_country_name='Panama', count=1), 
Row(dest_country_name='Cape Verde', count=1), 
Row(dest_country_name='Hong Kong', count=1), 
Row(dest_country_name='Ukraine', count=1), 
Row(dest_country_name='Venezuela', count=1), 
Row(dest_country_name='Israel', count=1), 
Row(dest_country_name='Saint Barthelemy', count=1), Row(dest_country_name='Iceland', count=1), 
Row(dest_country_name='Saint Kitts and Nevis', count=1), 
Row(dest_country_name='French Polynesia', count=1), 
Row(dest_country_name='South Korea', count=1), 
Row(dest_country_name='Cyprus', count=1), 
Row(dest_country_name='Bonaire, Sint Eustatius, and Saba', count=1), 
Row(dest_country_name='Uruguay', count=1), 
Row(dest_country_name='Mexico', count=1), 
Row(dest_country_name='Aruba', count=1), 
Row(dest_country_name='Georgia', count=1), 
Row(dest_country_name='Saint Vincent and the Grenadines', count=1), 
Row(dest_country_name='The Bahamas', count=1), 
Row(dest_country_name='Guatemala', count=1), 
Row(dest_country_name='Azerbaijan', count=1), 
Row(dest_country_name='Sint Maarten', count=1), 
Row(dest_country_name='Grenada', count=1), 
Row(dest_country_name='Federated States of Micronesia', count=1), 
Row(dest_country_name='Liberia', count=1), 
Row(dest_country_name='Honduras', count=1), 
Row(dest_country_name='Trinidad and Tobago', count=1), 
Row(dest_country_name='Saudi Arabia', count=1), 
Row(dest_country_name='Uganda', count=1), 
Row(dest_country_name='French Guiana', count=1), 
Row(dest_country_name='Switzerland', count=1), 
Row(dest_country_name='Ethiopia', count=1), 
Row(dest_country_name='Latvia', count=1), 
Row(dest_country_name='Jamaica', count=1), 
Row(dest_country_name='United Arab Emirates', count=1), 
Row(dest_country_name='Saint Lucia', count=1), 
Row(dest_country_name='Canada', count=1), 
Row(dest_country_name='Kyrgyzstan', count=1), 
Row(dest_country_name='Samoa', count=1), 
Row(dest_country_name='Czech Republic', count=1), 
Row(dest_country_name='Cook Islands', count=1), 
Row(dest_country_name='Brazil', count=1), 
Row(dest_country_name='Belize', count=1), 
Row(dest_country_name='Antigua and Barbuda', count=1), 
Row(dest_country_name='Dominican Republic', count=1), 
Row(dest_country_name='Japan', count=1), 
Row(dest_country_name='Luxembourg', count=1), 
Row(dest_country_name='New Zealand', count=1), 
Row(dest_country_name='Greenland', count=1), 
Row(dest_country_name='Haiti', count=1), 
Row(dest_country_name='Poland', count=1), 
Row(dest_country_name='Portugal', count=1), 
Row(dest_country_name='Australia', count=1), 
Row(dest_country_name='Bulgaria', count=1), 
Row(dest_country_name='Austria', count=1), 
Row(dest_country_name='Egypt', count=1), 
Row(dest_country_name='Costa Rica', count=1), 
Row(dest_country_name='Kazakhstan', count=1), 
Row(dest_country_name='El Salvador', count=1), 
Row(dest_country_name='South Africa', count=1), 
Row(dest_country_name='Bermuda', count=1), 
Row(dest_country_name='Bahrain', count=1), 
Row(dest_country_name='Colombia', count=1), 
Row(dest_country_name='Hungary', count=1), 
Row(dest_country_name='Pakistan', count=1), 
Row(dest_country_name='United Kingdom', count=1), 
Row(dest_country_name='Vietnam', count=1), 
Row(dest_country_name='Netherlands', count=1)]

스테이지 확인

  • stage 총 3개
    • stage1: 파일 스캔 + 파티션 재분배
      • task 1개
    • stage2: 파티션별 집계 수행
      • task 2개(파티션이 두 개 이므로)
    • stage3: 셔플 파티션
      • task 200개(기본값)
  • 태스크 상세 정보 확인
    • Summary Metrics: 다양한 메트릭에 관한 요약 통계
      • 값이 균일한지 봐야함 (균일하지 않다면 눈여겨 봐야함)
    • Aggregated Metrics by Executor: 익스큐터별 통계
      • 특정 익스큐터가 워크로드를 처리하는 데 어려움을 겪고 있는지 판단할 때 유용
    • Show Additional Metrics: 더 상세한 메트릭 정보

스파크 REST API

  • 사용자 정의 리포트 솔루션을 구축할 때 REST API 사용
  • 스파크 UI 외에도 REST API로 스파크의 상태와 메트릭 확인 가능
  • 대부분의 REST API는 스파크 UI와 동일한 정보를 제공하지만 SQL 관련 정보는 제공X

스파크 UI 히스토리 서버

  • 스파크 UI는 SparkContext가 실행되는 동안 사용 가능
  • 정상적으로 종료되거나 비정상적으로 종료된 애플리케이션의 정보를 확인하려면 스파크 히스토리 서버를 이용해야함
    • 그 전에 특정 경로에 이벤트 로그를 저장하도록 스파크 애플리케이션을 설정해야함

디버깅 및 스파크 응급 처치

  • 사용자가 자주 경험할 만한 몇몇 문제와 잠재적 대응법에 대해 알아볼 것임

1. 스파크 애플리케이션이 시작되지 않는 경우

  • 신규 클러스터 매니저나 환경을 사용하는 경우 자주 발생함

징후와 증상

  • 스파크 잡이 시작되지 않음
  • 스파크 UI가 드라이버 노드를 제외한 클러스터 노드 정보를 전혀 표시하지 않음
  • 스파크 UI가 잘못된 정보를 표시함

잠재적 대응법

  • 해당 유형의 문제는 주로 클러스터나 사용자 애플리케이션의 실행에 필요한 자원을 적절하게 설정하지 않았을 때 발생
  • 드라이버와 익스큐터 간 통신 문제인지 확인
    • ip를 제대로 입력했는지 확인
    • 설정한 포트로 클러스터 머신 간 통신 가능 여부 체크
  • 익스큐터 자원을 클러스터 매니저의 유휴 자원 이상으로 요청했는지 확인
    • 클러스터 매니저의 UI로 유휴 자원을 확인하고 spark-submit 명령에 할당할 메모리 설정

2. 스파크 애플리케이션 실행 전에 오류가 발생한 경우

  • 새로운 애플리케이션을 개발해 클러스터에서 실행할 때 발생할 수 있음

징후와 증상

  • 명령이 전혀 실행되지 않으며 오류 메시지가 출력됨
  • 스파크 UI에서 잡, 스테이지, 태스크의 정보를 확인할 수 없음

잠재적 대응법

  • 스파크 UI의 Environment 탭에서 애플리케이션 정보가 올바른지 확인한 다음 코드 검토
  • 환경 정보 확인
    • 클러스터의 드라이버, 워커, 그리고 사용하는 저장소 시스템 간의 네트워크 연결 상태 점검
    • 라이브러리나 클래스패스 확인
      • 잘못된 버전의 저장소 접속용 라이브러리를 사용할 수 있음
  • 코드 검토
    • 잘못된 입력 파일 경로나 필드명을 사용했는지
    • 스파크가 반환하는 오류를 살펴보자

3. 스파크 애플리케이션 실행 중에 오류가 발생한 경우

  • 이미 클러스터를 사용 중이거나 사용자 스파크 애플리케이션을 실행하는 중에 발생함
  • 특정 주기로 실행되는 예약 작업이나 일부 대화형 탐색에서 발생할 수도 있음

징후와 증상

  • 하나의 스파크 잡이 전체 클러스터에서 성공적으로 실행되지만 다음 잡은 실패
  • 여러 단계로 처리되는 쿼리의 특정 단계가 실패
  • 어제 정상 동작한 예약 작업이 오늘은 실패
  • 오류 메시지를 해석하기 어려움

잠재적 대응법

  • 데이터가 존재하는지 또는 데이터가 올바른 포맷인지 확인
    • 포맷이 변경되었거나 일부 처리 과정이 변경되어 사용자 애플리케이션이 의도하지 않은 결과를 초래했을 수 있음
    • 입력 데이터와 데이터 포맷을 한 번 더 확인
      • 코드를 조금씩 줄이면서 문제의 원인을 찾자
  • 만약 쿼리 실행 즉시 오류가 발생했다면(태스크 실행 전) 실행 계획을 만드는 단계에서 발생한 분석 오류일 가능성이 높음
    • 즉, 잘못된 컬럼명, 존재하지 않는 뷰나 테이블은 아닌지 확인해야함
  • 스택 트레이스를 분석해서 단서를 찾자
  • 잡의 태스크가 잠시 실행되다가 비정상적으로 종료된 경우, 입력 데이터 자체의 문제일 수 있음
    • 스키마가 올바르게 지정되지 않았거나 특정 로우가 스키마 형태와 일치하지 않는 경우
  • 데이터를 처리하는 코드에서 오류 발생한 경우
    • 오류 발생 시 스파크 로그에 오류 내용이 출력됨
      • 또한 스파크 UI에서 태스크 상태가 'failed'로 표시됨
    • 위 로그 파일을 분석해서 오류 발생 시 어떤 작업이 진행 중이었는지 확인

4. 느리거나 뒤쳐진 태스크(낙오자라 부름)

  • 태스크가 느리거나 뒤처지는 현상은 애플리케이션을 최적화할 때 매우 흔히 발생
  • 머신 간의 작업이 균등하게 분배되지 않았거나 특정 머신이 다른 머신에 비해 처리 속도가 느린 경우에도 발생
  • 원인은 다양하지만 주로 DataFrame이나 RDD 파티션에 데이터가 균등하게 분할되지 않은 경우 주로 발생
    • 이 경우, 일부 익스큐터가 다른 익스큐터에 비해 훨씬 더 많은 양의 데이터를 처리함
      • ex) groupby 수행 시 특정 키가 다른 키에 비해 더 많은 양의 데이터를 가진 경우

징후와 증상

  • 스파크 스테이지에서 대부분의 태스크가 정상적으로 실행되어 소수의 태스크만 남음 근데 남은 태스크가 오래 실행됨
  • 스파크 UI에서 위 증상과 같은 느린 태스크를 확인할 수 있고, 동일한 데이터셋을 다룰 때 항상 발생함
  • 스파크 애플리케이션을 실행하는 머신 수를 늘려도 상황이 개선되지 않고 여전히 특정 태스크가 다른 태스크에 비해 훨씬 오래 실행
  • 스파크 메트릭을 보면 특정 익스큐터가 훨씬 많은 데이터를 읽거나 쓰고 있음을 알 수 있음

잠재적 대응법

  • 파티션별 데이터양을 줄이기 위해 파티션 수를 증가
  • 다른 컬럼을 조합해 파티션 재분배
    • 데이터 치우침이 심한 ID 컬럼을 파티셔닝
  • 컬럼에서 많은 값이 null이면 null값을 먼저 필터링
  • 가능하면 익스큐터 메모리 증가
  • 익스큐터에 문제가 있는지 모니터링하고 있다면 해당 머신이 다른 잡에서도 동일한 문제가 발생하는지 확인
  • 클러스터에 비정상적인 익스큐터나 머신이 있는지 확인
    • ex) 특정 머신의 디스크가 거의 가득 찼을 수 있음
  • UDF 구현 시 객체 할당이나 로직에 쓸모 없는 부분이 있는지 확인하고 가능하면 DataFrame 코드로 변환
  • UDF나 UDAF(사용자 정의 집계 함수)가 적당한 데이터를 사용해 실행되는지 확인
    • 집계 연산은 공통키와 관련된 많은 데이터를 메모리에 적재하므로 집계 연산을 수행하는 익스큐터는 다른 익스큐터에 비해 훨씬 많은 작업 수행
  • Dataset을 사용한다면 스파크 UI의 가비지 컬렉션 메트릭이 느린 태스크와 관련 있는지 확인

5. 느린 집계 속도

  • 위 4. 느리거나 뒤처진 태스크에서 설명한 문제점 먼저 검토

징후와 증상

  • groupBy 호출 시 느린 태스크가 발생
  • 집계 처리 이후의 잡도 느림

잠재적 대응법

  • 집계 연산 전에 파티션 수를 증가시키면 태스크별로 처리할 키 수를 줄일 수 있음

  • 익스큐터의 메모리를 증가시킨 경우 데이터가 많은 키를 처리하는 익스큐터는 여전히 느릴 수 있음

    • 하지만 데이터를 디스크에 저장하는 빈도를 줄일 수 있어 이전보다 빠르게 처리 가능
  • 집계 처리가 끝나고 이어서 실행되는 태스크가 느리면 집계 처리된 데이터셋에 불균형 현상이 남아 있음을 의미함

    • 이 경우 파티션을 임의로 재분배할 수 있도록 repartition 명령을 추가
  • 모든 필터와 select 구문이 집계 연산보다 먼저 처리된다면 필요한 데이터만 이용해서 집계 연산 수행 가능

    • 근데 구조적 API를 사용하는 경우 쿼리 옵티마이저가 이 과정을 자동으로 수행하긴함
  • null값을 나타내기 위해 " " 또는 "EMPTY" 같은 값을 대체 값으로 사용하는건 아닌지 확인함

    • 스파크는 잡 실행 전 null 값을 건너뛰기 위한 최적화를 수행함 그러나 null이 아니라면 건너뛰지 않음
  • collect_list, collect_set 등은 일치하는 모든 객체를 드라이버에 전송하므로 아주 느리게 동작하므로 성능이 중요하면 사용을 피하자

6. 느린 조인 속도

  • 조인과 집계는 모두 셔플을 유발하므로 동일한 증상과 대응법을 가짐

징후와 증상

  • 조인 스테이지의 처리 시간이 오래 걸림
    • 하나 이상의 태스크가 여기에 해당할 수 있음
  • 조인 전후의 스테이지는 정상적으로 동작함

잠재적 대응법

  • 많은 조인 연산은 다른 조인 타입으로 변경해 최적화(자동 또는 수동 방식)할 수 있음

  • 조인 순서를 변경하면서 잡의 처리 속도가 올라가는지 테스트함

    • 일부 조인은 많은 양의 데이터를 걸러낼 수 있으므로 이 작업을 먼저 시도해보자
  • 조인을 수행하기 전에 데이터셋을 분할하면 클러스터 노드 간 데이터 이동을 줄일 수 있음

    • 특히 동일한 데이터셋이 여러 조인 연산에서 사용된다면 더욱 유용함
    • 또한 다양한 사전조인 파티셔닝(조인되는 테이블을 하나로 결합해서 데이터를 분할하는 방식) 기법을 실험해보자
  • 데이터 치우침 현상은 느린 조인을 유발할 수 있음

    • 대응법은 앞에서 본 것 처럼 익스큐터의 자원 할당량을 늘리는 것이 있음
  • 모든 필터와 select 구문이 조인 연산보다 우선 처리되도록 한다.(구조적 API는 자동 최적화)

  • 조인 대상 테이블 중 하나가 작은 경우 강제로 브로드캐스트하거나(8장 참고) 스파크의 통계 수집 명령을 사용해 테이블 분석

7. 느린 읽기와 쓰기 속도

  • 느린 I/O는 진단이 어려울 수 있음
  • 특히 네트워크 기반의 파일 시스템을 사용하는 경우는 더 어려움

징후와 증상

  • 분산 파일 시스템이나 외부 시스템의 데이터를 읽는 속도가 느림
  • 네트워크 파일 시스템이나 blob 저장소에 데이터를 쓰는 속도가 느림

잠재적 대응법

  • 스파크의 투기적 실행(spark.speculation 속성을 true로)을 사용하면 느린 읽기와 쓰기 속도를 개선하는 데 도움이 될 수 있음

    • 이 기능은 첫 번째 태스크에서 발생한 문제가 일시적인지 확인하기 위해 동일한 연산을 수행하는 태스크를 추가로 실행함
    • 해당 기능은 일관성을 보장하는 파일 시스템과 함께 사용하는 것이 좋음
  • 스파크 클러스터와 저장소 시스템 간의 네트워크 대역폭이 충분하지 않을 수 있으므로 네트워크 성능에 문제가 없는지 확인함

  • 단일 클러스터에서 스파크 HDFS같은 분산 파일 시스템을 함께 구성하려면 클러스터의 노드마다 스파크와 분산 파일 시스템 모두 동일한 호스트명을 인식하는지 확인함

8. 드라이버 OutOfMemoryError 또는 응답 없음

  • 드라이버에 너무 많은 데이터를 전송해 메모리를 모두 소비한 경우에 자주 발생

징후와 증상

  • 스파크 애플리케이션이 응답하지 않거나 비정상적으로 종료됨
  • 드라이버 로그에 OutOfMemoryError 또는 가비지 컬렉션과 관련된 메시지가 출력됨
  • 명령이 장시간 실행되거나 실행되지 않음
  • 반응이 거의 없음
  • 드라이버 JVM의 메모리 사용량이 많음

잠재적 대응법(이 문제는 잠재적인 원인이 다양하므로 진단하기 쉽지 않음)

  • 사용자 코드에서 collect 메서드 같은 연산을 실행해 너무 큰 데이터셋을 드라이버에 전송하려고 시도했을 수 있음

  • 브로드캐스트하기에 너무 큰 데이터를 브로드캐스트 조인에 사용한 경우

    • 스파크의 최대 브로드캐스트 조인 설정을 이용해 브로드캐스트할 크기를 제어할 수 있음
  • 장시간 실행되는 애플리케이션은 드라이버에 많은 양의 객체를 생성해 해제하지 못할 수 있음

    • 자바의 jmap 도구로 힙 메모리의 히스토그램을 확인할 수 있음(근데 잠시 JVM이 중단됨)
  • 가능하면 더 많은 데이터를 다룰 수 있도록 드라이버의 가용 메모리를 증가시킴

  • JVM 메모리 부족 현상은 파이썬 같은 다른 언어를 함께 사용하는 경우에 발생 가능

    • 두 언어 간의 데이터 변환 과정에서 과도한 메모리를 사용하기 때문
    • 그래서 더 적은 양의 데이터를 전송하거나 드라이버 메모리에 모으지 말고 파일로 저장해볼 수 있음
  • SQL JDBC 서버와 노트북 환경을 이용해 다른 사용자와 SparkContext를 공유하는 상황이라면 여러 사용자가 동시에 대량의 데이터를 드라이버 메모리로 전송할 수 있는 명령을 막아둬야함

    • ex) 지나치게 큰 배열을 다루거나 대규모 데이터셋을 드라이버에 전송하는 명령 등

9. 익스큐터 OutOfMemoryError 또는 응답 없음

  • 문제의 근본 원인에 따라 다르겠지만, 어떤 경우는 스파크 애플리케이션이 이런 문제를 자동으로 복구할 수 있음

징후와 증상

  • 익스큐터 로그에 OutOfMemoryError 또는 가비지 컬렉션과 관련된 메시지가 출력되며 스파크 UI에서도 확인 가능
  • 익스큐터가 비정상적으로 종료되거나 응답하지 않음
  • 특정 노드의 느린 태스크가 복구되지 않음

잠재적 대응법

  • 익스큐터의 가용 메모리와 익스큐터 수를 증가시킴

  • 관련 파이썬 설정을 변경해 PySpark 워커의 크기를 증가시킴

  • 익스큐터 로그에 가비지 컬렉션 오류 메시지가 발생했는지 확인함

    • 실행 중인 태스크의 일부가 너무 많은 객체를 생성하고 있어 가비지 컬렉션이 발생할 수 있음
      • 특히 UDF 사용 시에 발생 확률이 높음
    • 데이터 파티션을 재분배하여 병렬성을 높이고 태스크별로 처리해야 하는 레코드 수를 줄일 수 있음
    • 그리고 모든 익스큐터에 동일한 양의 작업이 할당되었는지 확인함
  • null값을 정확하게 제어하기 위해 ' ' 또는 'EMPTY' 같은 값을 기본값으로 사용하는 것은 아닌지 확인함

  • 가능하면 UDF 사용을 줄이고 스파크의 구조적 API를 더 많이 사용함

  • 자바의 jmap 도구를 사용해 익스큐터 힙 메모리의 히스토그램을 보고 가장 많은 메모리를 사용하는 클래스를 확인

  • 키-값 저장소 같이 다른 워크로드를 처리하는 노드에 익스큐터가 위치한다면 스파크 잡을 다른 작업과 분리해야함

10. 의도하지 않은 null 값이 있는 결과 데이터

징후와 증상

  • 트랜스포메이션이 실행된 결과에 의도치 않은 null 값이 발생함
  • 잘 동작하던 운영 환경의 예약 작업이 더는 동작하지 않거나 정확한 결과를 생성하지 못함

잠재적 대응법

  • 비즈니스 로직을 변경하지 않았다면 데이터 포맷이 변경되었을 수 있음 (즉, 이전에 정상 동작했던 코드가 더는 동작하지 않는 상황)

  • 어큐뮬레이터를 사용해 레코드나 특정 데이터 타입의 수를 확인할 수 있음

    • 이 방식으로 레코드를 건너뛰는 오류도 분석 가능
    • ex) 특정 포맷의 데이터를 파싱하던 중 일부 데이터가 파싱되지 않았다면 이 방식이 유용할 수 있음
    • 어큐뮬레이터가 탑재된 UDF를 사용해서 정상과 비정상 레코드 수를 확인할 수 있음
  • 트랜스포메이션이 실제 유효한 쿼리 실행 계획을 생성하는지 확인함

11. 디스크 공간 없음 오류

징후와 증상

  • 'no space left on disk' 오류 메시지와 함께 잡이 실패함

잠재적 대응법

  • 더 많은 디스크 공간을 확보하면 됨

  • 제한된 용량의 저장소를 가진 클러스터를 사용하는 경우, 데이터 치우침 현상이 발생하면 일부 노드의 저장소 공간이 모두 소진될 수 있음

    • 이땐 파티션 재분배가 답
  • 몇 가지 저장소 설정을 실험

    • 설정 중 일부는 스파크에서 로그를 유지하는 기간을 지정함
  • 문제가 되는 머신의 오래된 로그 파일과 셔플 파일을 수동으로 제거

12. 직렬화 오류

징후와 증상

  • 직렬화 오류와 함께 잡이 실패

잠재적 대응법

  • 구조적 API를 사용하는 경우엔 직렬화 오류가 거의 나타나지 않음
  • UDF나 함수로 직렬화할 수 없는 코드 또는 데이터를 다루거나 이상한 데이터 타입을 다루는 경우에 발생 가능
    • 직렬화 대상 사용자 클래스를 실제로 등록해 직렬화 성공 여부를 확인함
  • 자바나 스칼라 클래스에서 UDF를 생성할 땐 UDF 내에서 인클로징 객체의 필드를 참조하면 안됨
profile
Data Engineer

0개의 댓글