Apache Spark의 Job, Stage, Task
Apache Spark의 실행 모델에서 Job, Stage, Task는 분산 처리를 구조화해서 이해하는 핵심 개념임.
Spark가 내부적으로 어떻게 작업을 쪼개고, 네트워크를 통해 데이터를 주고받으며, 실제 연산을 실행하는지 파악하기 위해서는 이 세 가지 개념을 명확히 이해해야 함.
Job : Job이란 무엇인가?
Job은 사용자가 Action을 호출했을 때 트리거되는, 물리적 실행 단위들의 묶음임.
Spark에서 collect(), count(), save(), show() 등 Action 함수를 호출할 때마다 새로운 Job이 생성됨.
단일 Spark 애플리케이션 안에서도 여러 Job이 순차적으로(또는 때때로 병렬적으로) 실행될 수 있음.
Job : Job이 생성되는 과정
사용자가 Spark의 Transformation(map, filter, groupByKey, join 등)을 연속적으로 정의하면, 실제로는 Lazy Evaluation 원칙에 따라 즉시 계산을 수행하지 않고 논리적 실행 계획(Logical Plan)을 구성함.
사용자가 마지막에 action()을 호출하면, Spark는 그 시점에 DAG(Directed Acyclic Graph) Scheduler를 통해 논리적 계획을 분석하여 물리적 실행 계획을 만들어내고, 새로운 Job을 생성함.
이후 Job 안에서 구체적인 Stage와 Task가 결정되고, Cluster Manager를 통해 Executor들에게 작업이 배포됨.
Job : Job의 역할
Job은 “최종 결과를 내기 위한 Spark 엔진의 실행 단위”로 이해할 수 있음.
Job이 끝나면 Action이 요구한 결과(예: 데이터 개수, 수집된 레코드, 파일에 저장된 결과)가 만들어짐.
Spark UI(또는 History Server)에서 보면, 어떤 Action이 실행될 때마다 새로운 Job이 발생하는 것을 확인할 수 있음.
Stage : Stage란 무엇인가?
Stage는 Shuffle 경계를 기준으로 분리되는 물리적 실행 단위임.
Spark는 내부적으로 DAG(Directed Acyclic Graph)를 구성하는데, Shuffle을 일으키는 Transformation(예: reduceByKey, groupByKey, join) 직전에 Stage가 끊어짐.
Stage : 왜 Shuffle 경계에서 Stage가 나뉘는가?
Shuffle이 발생한다는 것은 데이터 재분배(재파티셔닝)가 필요하다는 의미임.
이전 단계(Map 단계)가 끝나야, Shuffle 파일(중간 결과)이 Executor의 디스크(또는 External Shuffle Service)에 저장되고, 그 다음 단계(Reduce 단계)가 이를 읽어와 연산을 진행할 수 있음.
즉, Shuffle을 기준으로 의존성(Dependency)이 끊기기 때문에 하나의 Stage가 완전히 끝난 후에만 다음 Stage가 시작될 수 있음.
Stage 간에는 선후 관계가 존재하므로, Stage 0이 끝나야 Stage 1이 실행될 수 있다는 식의 의존성이 생김.
Stage : Stage의 실행 흐름
DAG Scheduler는 Job을 분석해, Shuffle 연산이 등장할 때마다 Stage를 분리함.
Stage는 ‘연속된 파이프라이닝(Pipelining)이 가능한 Transformation 집합’으로 구성됨.
예를 들어, map -> filter -> map -> reduceByKey 중 reduceByKey 전까지가 하나의 Stage, 그 뒤가 또 다른 Stage가 됨.
각 Stage 내부에서는 여러 Task가 병렬로 실행되며, Stage의 모든 Task가 성공적으로 끝나야 해당 Stage는 완료됨.
Stage : Narrow vs Wide Dependency
Spark에서는 Shuffle 경계가 있는 연산(reduceByKey, join)을 Wide Dependency라고 부르며, 이 경계가 Stage를 나누는 핵심 기준임.
map, filter와 같이 특정 파티션이 1:1로 변환되는 연산은 Narrow Dependency라 하며, 이는 Stage 내부에서 파이프라이닝(pipelining)될 수 있음.
Task : Task란 무엇인가?
Task는 실제로 Executor가 실행하는 가장 작은 물리적 연산 단위임.
한 Stage 내의 각 파티션마다 보통 하나의 Task가 매핑됨.
예를 들어, Stage에 100개의 파티션이 있다면, 100개의 Task가 생성됨.
Task는 Executor의 CPU 코어 중 하나에서 실행되며, 데이터를 처리하고 결과를 생성함.
Task : Task 스케줄링
1. Driver(또는 SparkContext)는 Stage를 Task들의 집합으로 분할함.
2. Task Scheduler(저수준 스케줄러)는 가용한 Executor 리소스(코어 수, 작업 슬롯 등)를 확인해, 각 Task를 적절한 Executor에게 할당함.
데이터 로컬리티(해당 Task가 필요한 데이터가 어느 노드에 있는가)도 고려하여, 네트워크 전송을 최소화함.
3. Executor는 Task를 실행한 후, 결과(또는 중간 Shuffle 파일)를 생성함.
Task가 완료되면 Driver에게 보고하고, 다른 대기 중인 Task를 받을 수 있음.
Task : Task의 유형
크게 Shuffle Map Task와 Shuffle Reduce Task로 구분할 수 있음.
1. Shuffle Map Task
Shuffle을 일으키기 직전의 연산을 담당하며, (키, 값) 형태로 데이터를 파티션 나눠서 디스크에 기록(Shuffle Write).
2. Shuffle Reduce Task
앞 Stage에서 생성된 Shuffle 파일을 읽어(Shuffle Read) 최종 reduce나 집계 연산을 수행.
Task : Speculative Execution
Spark는 일부 Task가 지연되면, Speculative Execution을 통해 동일한 Task를 중복 실행하여, 더 빠른 쪽 결과를 취함으로써 전체 Job 시간을 단축하기도 함.
데이터 스큐나 노드 장애 등으로 인해 특정 Task가 지나치게 느려지는 상황을 완화해주는 기법임.
Job → Stage → Task 흐름 요약
1. 사용자 Action 호출
DataFrame/RDD의 Transformation이 모아진 상태에서, Action(collect, count 등)을 호출하면 Spark는 새로운 Job을 생성함.
2. DAG Scheduler
논리적 실행 계획을 분석하고, Shuffle 경계를 중심으로 여러 개의 Stage로 분할함.
Stage 간에는 의존성(Dependency)이 있으며, 보통 Stage 0 → Stage 1 → Stage 2 순으로 순차 실행됨.
병렬 가능성도 있으나 보통 의존 순서를 따름.
3. Stage 내 Task 생성
각 Stage는 파티션 단위로 병렬 처리가 가능하므로, Stage에 속한 파티션 수만큼 Task가 만들어짐.
예를 들어, 200개 파티션 → 200개 Task.
Task는 Executor에서 실행되어 실제 연산을 수행하고, 중간 결과(Shuffle Write) 또는 최종 결과를 생성함.
4. Task 실행 및 완료
Stage의 모든 Task가 성공해야 그 Stage가 완료되며, 다음 Stage가 실행됨.
Shuffle 파일이 준비된 뒤 다음 Stage에서 이를 Shuffle Read.
최종 Stage까지 마무리되면 Job이 완료되어 Action의 결과가 반환(또는 저장)됨.
고려할 사항
1. 각 Stage마다 다른 Executor 구성
Spark 동적 할당(Dynamic Allocation)을 사용하면, Stage별로 필요한 Executor 수를 조절함.
한 Stage가 끝나면 Executor를 회수해 비용을 절감하거나, 다음 Stage에서 다시 할당받을 수 있음.
이때 Shuffle 파일의 유효성(External Shuffle Service 등)도 함께 관리해야 함.
2. 데이터 스큐(Data Skew)에 따른 Task 불균형
특정 Stage 내에서 일부 파티션에만 데이터가 몰리는 경우, 해당 Task가 매우 오래 걸릴 수 있음.
이를 방지하기 위해 Skew Join 최적화(spark.sql.adaptive.skewJoin.enabled), 샘플링을 통한 재파티셔닝 등의 기법을 사용함.
3. Stage 병렬 실행
특정 Stage가 다른 Stage의 shuffle 데이터를 전혀 참조하지 않는 경우(즉, DAG 상 독립적인 경로가 있는 경우), 여러 Stage가 병렬로 실행될 수도 있음.
실제 프로덕션 환경에서 본격적으로 다양한 입력 소스(여러 RDD나 DataFrame)에서 작업할 때 나타남.
4. 저장 모드와 캐싱
Spark Application에서 중간 결과를 여러 번 참조해야 하는 경우, RDD/DataFrame 캐싱(persist)이나 체크포인팅(checkpoint)을 통해 재계산 오버헤드를 줄일 수 있음.
이로 인해 Job과 Stage 간의 실행 흐름이 더 복잡해질 수 있지만, 성능 향상에 큰 도움이 됨.
5. Adaptive Query Execution(AQE)
Spark SQL 3.x 이상에서 제공되는 AQE는 런타임에 Stage 결과 통계를 이용해 파티션 수 조정, 스큐 파티션 핸들링 등을 자동으로 수행함.
Stage가 실행 중에 상태를 모니터링하여 다음 Stage의 계획을 동적으로 최적화하기 때문에, Job/Stage 구조가 유연해짐.
정리
1. Job
Action을 트리거로 생성되는 최상위 실행 단위이며, 최종 결과를 산출하기 위한 전체 과정을 나타냄.
한 Spark Application 안에 여러 Job이 존재할 수 있음.
2. Stage
Shuffle 경계를 기준으로 Job이 나뉘어 형성되는 중간 실행 단위임.
Narrow Dependency 연산들은 한 Stage 안에서 연속적으로 파이프라이닝되고, Wide Dependency(Shuffle 연산)가 등장하면 새로운 Stage가 시작됨.
3. Task
Stage 내에서 파티션을 기반으로 병렬 실행되는 가장 작은 실행 단위로, 실제 계산(맵, 필터, 조인 등)을 담당함.
Executor 프로세스에서 Task를 수행하고, 중간 결과(Shuffle 파일) 또는 최종 결과를 생성함.
이러한 Job → Stage → Task 구조를 이해하면, Spark 애플리케이션이 어떤 경로로 데이터를 흐르게 하고, 어떻게 병렬 처리 및 Shuffle이 이루어지며, 어디에서 성능 병목이나 스큐가 발생하는지를 정확히 파악할 수 있음.
이를 기반으로 최적의 파티션 수 조정, Skew 처리, Dynamic Allocation, 캐싱, AQE 적용 등 다양한 성능 튜닝 방안을 효과적으로 적용할 수 있게 됨.
'Data Engineering > Spark' 카테고리의 다른 글
[Spark] Adaptive Query Execution (0) | 2025.01.17 |
---|---|
[Spark] 스파크의 동작 원리 (0) | 2025.01.17 |
[Spark] 스파크에서 Shuffle 개념 (0) | 2025.01.17 |
[Spark] 클러스터 매니저 종류 (0) | 2025.01.17 |
[Spark] Executor 개념 (0) | 2025.01.17 |