IT/Spark

Spark - RDD Dependency

물통꿀꿀이 2020. 2. 5. 23:29

RDD의 내부 구성을 잠깐 생각해보자. RDD는 Spark에서 추상화 데이터 모델이다. 때문에 분산 처리된 데이터의 집합이기도 하다.

그러므로 위와 같이 표현될 수 있다. 즉, RDD는 각 노드의 파티션에 분산되어 있다.

(예를 들어, groupByKey 메소드의 결과는 Shuffling된 Pair RDD가 될 것이며, Pair RDD는 각 노드의 파티션에 골고루 분산되어 있다.)


여기서 Lineage에서 처럼 A -> B의 관계를 RDD -> RDD로 위의 그림을 좀 더 확장하면, 아래와 같다.

사전에 작업된 RDD를 부모 RDD, 이를 바탕으로 작업된 RDD를 자식 RDD로 정의하며 자식 RDD를 만들게한 map 메소드를 Dependency라 한다.

개념적으로 이제 짚고 넘어가는 것이긴한데, 그 동안 사용했던 RDD Transformation 관련 메소드들은 모두 Dependency 개념을 탑재한 것이다.

그리고 이 개념이 내부적으로 Spark의 성능을 결정하는 Shuffling을 만든다.


좀더 Dependency에 대해 알아보면, 세부적으로 Narrow, Wide Dependency로 나뉜다.

- Narrow Dependency :  부모 RDD 파티션이 자식 RDD 파티션과 최대 1:1로 대응된다. (Shuffling이 거의 발생하지 않으며, 빠르다.)

Narrow Dependency


- Wide Dependency : 부모 RDD 파티션이 자식 RDD 파티션과 N:1로 대응된다. (Shuffling이 많은 데이터에 발생하며, 느리다.)

Wide Dependency


대략 그림만 봐도 이해 할 수 있을 것이다. Wide Dependency는 자식 RDD의 여러 파티션의 데이터와 대응시켜야 하므로 Shuffling이 많이 발생 할 수 밖에 없다.

Spark의 메소드에 따라 Dependency가 다르게 보여지는데, 아래 표를 확인해보자.

Narrow Dependency 

Wide Dependency 

map 

cogroup 

mapValues 

groupWith 

flatMap 

join 

filter 

leftOuterJoin 

 

groupByKey

 

reduceByKey 

 

distinct 

(위에 표기된 것 외에도 다양한 메소드들이 존재한다.)


이외에도 Spark 스케줄러는 RDD Dependency를 통해 RDD를 추적한다. (즉, 이전 작업 RDD가 무엇인지)

이를 위해 내부적으로는 Dependency 객체가 존재하는데, dependencies 메소드를 통해 해당 객체를 알 수 있다.

val dependencyObject = sc
.parallelize(List(1,2,3,4,5))
.map(a => (a, 1))
.groupByKey()
.dependencies

그런데 Dependency의 종료에 따라 결과가 조금 다를 수 있다.

- Narrow Dependency 일 때는, OneToOneDependency, PruneDependency, RangeDependency

- Wide Dependency 일 때는, ShuffleDependency


간단히 확인해보면, 아래와 같은 결과가 출력된다.

(4) ShuffledRDD[17] at groupByKey at <console>:30 []

 +-(4) MapPartitionsRDD[15] at map at <console>:27 []

    |  ParallelCollectionRDD[14] at parallelize at <console>:25 [] 

(위에서 언급한 것처럼 groupByKey는 Wide Dependency 바탕의 메소드로 ShuffledRDD이다.)


위의 출력된 결과는 Logical Execution Plan으로 Spark에서는 Lineage로 불린다. 

사실 새로운 것도 아니고, 단순히 Dependency 따라 RDD를 나열한 것 뿐이다. 때문에 Lineage를 보통 DAG(Directed Acyclic Graph)로 표현한다. (아래 그림 참조)

이렇게 Spark가 RDD를 Lineage의 형태로 구성하는 장점 중 하나는 fault tolerance 특징을 가질 수 있기 때문이다.

즉, Spark는 작업 중 장애가 발생했을 때, 장애가 발생한 부분 부터 다시 시작 할 수 있다.


지금까지 RDD Dependency를 알아보았다.

생각해보면 RDD와 RDD의 의존성을 표현하지만 서로 간의 관계를 통해 Spark의 성능 향상, 장애 대응 또한 접목 시킬 수 있을 정도로 중요한 개념이다.