← 학습 카테고리

Learn

Spark

38개 모듈 · 현재 23번째

Spark 모듈 23/38 spark-learn-23

RDD·DataFrame·Dataset와 데이터 처리

Apache Spark Data Engineering Interview Questions (blog, 50 Q&A) — (블로그, 발행처 미상) spark_ref_01.md Q11-20 (Data Structures & APIs)

이 모듈을 다 읽으면

  • RDD/DataFrame/Dataset 세 API 중 무엇이 왜 더 강한 최적화를 받는지 설명할 수 있다
  • narrow/wide 트랜스포메이션을 구분하고 셔플 발생 여부를 판단할 수 있다
  • repartition과 coalesce의 동작 차이를 실전 상황(출력 파일 수 줄이기 등)에 맞게 선택할 수 있다
  • accumulator와 broadcast variable의 용도 차이를 설명할 수 있다

RDD/DataFrame/Dataset의 최적화 수준 차이, narrow/wide 트랜스포메이션과 셔플의 관계, repartition/coalesce 선택 기준, accumulator/broadcast variable의 용도를 다룬다.

RDD vs DataFrame vs Dataset

RDD는 스키마가 없는 저수준 객체 컬렉션이라 Spark가 데이터 내부 구조를 알 수 없고, 그만큼 옵티마이저가 개입할 여지가 적다. DataFrame은 이름이 붙은 컬럼으로 구성된 분산 테이블로, Catalyst 옵티마이저와 Tungsten 실행 엔진의 최적화를 받기 때문에 동일한 로직이라도 RDD보다 대체로 빠르다. Dataset은 DataFrame에 컴파일 타임 타입 안정성을 더한 것으로 Scala/Java에서만 제공된다 — PySpark의 DataFrame은 사실상 Row 타입의 Dataset이며, 파이썬 자체가 컴파일 타임 타입 체크를 지원하지 않아 별도의 'PySpark 전용 Dataset' 개념은 없다.

핵심 포인트

  • DataFrame/Dataset은 Catalyst/Tungsten 최적화를 받지만 RDD는 받지 못한다.
  • Dataset의 컴파일 타임 타입 안정성은 Scala/Java 전용이며 PySpark에는 해당 개념이 없다.

DataFrame 생성 방법과 스키마

파일 기반(spark.read.csv/json/parquet 등), 기존 RDD 변환(rdd.toDF()), 프로그래밍 방식(spark.createDataFrame(data, schema)) 세 가지가 대표적인 DataFrame 생성 경로다.

스키마는 DataFrame의 컬럼명과 타입을 정의하는 메타데이터다. Spark가 데이터 일부를 읽어 스키마를 '추론'할 수도 있지만, 이 과정은 느리고(전체 또는 상당 부분을 미리 스캔해야 함) 타입을 잘못 추정할 위험(예: 정수 컬럼을 문자열로 인식)이 있다. StructType/StructField로 스키마를 명시하면 이 스캔 비용을 없애고 데이터 무결성도 보장할 수 있다.

핵심 포인트

  • 파일 읽기, RDD 변환, 프로그래밍적 생성 세 가지 경로로 DataFrame을 만든다.
  • 스키마 추론은 느리고 타입 오판 위험이 있어 명시적 스키마 정의가 권장된다.

map vs flatMap

map()은 입력 원소 하나당 정확히 하나의 출력 원소를 만든다. flatMap()은 입력 원소 하나당 0개, 1개, 또는 여러 개의 출력 원소를 만들 수 있으며 그 결과를 평탄화(flatten)한다. 대표적인 예가 워드카운트로, 한 줄(원소 1개)을 여러 단어(원소 여러 개)로 쪼갤 때 flatMap을 쓴다.

핵심 포인트

  • map은 1:1 매핑, flatMap은 1:N(0 포함) 매핑 후 평탄화한다.

narrow/wide 트랜스포메이션과 셔플

narrow 트랜스포메이션(filter, map 등)은 부모 파티션 하나가 자식 파티션 하나에만 대응되므로 네트워크 이동 없이 같은 파티션 내에서 처리된다. wide 트랜스포메이션(groupByKey, reduceByKey, join 등)은 자식 파티션 하나를 계산하기 위해 여러 부모 파티션의 데이터가 필요해 셔플이 발생한다.

셔플은 데이터 직렬화, 디스크 쓰기, 네트워크 전송, 역직렬화가 모두 수반되는 Spark에서 가장 비용이 큰 연산이므로, 셔플 최소화가 성능 튜닝의 핵심 축이 된다.

핵심 포인트

  • narrow는 파티션 내에서 처리되어 셔플이 없고, wide는 여러 파티션 데이터를 모아야 해서 셔플이 발생한다.
  • 셔플은 직렬화·디스크 I/O·네트워크 전송을 모두 수반해 가장 비용이 큰 연산이다.

결측치 처리

DataFrame.na.drop()으로 null이 있는 행을 제거하고, na.fill(value)로 특정 값으로 대체하며, na.replace()로 특정 값을 치환할 수 있다. 특정 컬럼만 대상으로 지정해 다른 컬럼의 유효한 데이터를 불필요하게 잃지 않도록 범위를 좁힐 수도 있다.

핵심 포인트

  • na.drop/fill/replace로 결측치를 처리하며 컬럼 단위로 적용 범위를 좁힐 수 있다.

repartition vs coalesce

repartition은 지정한 파티션 수로 데이터를 재분배하며 항상 전체 셔플(full shuffle)을 수행한다 — 파티션 수를 늘릴 때도 줄일 때도 사용 가능하다. coalesce는 파티션 수를 줄이는 데 특화되어 있으며, 같은 Executor 안의 인접 파티션들을 셔플 없이 로컬로 병합하기 때문에 저장 전 출력 파일 수를 줄이는 용도로는 repartition보다 훨씬 효율적이다.

핵심 포인트

  • repartition은 증가/감소 모두 가능하지만 항상 전체 셔플을 수행한다.
  • coalesce는 감소 전용이며 로컬 병합으로 셔플을 피해 더 효율적이다.

Accumulator와 Broadcast Variable

Accumulator는 Executor에서 Driver로 정보를 집계해 보내는 변수로, Executor 입장에서는 '쓰기 전용'이다 — 값을 더할 수는 있지만 읽을 수는 없고, 최종 누적값은 Driver만 읽을 수 있다. 손상된 레코드 수를 세는 카운터 같은 용도로 흔히 쓰인다.

Broadcast Variable은 읽기 전용 데이터를 각 머신에 캐시해두고 태스크마다 복사본을 재전송하지 않도록 하는 기능이다. 큰 팩트 테이블과 작은 조회 테이블(lookup table)을 조인할 때 작은 테이블을 브로드캐스트하면 원래 발생했을 wide 트랜스포메이션 셔플을 막을 수 있다.

핵심 포인트

  • Accumulator는 Executor→Driver 단방향 집계용이며 Executor에서는 값을 읽을 수 없다.
  • Broadcast Variable은 읽기 전용 데이터를 각 노드에 캐시해 반복 전송과 조인 셔플을 줄인다.