특정한 타입(T) 을 가진 JVM 객체들의 분산 컬렉션 (Distributed, Typed Collection).
타입 을 가진 RDD + DataFrame의 최적화 기능을 결합한 API
case class Person(name: String, age: Int)
val ds: Dataset[Person]
Dataset[Person] = Person 객체들의 분산된 컬렉션
Dataset은 컴파일 타임 타입 체크가 된다.
ds.filter(_.age > 20)
여기서 _.age는 Person.age 타입이 Int라는 걸 컴파일러가 100% 알고 있다.
Dataset은 JVM 타입을 Spark SQL 엔진이 처리할 수 있도록
Encoder라는 도구를 사용한다.
case class Person(name: String, age: Int)
Spark는 이 타입에 대해:
직렬화/역직렬화
스키마 생성
컬럼 변환
등을 자동으로 수행한다.
=> 복잡한 타입일수록 직렬화 , 역직렬화 오버헤드 발생
=> 주로 Scala , Java 등 정적 타입 언어에서만 사용 가능
⭐ Dataset을 사용하지 말아야 하는 경우
❌ 1) Python 사용자
Dataset 없음 → DataFrame만 사용
❌ 2) 머신러닝 MLlib
주로 DataFrame 기반 API 사용
Dataset은 불필요하게 복잡해짐
❌ 3) 단순 ETL / SQL 중심 처리
DataFrame이 더 간결하고 더 빠른 경우 많음
이름이 붙은 컬럼(Column)들로 구성된 분산 데이터셋.
쉽게 말해, “분산 환경에서 동작하는 테이블(table)”이다.

우리가 아는 그 데이터프레임 맞다
(컬럼 이름 + 타입 정보)
name: string
age: integer
city: string
RDD는 타입 정보가 없어서 Spark가 최적화를 거의 할 수 없고 런타임 중 오류에 취약하다.
반면 DataFrame은 스키마 기반이므로:
Catalyst 옵티마이저가 자동으로 연산 재작성
코드 생성(Whole-Stage Codegen)
컬럼 단위 연산 을 메모리 최적화(Tungsten)
이 가능해져 성능이 10~100배 이상 빠르다
Spark API로는 DataFrame이라고 부르지만, DataFrame은 사실 Dataset의 Untyped Version이다.
| API | 내부 표현 | 타입 체크 | 사용 언어 |
|---|---|---|---|
| DataFrame | Dataset[Row] | Untyped | Scala, Java, Python |
| Dataset[T] | Dataset[T] | Typed | Scala, Java |
| RDD[T] | 분산 객체 | Typed (약함) | 모든 언어 |
DataFrame == Dataset[Row]
Dataset: 타입 안전한 데이터 구조(Scala/Java)
Row: “열 데이터의 묶음”을 표현하는 객체 (generic, dynamic)
그리고 Row는 Python 같은 dynamic language에서도 유연하게 쓰도록 만들어진 제네릭 구조다.
Row는 스키마를 따르지만:
내부 필드의 정확한 데이터 타입을 컴파일 타임에는 알 수 없다 -> 약한 타입 안정성
대신 런타임에서 스키마를 보고 타입을 읽어온다.
행(Row)의 형태는 스키마를 따르지만, Spark 컴파일러는 Row가 어떤 타입인지 사전에 알지 못한다.
이게 바로 DataFrame이 데이터 처리를 유연하게 해주는 이유다.