Spark - RDD vs Dataframes vs Datasets
RDD와 Dataframes, Datasets를 비교하는 글이 있어서 보려고 한다.
중간중간 의역 및 생략이 있을 수 있다. 때문에 보다 자세한 글은 아래 원문을 참조
우리는 언제, 왜 RDD, Dataframes, Datasets를 사용해야 할까?
Resilient Distributed Dataset (RDD)
RDD는 Spark 등장 이래로 주로 사용된 API 이다. Spark Core에서는 RDD는 변경할 수 없는(Immutable) 데이터의 집합으로 클러스터 내의 여러 노드에 분산되어 있다.
(Transformation, Action과 같은 low-level API를 지원한다.)
그럼 언제 RDD를 사용해야 할까? 일반적으로 사용되는 경우를 살펴보자.
- low-level API인 Transformation, Action을 사용할 때
- 데이터가 미디어와 같이 비구조화 형태로 되어 있을 때
- 데이터를 함수형 프로그래밍으로 조작하고 싶을 때
- 데이터를 처리할 때, 칼럼 형식과 같은 스키마를 굳이 따지고 싶지 않을 때
- 최적화를 굳이 신경쓰지 않을 때
그럼 RDD는 Spark 2.0에서는 어떻게 되었을까? deprecated 되었을까?
이후에도 알아 보겠지만 Dataframe, Dataset 와 함께 존재한다. (해당 타입이 RDD의 상위 개념에 속하기 때문)
Dataframes
RDD와 같이 Dataframe도 변경 할 수 없는 데이터 집합이다. 다만, 데이터가 RDB의 관계형 테이블처럼 칼럼이 존재한다. 따라서 대용량 데이터를 좀 더 쉽게 처리 할 수 있다.
또한 구조화 되어 있기 때문에 여러 데이터 엔지니어들이 쉽게 Spark에 접근 할 수 있다. (RDB와 같은 형태를 많은 사람들에게 익숙하기 때문에)
그러나 Spark 2.0에서 Dataframe API는 Datasets API와 통합되었다.
Datasets
Spark 2.0이 대두되면서, Dataset은 2가지 특징(strongly-typed API, uptyped API)을 가지게 되었다. 개념적으로 Dataframe은 제네릭(generic) 객체인 Dataset[Row]의 집합이며 Alias이다. (DataFrame = Dataset[Row])
Dataset API의 장점에 대해 알아보자.
Spark 2.0 이후로 Dataframe과 Dataset이 통합되면서 여러 가지 장점을 갖는다.
1. Static-typing & runtime type-safety
예를 들어 Spark SQL을 사용하면 실행 할 때까지 문법 에러를 알 수 없다. 반면에 Dataframe과 Dataset은 컴파일 시점에 에러를 잡을 수 있다. (물론 실행 할 때까지 존재하지도 않는 칼럼은 찾을 수 없다.) 또한 Dataset API는 람다 함수 및 JVM 타입 객체로 표현되기 때문에, 컴파일 시점에 타입을 검사할 수 있다.
2. High-level abstraction and custom view into structured and semi-structured data
예를 들어 JSON으로 표현된 거대한 IoT 디바이스 데이터가 있다고 가정해보자. JSON은 반구조화 형태이지만 Dataset[DeviceIoTData]와 같은 strongly typed의 집합으로 구성된다.
{
"device_id": 198164, "device_name": "sensor-pad-198164owomcJZ",
"ip": "80.55.20.25", "cca2": "PL", "cca3": "POL",
"cn": "Poland", "latitude": 53.080000, "longitude": 18.620000,
"scale": "Celsius", "temp": 21, "humidity": 65,
"battery_level": 8, "c02_level": 1408, "lcd": "red", "timestamp": 1458081226051
}
case class DeviceIoTData (battery_level: Long, c02_level: Long, cca2: String, cca3: String, cn: String,
device_id: Long, device_name: String, humidity: Long, ip: String, latitude: Double, lcd: String,
longitude: Double, scale:String, temp: Long, timestamp: Long)
// read the json file and create the dataset from the
// case class DeviceIoTData
// ds is now a collection of JVM Scala objects DeviceIoTData
val ds = spark.read.json(“/databricks-public-datasets/data/iot/iot_devices.json”).as[DeviceIoTData]
위의 코드를 보면,
1) JSON 데이터
2) 추출하기 위한 클래스
3) JSON 데이터를 읽어서 클래스 형태로 구성
이 중, 3)에서 데이터를 Dataset으로 구성하면서 타입을 DevcieIoTData 으로 명시한다.
대부분의 사람들은 구조화된 데이터를 처리하는데 익숙하다. 이렇듯 타입이 명시된 데이터 집합인 Dataset으로 컴파일 시점에 데이터 뷰를 볼 수 있다. 게다가 Dataset[T] 형태는High-level 메소드로 쉽게 작업 할 수 있다.
3. Ease-of-use of APIs with structure
구조적 형태는 Spark에서 데이터를 다루는데 한계가 있을 수 도 있다. 그러나 Dataset은 쉽게 이해 할 수 있고, 데이터를 다양하게 표현 할 수 있다.
예시로 agg, select, sum, avg, map, filter, groupby를 사용하여 데이터를 다루는 것은 RDD의 각 row 필드를 사용하는 것보다 쉽다.
// Use filter(), map(), groupBy() country, and compute avg()
// for temperatures and humidity. This operation results in
// another immutable Dataset. The query is simpler to read,
// and expressive
val dsAvgTmp = ds.filter(d => {d.temp > 25}).map(d => (d.temp, d.humidity, d.cca3)).groupBy($"_3").avg()
//display the resulting dataset
display(dsAvgTmp)
4. Performance & Optimization
Dataframe와 Dataset은 Spark SQL 엔진 위에 탑재되어 있다. (Spark Stack 참조)
즉, 최적화된 논리적 및 물리적 쿼리 계획을 생성 할 수 있는 Catalyst를 사용 할 수 있다. 그러므로 R, Java, Scala, Python과 같은 언어도 같은 장점을 취할 수 있다.
다음으로 컴파일러가 데이터 타입 JVM 객체를 알고 있으므로, Encoders를 사용하여 명시화된 JVM 객체를 Tungsten 내부 메모리에 매핑한다. 때문에 효율적으로 JVM 객체를 직별화/반직렬화 시킬 수 있고 바이크도 또한 생성 할 수 있다.
그렇다면 언제 Dataframe, Dataset를 사용해야 할까?
- 도메인 API, 추상화 등등이 필요할 때
- filters, maps, aggergagion, 반구조화 데이터에서 람다 함수 등등이 필요할 때
- Catalyst 최적화 및 Tungsten 코드를 사용하고 싶을 때
- API를 단순화하여 사용하고 싶을때
- R, Python 사용자
(추가로 Dataframe, Dataset에서 RDD로 간단히 변환이 가능하다.)
Bringing It All Together
요약하자면 RDD, Dataframe 및 Dataset의 선택은 명확하다. (RDD는 저수준 기능 및 제어, Dataframe과 Dataset는 고수준 및 도메인 작접 그리고 최적화)