스파크에서의 변환과 액션에 대해 정리한 글입니다.
1. 들어가며
스파크의 동작 원리를 이해하기 위해 유튜브의 PySpark - Zero to Hero 시리즈와 여러 자료를 보면서 내용을 정리하고 있습니다. 이번 글에서는 스파크의 변환(Transformation)과 액션(Action)에 대해 다뤄보겠습니다. 내용 중 부정확하거나 애매한 부분이 있다면, 편하게 알려주세요!
2. 스파크 데이터 처리의 기초
2.1. 변환과 불변성
스파크의 데이터프레임은 내부적으로 RDD(Resilient Distributed Dataset) 위에서 동작하는데, RDD는 한 번 생성되면 수정할 수 없는(immutable) 데이터 구조입니다. 그렇다면, 스파크에서는 어떻게 데이터를 가공할 수 있을까요? 스파크는 기존 데이터를 수정하지 않고, 새로운 데이터프레임을 만들어 반환하는 방식으로 작동합니다.
2.2. 스파크에서 불변성이 중요한 이유
스파크는 데이터를 여러 노드에 나누어 동시에 처리합니다. 예를 들어, 수백만 건의 데이터를 10개 노드에 나누어 작업시킨다고 해볼게요. 만약 데이터가 변경 가능하다면, 각 노드는 다른 노드와 독립적으로 실행되지만, 만약 모든 노드가 같은 메모리의 데이터를 동시에 수정한다면 값이 꼬이는 경쟁 상태(race condition)가 발생합니다.
스파크에서는 한번 만들어진 데이터를 어떤 노드에서도 수정할 수 없고, 각 노드는 자신이 맡은 데이터를 "읽어서 새로운 데이터 조각을 만든 뒤 반환"하는 방식으로만 동작합니다. 원본은 누구도 건드리지 않기 때문에, 여러 노드가 동시에 일을 해도 충돌이 생기지 않습니다.
2.3. 지연 연산 (Lazy Evaluation)
스파크의 변환은 실제 연산을 즉시 실행하지 않고, "어떤 연산을 어떤 순서로 수행할지"를 실행 계획(DAG)으로 쌓아둡니다. select, where, groupBy 같은 연산들은 대표적인 변환으로, 여러 번 호출되더라도, 실제 데이터 처리가 일어나지 않고, "어떤 순서로 데이터를 가공하겠다"는 계획만 누적됩니다.
이렇게 쌓인 변환 단계는 스파크 내부적으로 DAG 형태로 기록해두고, 실제 실행은 count(), show() 같은 액션(action)이 호출되는 순간, 스파크는 지금까지 쌓인 변환 단계를 한꺼번에 분석하고, Catalyst Optimizer를 통해 연산 순서를 최적화한 뒤, 가장 효율적인 방식으로 Stage와 Task를 나누어 실제 실행을 시작합니다.
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
spark = SparkSession.builder.appName("Playground").getOrCreate()
# 샘플 데이터
data = [
(1, "원동선", "아메리카노", 4000),
(2, "성이헌", "아메리카노", 4000),
(3, "하정서", "라떼", 4500),
(4, "정민음", "율무차", 5000),
(5, "최준범", "녹차", 6000)
]
# DataFrame 생성
df = spark.createDataFrame(data, ["id", "name", "menu", "price"])
# 변환: 비싼 음료 시킨 사람 찾기 👀 (아직 실행 x)
df_filtered = df.filter(col("price") > 4500)
# 액션: show()가 호출되는 순간 실제 실행
df_filtered.show()
2.4. 변환의 두 가지 유형
스파크에서의 변환은 데이터가 다른 곳으로 이동하느냐 아니냐에 따라 두 가지로 나눌 수 있습니다. 이 차이는 단순해보이지만, 실제로는 처리 속도와 효율성에 큰 영향을 주는 요소입니다.
1) 좁은 변환 (Narrow Transformation)
좁은 변환은 각자 맡은 데이터만 처리하는 방식입니다. 예를 들어, 데이터를 10등분해서 10명이 나눠 맡았다고 하면, 데이터를 서로 주고받지 않고, 자기 데이터만 처리해서 데이터 이동(shuffle)이 발생하지 않기 때문에 훨씬 빠르게 실행됩니다. 대표적인 예로 filter()나 map() 같은 연산이 있습니다. 필터링이나 단순 변환처럼, 각자의 데이터 안에서 처리할 수 있는 연산들입니다.

2) 넓은 변환 (Wide Transformation)
서로 다른 데이터 조각(partition) 간에 데이터를 주고받는 변환입니다. 이번에는 각자의 결과를 합치거나, 새로운 기준으로 묶어야 하는 상황이라면, 이 과정에서 일부 데이터는 다른 작업자에게 이동해야 합니다. 즉, 파티션 간 데이터 재분배가 일어나는데 데이터 이동은 네트워크를 거치기 때문에 시간이 더 걸리고, 좁은 변환보다 비용이 상대적으로 큽니다. groupBy(), join() 같은 연산이 여기에 해당합니다.

3. 나가며
이번 글에서는 스파크의 변환과 액션에 대해 알아봤습니다. 다음 글에서는 데이터프레임과 실행 계획에 대해 살펴보겠습니다.
참고자료
'Data > Spark' 카테고리의 다른 글
| [Spark] 데이터프레임과 실행 계획 (0) | 2025.11.03 |
|---|---|
| [Spark] 스파크란 무엇인가? (0) | 2025.10.10 |
