데이터 엔지니어링

Apache Spark 기본 개념 및 아키텍처 소개

haong_ 2025. 2. 22. 15:32

Apache Spark는 대규모 데이터를 빠르게 처리할 수 있는 분산 데이터 처리 프레임워크이다. 대량의 데이터를 빠르게 분석하고, 머신러닝이나 실시간 스트리밍 처리에도 많이 사용된다. 이 글에서는 Spark의 기본 개념과 아키텍처, 핵심 구성요소를 정리해보겠다.

Apache Spark란?

Apache Spark는 대규모 데이터를 분산 환경에서 빠르게 처리할 수 있도록 설계된 오픈소스 클러스터 컴퓨팅 프레임워크

  • 메모리 기반의 빠른 연산: 기존 하둡이 디스크 I/O 비용이 많이 들어 in memory 기반으로 데이터 처리하는 spark 탄생 
  • 다양한 언어 지원(Python, Scala, Java, R) 
  • 배치 및 실시간 처리 지원 
  • HDFS, S3, Kafka, NoSQL등 다양한 데이터 소스 지원
  •  

Apache Spark 기본 아키텍처

Spark는 크게 Driver, Cluster Manager, Executor로 구성된다. 

Driver (드라이버)

사용자가 Spark 애플리케이션을 실행하는 엔트리 포인트.

  • SparkContext 생성: 클러스터와의 통신을 위한 진입점 역할.
  • 실행 계획 생성: Job을 여러 Stage로 분할하고, 각 Stage를 다시 여러 Task로 나눠 실행할 수 있도록 계획을 세움.
  • 클러스터와의 통신: Cluster Manager 및 Executor와 상호작용하여 작업을 분배하고 모니터링.

Cluster Manager (클러스터 매니저)

클러스터 리소스를 관리하며, Driver와 Executor 간 리소스 할당 및 작업 스케줄링 담당.

  • 지원되는 클러스터 매니저: 
    • Standalone: Spark 자체 내장 클러스터 매니저
    • YARN: Hadoop의 리소스 매니저
    • Kubernetes: 컨테이너 오케스트레이션 시스템 (Spark on K8s)

Executor (익스큐터)

실질적으로 작업(Task)을 수행하는 프로세스, 각 Executor는 할당받은 CPU 및 메모리 리소스를 활용해 연산을 수행.

  • Driver가 보낸 Stage를 여러 개의 Task로 분할하여 실행.
  • 연산이 끝나면 결과를 Driver에게 반환하거나 HDFS, S3, DB 등에 저장.

driver가 실행계획을 만들고, cluster manager가 executor에게 작업을 분배하는 구조

Spark 내부 구조 (Job, Stage, Task)

Spark의 실행 단위는 Job → Stage → Task 계층 구조로 되어 있다. 

Job

  • 사용자가 Action(show(), count(), collect())을 실행하면 Job이 생성됨.
  • 하나의 Job은 여러 개의 Stage로 나뉘어 실행됨.

Stage

  • 하나의 Job 내에서 실행 계획에 따라 분할된 단계
  • Narrow Transformation : map(), filter() 같이 각 데이터 파티션이 독립적으로 처리되는경우, 같은 Stage에서 실행됨
  • Wide Transformation : groupBy(), join() 같이 여러 파티션 간의 데이터 이동(Shuffle)이 필요한 경우 새로운 Stage로 분리됨 

Task

  • Stage 내에서 개별 데이터 파티션에 대해 실행되는 최소 작업 단위
  • Task는 Executor에서 병렬로 실행됨.

 

Spark 주요 데이터 구조

RDD (Resilient Distributed Dataset)

Spark의 가장 기본적인 데이터 구조로, 불변(immutable)하며 장애 발생 시 자동 복구되는 분산 데이터셋

  • 함수형 API를 활용하여 변환(Transformation)과 액션(Action)을 수행
  • 직접 메모리 관리와 최적화가 어렵기 때문에, 복잡한 작업에서는 DataFrame이나 Dataset으로 발전하게 되었음
rdd = spark.sparkContext.parallelize([("Alice", 25), ("Bob", 30)])
rdd_filtered = rdd.filter(lambda x: x[1] > 25)
print(rdd_filtered.collect())

DataFrame

RDD 위에 스키마를 적용하여 구조화된 데이터를 다룰 수 있는 고수준 API

 

  • SQL과 유사한 문법 및 연산을 지원하여 사용이 편리
  • Catalyst Optimizer를 통해 자동으로 쿼리 최적화를 수행해줌 

 

df = spark.createDataFrame([("Alice", 25), ("Bob", 30)], ["name", "age"])
df_filtered = df.filter(df.age > 25)
df_filtered.show()

Dataset

RDD와 DataFrame의 장점을 결합한 API로, 컴파일 시점에서 타입 체크가 가능하며, 데이터의 구조와 타입을 안전하게 관리할 수 있음 

  • Scala와 Java에서는 DataFrame보다 타입 안정성을 제공하는 반면, Python에서는 DataFrame과 동일하게 동작

Spark 실행 흐름 예제 

from pyspark.sql import SparkSession

# SparkSession 생성
spark = SparkSession.builder.appName("SparkExample").getOrCreate()

# 데이터 로드
df = spark.read.csv("data.csv", header=True, inferSchema=True)

# Transformations (Lazy Execution)
# Narrow Transformation: filter()는 각 파티션 내에서 독립적으로 처리됨.
df_filtered = df.filter(df["age"] > 30)

# Wide Transformation: groupBy()는 파티션 간의 데이터 이동(shuffle)이 발생하여 새로운 Stage 생성됨.
df_grouped = df_filtered.groupBy("city").count()

# Action 실행 시 Job이 생성되어 실제 연산 수행됨.
df_grouped.show()

# SparkSession 종료
spark.stop()
  • Lazy Execution:
    • filter()와 groupBy()와 같은 Transformation은 즉시 실행되지 않고, DAG(Directed Acyclic Graph) 형태의 연산 그래프를 생성
      실제 연산은 Action(예: show())이 호출될 때 수행됨 
  • Stage 분할:
    • filter()와 같은 Narrow Transformation은 동일 Stage에서 실행
    • groupBy()와 같이 데이터 재분배가 필요한 Wide Transformation은 별도의 Stage로 분리되어 실행