Apache Spark 기반 통합 분산 데이터 처리
1. 개요
가. 정의
Apache Spark란 대규모 데이터를 클러스터의 여러 노드에 분산해 처리하되, 중간 결과를 가능한 한 메모리상에서 재사용하고 모든 연산을 지연 평가(lazy evaluation) 기반의 방향성 비순환 그래프(DAG)로 최적화해 실행하는 통합(unified) 분산 데이터 처리 엔진이다.
Spark의 본질은 "하나의 엔진으로 배치·대화형 질의·스트리밍·머신러닝을 모두 처리한다"는 통합성에 있다. 개발자는 RDD(Resilient Distributed Dataset)·DataFrame·Dataset이라는 추상화를 통해 분산 컬렉션을 마치 단일 컬렉션처럼 조작하고, 엔진은 그 선언을 실행 계획으로 번역해 분할·스케줄링·셔플·장애 복구를 대신 수행한다. 따라서 개발자는 노드 간 통신과 장애 재시도를 직접 구현하지 않고도 수백~수천 대 규모로 확장된 데이터 파이프라인을 작성할 수 있다.
Spark는 2009년 UC 버클리 AMPLab에서 시작되어 2014년 Apache 최상위 프로젝트가 되었으며, 기존 MapReduce의 디스크 중심 배치 모델이 가진 반복 계산 비효율을 메모리 기반 처리로 극복하는 것을 핵심 가치로 내세웠다. 오늘날 Spark는 데이터 레이크하우스, ETL/ELT, 피처 엔지니어링, 로그 분석의 사실상 표준 처리 엔진으로 자리잡았다.
나. 등장 배경과 필요성
MapReduce는 대규모 배치를 단순화한 혁신이었지만, 각 단계(Map→Reduce)의 중간 결과를 반드시 분산 파일시스템(HDFS)에 기록하고 다음 단계에서 다시 읽어들이는 구조였다. 이 디스크 왕복은 한 번만 집계하는 배치에는 문제가 없지만, 동일 데이터를 수십 번 순회하는 반복 알고리즘(경사하강법, PageRank, K-means)이나 사람이 질의를 바꿔가며 탐색하는 대화형 분석에서는 I/O 비용이 누적되어 처리 시간이 비현실적으로 늘어난다.
현실의 데이터 조직은 서로 다른 워크로드마다 서로 다른 엔진(배치는 MapReduce, 질의는 Impala, 스트림은 Storm, ML은 Mahout)을 운영하면서 데이터 이동·중복 저장·운영 복잡성이라는 비용을 지불하고 있었다. Spark는 "메모리에 중간 결과를 유지하는 단일 추상화"와 "같은 엔진 위에서 SQL·스트림·ML을 함께 쓰는 통합 스택"으로 이 두 가지 고통을 동시에 해결하고자 등장했다.
특히 데이터 레이크하우스와 생성형 AI 시대에 들어서면서, 정형·반정형·비정형 데이터를 한 엔진에서 변환하고 대규모 피처를 생성해 모델 학습 파이프라인에 공급해야 할 필요가 커졌다. Spark는 이처럼 "이질적 데이터·이질적 워크로드"를 하나의 처리 계층으로 수렴시키는 수요에 부합한다.
다. 핵심 설계 목표
Spark 설계의 목표는 단순히 빠른 실행에 그치지 않는다. 메모리 재사용을 통한 속도, 계보(lineage) 기반의 장애 허용성, 선언적 API를 통한 프로그래밍 단순성, 하나의 엔진으로 여러 워크로드를 수용하는 통합성, 그리고 옵티마이저를 통한 자동 최적화를 함께 달성하는 것이 목적이다.
첫째, 데이터를 메모리에 캐싱해 반복 접근 비용을 없앤다. 둘째, 데이터 복제 대신 "어떻게 만들어졌는가"를 기록하는 계보로 장애를 복구해 저장 오버헤드를 줄인다. 셋째, 지연 평가로 전체 연산 그래프를 먼저 파악한 뒤 셔플 최소화·조건 하향 전파 같은 최적화를 적용한다. 이 세 축이 Spark 성능과 안정성의 근간이다.
2. 핵심 추상화와 전체 아키텍처
가. RDD·DataFrame·Dataset 추상화
Spark의 1세대 추상화는 RDD로, 파티션으로 분할된 불변(immutable) 분산 컬렉션이며 map·filter·reduceByKey 같은 함수형 연산으로 변환한다. RDD는 각 변환이 "부모 RDD로부터 어떻게 파생되었는지"를 계보로 기억하므로, 일부 파티션이 노드 장애로 사라져도 해당 부분만 재계산해 복구한다. 이것이 복제 없이 장애를 견디는 핵심 원리다.
2세대 추상화인 DataFrame은 행과 열(스키마)을 가진 테이블형 분산 컬렉션으로, 내부적으로 Catalyst 옵티마이저와 Tungsten 실행엔진의 최적화를 받는다. DataFrame은 사용자가 "무엇을 원하는지"를 선언하면 엔진이 "어떻게 실행할지"를 결정하므로, 같은 논리를 저수준 RDD로 쓸 때보다 일반적으로 더 빠르다. Dataset은 여기에 컴파일 타임 타입 안전성을 더한 JVM 전용(Scala/Java) 추상화다.
실무에서는 특별한 저수준 제어가 필요하지 않다면 DataFrame/Dataset을 우선 사용하는 것이 권장된다. 예를 들어 하루 수십억 건의 거래 로그에서 가맹점별 매출을 집계할 때, DataFrame의 groupBy().agg()는 옵티마이저가 셔플 전 부분 집계(partial aggregation)를 자동 삽입해 네트워크 전송량을 크게 줄여준다. 동일 로직을 RDD로 직접 짜면 이 최적화를 개발자가 수작업으로 구현해야 한다.
나. 전체 실행 아키텍처
flowchart LR
subgraph Driver["드라이버(Driver)"]
SC["SparkContext / SparkSession"]
CAT["Catalyst 옵티마이저"]
DAG["DAG 스케줄러"]
end
subgraph CM["클러스터 매니저(YARN·K8s·Standalone)"]
RES["자원 할당"]
end
subgraph Workers["워커 노드(Executor)"]
E1["Executor 1<br/>Task·캐시"]
E2["Executor 2<br/>Task·캐시"]
E3["Executor 3<br/>Task·캐시"]
end
SRC["데이터 소스(HDFS·S3·Kafka·JDBC)"] --> SC
SC --> CAT --> DAG
DAG -->|자원 요청| RES
RES -->|Executor 기동| E1
RES -->|Executor 기동| E2
RES -->|Executor 기동| E3
DAG -->|Task 배포| E1
DAG -->|Task 배포| E2
DAG -->|Task 배포| E3
E1 -->|결과·상태| SC
E2 -->|결과·상태| SC
E3 -->|결과·상태| SC
Spark 애플리케이션은 드라이버가 사용자 코드를 실행하면서 논리 계획을 수립하고, 클러스터 매니저(YARN·Kubernetes·Standalone)에 자원을 요청해 Executor들을 기동한 뒤, 각 Executor에 Task를 배포하는 구조로 동작한다. 드라이버는 전체 계산의 두뇌로서 스케줄링과 결과 수집을 담당하고, Executor는 실제 파티션별 연산과 메모리 캐시를 담당한다. 드라이버가 죽으면 애플리케이션 전체가 종료되므로, 운영에서는 드라이버의 메모리·HA 구성이 중요한 관리 포인트가 된다.
다. 지연 평가와 DAG 실행
Spark의 연산은 트랜스포메이션(transformation)과 액션(action)으로 나뉜다. map·filter·join 같은 트랜스포메이션은 즉시 실행되지 않고 계보만 쌓이며, count·collect·save 같은 액션이 호출될 때 비로소 전체 계획이 실행된다. 이 지연 평가 덕분에 엔진은 연산 체인 전체를 본 뒤 불필요한 컬럼 제거(column pruning), 조건 하향 전파(predicate pushdown), 연속된 좁은 변환의 결합 같은 최적화를 수행할 수 있다.
flowchart TD
READ["① 데이터 읽기 read()"] --> T1["② filter (좁은 의존성)"]
T1 --> T2["③ select (좁은 의존성)"]
T2 --> SH["④ groupBy·join (넓은 의존성·셔플)"]
SH --> T3["⑤ agg 집계"]
T3 --> ACT["⑥ write / collect (액션)"]
ACT -.트리거.-> JOB["Job 생성"]
JOB --> ST["Stage 분할(셔플 경계 기준)"]
ST --> TASK["Task 생성(파티션 수만큼)"]
실행 시 Spark는 하나의 액션을 Job으로, 셔플(넓은 의존성)이 발생하는 지점을 경계로 Stage로, 각 Stage를 파티션 수만큼의 Task로 분할한다. 좁은 의존성(narrow dependency)은 한 파티션이 부모의 한 파티션에만 의존해 셔플 없이 파이프라인으로 묶을 수 있고, 넓은 의존성(wide dependency)은 여러 파티션에 데이터를 재분배하는 셔플을 유발한다. 성능 튜닝의 상당 부분은 바로 이 셔플 횟수와 셔플 데이터량을 줄이는 작업이다.
3. 통합 라이브러리 스택과 처리 모델
가. Spark SQL과 Catalyst·Tungsten
Spark SQL은 SQL과 DataFrame API를 같은 실행엔진으로 통합한다. 질의는 Catalyst 옵티마이저가 논리 계획→최적화된 논리 계획→여러 물리 계획→비용 기반 선택의 단계를 거쳐 변환하고, Tungsten은 전체 단계 코드 생성(whole-stage code generation)과 오프힙(off-heap) 메모리 관리로 CPU·메모리 효율을 끌어올린다. 예컨대 S3에 저장된 Parquet 로그 1TB에 WHERE dt='2026-10-31' 조건을 걸면, Catalyst는 조건을 소스 스캔 단계로 밀어내고(predicate pushdown) 파티션 가지치기(partition pruning)로 필요한 날짜 폴더만 읽어 실제 I/O를 수 GB 수준으로 줄인다.
나. 구조적 스트리밍(Structured Streaming)
구조적 스트리밍은 스트림을 "끝없이 커지는 테이블"로 모델링해, 배치에서 쓰던 DataFrame 코드를 거의 그대로 스트림에 재사용하게 한다. 마이크로배치(micro-batch) 또는 연속 처리 모드로 동작하며, 이벤트 시간(event time) 기반 윈도우와 워터마크(watermark)로 지연 도착 데이터를 처리하고, 체크포인트와 쓰기 전 로그(WAL)로 정확히 한 번(exactly-once) 처리 의미론을 제공한다. 가령 Kafka에서 유입되는 결제 이벤트를 5분 윈도우로 집계해 이상 거래를 탐지하는 파이프라인을, 야간 배치와 동일한 집계 코드로 구현할 수 있다는 점이 운영 단순화의 핵심이다.
다. MLlib와 GraphX
MLlib는 분류·회귀·군집·협업 필터링 등의 분산 머신러닝 알고리즘과 파이프라인 API를 제공해, 수 TB 규모 피처 데이터에서도 학습을 분산한다. GraphX(및 GraphFrames)는 PageRank, 연결 요소 탐색 같은 그래프 연산을 분산 처리한다. 다만 딥러닝 학습 자체는 Spark보다 GPU 전용 프레임워크가 적합하므로, 실무에서는 Spark로 대규모 전처리·피처 생성을 수행하고 학습은 별도 GPU 클러스터로 넘기는 분업 구조가 흔하다.
라. 메모리 관리와 셔플
Executor 메모리는 실행(execution) 영역과 저장(storage) 캐시 영역이 통합 관리되며(Unified Memory Management), 둘은 필요에 따라 서로의 공간을 빌린다. 셔플 단계에서는 맵 출력이 디스크에 기록되었다가 리듀스 측으로 전송되므로, 데이터 스큐(특정 키에 값이 몰리는 현상)가 발생하면 일부 Task가 전체 Stage를 지연시킨다. 이를 완화하기 위해 Spark 3.x의 AQE(Adaptive Query Execution)는 런타임 통계로 셔플 파티션 수를 자동 조정하고, 스큐 조인을 분할하며, 작은 조인을 브로드캐스트로 전환한다.
4. 비교와 적용 사례
Spark의 위치는 MapReduce 및 순수 스트림 엔진과의 비교로 분명해진다. 아래 표는 보조 정리이며, 차이가 생기는 이유는 이어지는 문장으로 설명한다.
| 구분 | MapReduce | Apache Spark | Apache Flink |
|---|---|---|---|
| 처리 모델 | 디스크 기반 배치 | 메모리 기반 배치+마이크로배치 스트림 | 네이티브 스트림(배치는 특수 케이스) |
| 중간 결과 | HDFS 기록 | 메모리 유지·선택적 디스크 | 상태(state) 기반 |
| 반복 계산 | 느림(매 단계 I/O) | 빠름(캐시 재사용) | 빠름 |
| 지연시간 | 높음(분 단위) | 중간(초~분) | 낮음(밀리초~초) |
| 주 용도 | 대규모 배치 | 통합 배치·ETL·ML·준실시간 | 저지연 실시간 |
MapReduce 대비 Spark가 반복 계산에서 수 배~수십 배 빠른 이유는 디스크 왕복을 메모리 재사용으로 대체했기 때문이다. 반면 Flink가 초저지연 스트림에서 우위를 갖는 이유는 Spark의 전통적 마이크로배치가 배치 경계만큼의 지연을 내재하는 데 비해, Flink는 레코드 단위 네이티브 스트림과 정교한 상태 관리를 기본으로 삼기 때문이다. 따라서 "모든 것을 하나로 통합"이 목표라면 Spark가, "밀리초 수준 이벤트 처리"가 목표라면 Flink가 유리하다는 실무적 함의가 도출된다.
구체적 적용 사례로, 국내외 대형 커머스 기업은 하루 수백억 건의 클릭·주문 로그를 Spark 배치로 집계해 상품 추천용 피처를 생성하고, 동시에 구조적 스트리밍으로 실시간 재고·부정결제 지표를 산출하는 람다 유사 구조를 운영한다. 금융권에서는 야간에 수 TB 거래 데이터를 Spark SQL로 정산·리스크 집계하고, AQE로 월말 피크 시 급증하는 셔플 스큐를 자동 흡수해 배치 완료 시간을 안정화한 사례가 보고된다. 통신사에서는 수천만 가입자의 CDR(통화상세기록)을 Spark로 전처리해 MLlib 기반 이탈 예측 피처를 하루 단위로 갱신한다.
5. 심화: 레이크하우스·AI 시대의 Spark와 최신 동향
최근 Spark는 단순 처리 엔진을 넘어 데이터 레이크하우스의 처리 계층으로 진화하고 있다. Delta Lake·Apache Iceberg·Hudi 같은 오픈 테이블 포맷과 결합해 ACID 트랜잭션, 스키마 진화, 타임 트래블을 지원함으로써, 과거 데이터 웨어하우스에서만 가능하던 신뢰성 있는 갱신을 객체 스토리지 위에서 구현한다. 예를 들어 Iceberg 테이블에 Spark로 MERGE(업서트)를 수행하면 동시 쓰기 충돌을 스냅샷 격리로 관리하며, 잘못된 적재는 이전 스냅샷으로 롤백할 수 있다.
운영 측면에서는 Spark on Kubernetes가 YARN을 대체하는 흐름이 뚜렷하다. 쿠버네티스의 동적 자원 할당(Dynamic Resource Allocation)과 결합하면 유휴 Executor를 자동 반납해 클라우드 비용을 절감하고, 스팟 인스턴스 회수 시 셔플 데이터를 별도 스토리지에 분리 저장하는 기법으로 안정성을 보완한다. 또한 서버리스 Spark(관리형 서비스)와 사진·상호작용식 노트북 환경(Databricks·EMR·Dataproc)이 보편화되며 클러스터 운영 부담이 낮아지고 있다.
AI 시대 들어 Spark는 생성형 AI 파이프라인의 전처리 허브 역할이 커졌다. 수십억 문서의 중복 제거·필터링·토큰화 같은 대규모 LLM 학습 데이터 큐레이션, 임베딩 배치 생성, RAG용 청크 전처리가 Spark로 수행된다. 최신 버전에서는 Spark Connect(클라이언트-서버 분리 아키텍처)로 경량 클라이언트가 원격 Spark 클러스터에 접속하고, 판다스 API on Spark로 단일 노드 판다스 코드를 분산 실행하는 등 접근성이 개선되었다. 다만 벡터 유사도 검색이나 GPU 집약 추론은 전용 엔진에 위임하는 역할 분담이 여전히 권장된다.
6. 고려사항 및 시사점
기술사 관점에서 Spark 도입과 운영은 다음을 종합적으로 고려해야 한다.
첫째(적용 전략), "통합 vs 전문화"의 선택이 선행되어야 한다. Spark는 배치·ETL·준실시간·ML을 하나로 묶는 데 강하지만, 밀리초 수준 실시간이 핵심이라면 Flink를, 초저지연 OLAP 질의가 핵심이라면 분산 SQL 엔진을 병행하는 것이 합리적이다. 조직의 지배적 워크로드를 기준으로 Spark를 중심 엔진으로 두되 보완 엔진을 명확히 구분해야 과도한 단일화의 비효율을 피한다.
둘째(트레이드오프), 메모리 재사용의 장점과 비용을 균형 있게 봐야 한다. 캐싱은 반복 계산을 가속하지만 과도한 캐시는 GC 부담과 OOM(메모리 부족)을 유발한다. 드라이버 collect 남용, 비현실적 파티션 수, 데이터 스큐는 전형적 장애 원인이므로, AQE·브로드캐스트 조인·적정 파티셔닝·스큐 키 분산(salting)을 설계 단계에서 반영해야 한다.
셋째(비용·운영), 클라우드 비용 거버넌스가 중요하다. Spark on Kubernetes의 동적 할당, 스팟 인스턴스 활용, 데이터 스킵핑(파티셔닝·Z-Ordering)으로 스캔량을 줄이면 비용을 크게 낮출 수 있다. FinOps 관점에서 잡 단위 비용 가시화와 자동 스케일 정책을 결합해야 지속 가능한 운영이 된다.
넷째(데이터 신뢰성·거버넌스), 레이크하우스 테이블 포맷과 결합해 ACID·스키마 진화·데이터 계보(lineage)·품질 검증을 파이프라인에 내장해야 한다. 처리 속도만 추구하고 데이터 품질·거버넌스를 간과하면 "빠르게 잘못된 결과"를 양산할 위험이 있다.
다섯째(연계 기술 및 전망), Spark는 Kafka(수집)·오픈 테이블 포맷(저장)·MLOps/피처 스토어(활용)·오케스트레이션(Airflow)과 결합해 현대 데이터 플랫폼의 처리 중추를 형성한다. 향후에는 Spark Connect 기반 경량화, 벡터·AI 워크로드와의 상호운용, 서버리스화가 심화되며, 기술사는 Spark를 "만능 엔진"이 아니라 "통합 처리 계층"으로 자리매김하고 전문 엔진과의 역할 분담을 아키텍처 관점에서 설계하는 안목을 갖추어야 한다.
참고자료
- Apache Spark 공식 문서, https://spark.apache.org/docs/latest/
- M. Zaharia et al., "Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing", NSDI 2012, https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf
- Apache Spark, "Structured Streaming Programming Guide", https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
- Databricks, "Adaptive Query Execution", https://www.databricks.com/blog/2020/05/29/adaptive-query-execution-speeding-up-spark-sql-at-runtime.html
한 줄 요약: Apache Spark는 RDD·DataFrame 추상화와 계보 기반 장애복구, 지연 평가·DAG 최적화, 메모리 재사용을 통해 배치·SQL·스트리밍·ML을 하나의 엔진으로 통합 처리하는 분산 데이터 처리 플랫폼으로, 레이크하우스·AI 전처리의 처리 중추로 진화하고 있다.