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())이 호출될 때 수행됨
- filter()와 groupBy()와 같은 Transformation은 즉시 실행되지 않고, DAG(Directed Acyclic Graph) 형태의 연산 그래프를 생성
- Stage 분할:
- filter()와 같은 Narrow Transformation은 동일 Stage에서 실행
- groupBy()와 같이 데이터 재분배가 필요한 Wide Transformation은 별도의 Stage로 분리되어 실행
'데이터 엔지니어링' 카테고리의 다른 글
PostgreSQL JOIN 성능 이슈 파이썬에서 해결하기 (0) | 2025.04.10 |
---|---|
Clickhouse 데이터 웨어하우스로서의 한계와 단점 (0) | 2025.03.18 |
BigQuery에서 ClickHouse로 이벤트 로그 데이터 분석 환경 개선하기 (1) | 2024.11.22 |
유저의 액션 이벤트 로그 설계와 개선 과정 (7) | 2024.11.12 |
Apache Airflow 이해하기 (1) | 2024.09.17 |