본문 바로가기

Spark

Spark 실행 모델 - RDD부터 Task까지

해당 학습 문서는 Spark가 데이터를 어떻게 표현하고, 어떤 순서로 실행하는지를 다룹니다.

데이터의 표현 방식 - RDD, Dataframe, Dataset

Spark에서 데이터를 다루는 방식은 세가지가 있습니다.

RDD(Resilient Distributed Dataset)

Spark의 가장 기본적인 데이터 추상화입니다. 여러 서버에 분산된 데이터를 하나의 컬렉션처럼 다룰 수 있게 해주는 객체입니다. 이름 그대로 분산(Distributed)되어 있고, 장애가 발생해도 복구(Resilient)할 수 있습니다. 다만 RDD는 데이터의 구조(스키마)를 모릅니다. 단순히 "Java 객체들의 묶음"에 가깝기 때문에 Spark가 내부적으로 최적화하기 어렵습니다.

DataFrame

RDD의 한계를 극복하기 위해 만들어졌습니다. DataFrame은 RDD 위에 스키마(컬럼명, 타입)를 얹은 구조입니다. 관계형 데이터베이스의 테이블처럼 생겼고, SQL로도 조작할 수 있습니다. Spark가 구조를 알기 때문에 내부적으로 실행 계획을 최적화할 수 있어요. 현재 가장 많이 쓰이는 방식입니다.

Dataset

DataFrame에 타입 안정성(type safety)을 더한 것입니다. Java/Scala에서 주로 쓰이고, Python에서는 DataFrame과 동일하게 취급됩니다.

 

더 자세한 비교 분석은 아래 글을 참고하세요
https://www.databricks.com/blog/2016/07/14/a-tale-of-three-apache-spark-apis-rdds-dataframes-and-datasets.html

 

Spark는 왜 바로 실행되지 않을까 - Lazy Evaluation

Spark에서 데이터 변환 코드를 작성하면 즉시 실행되지 않습니다.

df = spark.read.csv("data.csv")
df2 = df.filter(df["age"] > 30)
df3 = df2.select("name", "age")

이 코드를 실행해도 아무 데이터도 읽히지 않습니다. Spark는 .filter(), .select() 같은 변환(Transformation)을 "나중에 이렇게 처리하겠다"는 계획으로만 기록해 둡니다.

실제로 실행되는 시점은 .show(), .count(), .write() 같은 액션(Action)이 호출 될 때입니다.

df3.show() # 이 시점에 비로소 실행

이것을 Lazy Evaluation(지연 실행)이라고 합니다. 왜 이렇게 설계했을까요?

모든 변환을 미리 받아둔 뒤 한 번에 최적화할 수 있기 때문입니다. 예를 들어 filter와 select가 따로 있으면 Spark는 "어차피 age > 30행만 필요하고 name, age 컬럼만 쓰니까 처음부터 그것만 읽자"고 계획을 줄일 수 있습니다. 즉시 실행했다면 이런 최적화가 불가능합니다.

 

실행 계획은 어떻게 생성될까 - DAG

Lazy Evaluation 덕분에 Spark는 액션이 호출되는 순간 지금까지 쌓인 변화들을 분석해서 DAG(Directed Acyclic Graph)를 만듭니다.

DAG는 쉽게 말해 "어떤 작업을 어떤 순서로 실행할지"를 나타내는 실행 계획 그래프입니다.

DAG 관련 자세한 글은 다음을 참고하세요: https://soecp.tistory.com/60

 

CSV 읽기 → filter → select → show

 

방향은 있고(앞에서 뒤로), 순환은 없습니다(이미 지나간 단계로 돌아가지 않음). Spark는 이 DAG를 보고 어디까지 묶어서 처리할 수 있는지, 어디서 끊어야 하는지를 판단합니다. 그 기준이 되는 것이 바로 다음에 나오는 Stage 입니다.

작업이 어떻게 나뉘는가 - Stage와 Task

Dag를 만든 Spark는 Job를 Stage로 나눕니다. Stage는 서버 간 데이터 이동(Shuffle) 없이 연속으로 처리할 수 있는 작업의 묶음입니다. 

Shuffle이 필요한 시점, 즉 서버 간에 데이터를 재분배해야 하는 시점에서 Stage가 끊깁니다. 예를 들어 groupBy().count() 같은 집계 연산은 같은 key를 가진 데이터가 같은 서버에 모여야 하기 때문에 Shuffle이 발생하고 Stage가 나뉩니다.

Stage 안에서는 데이터가 Partition 단위로 나뉘어 처리됩니다. Partition은 RDD나 DataFrame을 실제로 쪼개는 단위입니다. 그리고 각 Partition 하나를 처리하는 작업 단위가 Task입니다.

Task는 Spark의 가장 작은 실행 단위이며, 각각 독립적으로 서로 다른 서버(Executer)에서 병렬 처리됩니다.

정리하면:

흐름 전체를 한 번에 보면

코드 작성 (Transformation 누적)
    ↓
Action 호출
    ↓
DAG 생성 (전체 실행 계획)
    ↓
Stage 분리 (Shuffle 기준)
    ↓
Task 생성 (Partition당 1개)
    ↓
각 Executor에서 병렬 실행

이 흐름을 이해하면 Spark에서 성능 문제가 생겼을 때 어느 단계에서 병목이 발생하는지 추적할 수 있습니다.

'Spark' 카테고리의 다른 글

빅데이터, Hadoop 그리고 Spark  (0) 2026.05.13