Spark Application 실행 과정
아파치 스파크(Apache Spark)는 대규모 데이터 처리를 위해 설계된 분산 컴퓨팅 프레임워크임.
스파크 애플리케이션은 “드라이버(Driver)”와 “익스큐터(Executor)”가 상호작용하며 작업을 수행하는 구조로 동작함.
이때 기본적으로 스파크 애플리케이션이 생성되고 실행되는 과정을 이해하면, 내부 아키텍처나 최적화 전략을 수립하는 데 큰 도움이 됨.
스파크 애플리케이션의 구성 요소
1. Driver Program(드라이버)
사용자 코드(예: 스칼라, 파이썬, 자바 등)에서 SparkSession 또는 SparkContext를 초기화하면, 드라이버가 생성됨.
드라이버는 스파크 애플리케이션의 메인 진입점으로, 전체 실행 계획(DAG)을 관리하고, 스케줄링하고, 작업(Job) 진행 상황을 모니터링함.
드라이버는 클러스터 매니저(Cluster Manager)에게 자원(코어, 메모리 등)을 요청하고, 해당 자원 위에서 익스큐터를 기동시킴.
2. Cluster Manager(클러스터 매니저)
스파크는 자체 스탠드얼론 모드(Stand-Alone), YARN, Kubernetes, 또는 Mesos 같은 다양한 클러스터 매니저에서 동작할 수 있음.
드라이버가 클러스터 매니저에게 필요한 자원(Executor 개수, 메모리, CPU 등)을 요청하면, 클러스터 매니저가 적절한 워커(Worker) 노드에서 익스큐터 프로세스를 생성하도록 지시함.
3. Executor(익스큐터)
익스큐터는 실제로 태스크(Task)를 수행하는 프로세스임.
드라이버가 할당한 각 작업(Task)을 받아서 실행하고, 작업 결과(중간 결과나 최종 결과)를 메모리에 보관하거나(캐싱, RDD 저장), 디스크에 셔플 데이터를 기록함.
애플리케이션이 종료될 때까지 실행되며, 드라이버가 제출하는 모든 작업을 책임지게 됨.
스파크 애플리케이션 실행 모드
1. 클라이언트 모드(Client Mode)
드라이버 프로세스가 사용자의 로컬 머신(또는 외부 서버)에서 실행됨.
클러스터 매니저는 익스큐터만 클러스터 노드에서 동작하도록 함.
일반적으로 개발 및 테스트 환경에서 많이 사용함.
2. 클러스터 모드(Cluster Mode)
드라이버도 클러스터 내 컨테이너(노드)에서 실행됨.
애플리케이션 제출 시점부터 클러스터에서 자원을 받아 전체가 동작하므로, 특히 YARN 클러스터 모드나 Kubernetes 환경에서 프로덕션 수준으로 많이 활용함.
스파크 내부 동작: RDD 및 DAG 형성
1. RDD(Resilient Distributed Dataset)
스파크의 핵심 추상화 개념임.
RDD는 “트랜스포메이션(Transformation)”과 “액션(Action)”이라는 두 유형의 연산으로 구성됨.
트랜스포메이션은 새로운 RDD를 정의만 하며 실행 결과를 즉시 생성하지 않고, 액션이 호출되었을 때 비로소 실제 연산이 일어남.
2. DAG(Directed Acyclic Graph) 스케줄러
사용자가 트랜스포메이션을 연쇄적으로 호출하면, 내부적으로는 이들을 연결해 DAG가 구성됨.
map(), flatMap(), filter(), join() 같은 트랜스포메이션이 어떻게 연결되는지, 그리고 셔플(Shuffle) 경계가 어디에 있는지 파악하여, 스파크는 전체 연산 단계를 “Stage” 단위로 분할함.
이 Stage가 만들어지고, 각 Stage 내에서 병렬로 실행될 수 있는 최소 단위의 작업(태스크, Task)들을 생성함.
스파크 잡과 스테이지, 태스크
1. Job
액션(Action) 하나가 트리거될 때마다, 스파크 애플리케이션은 하나의 Job을 생성함.
예를 들어 count() 또는 collect() 같은 액션이 호출되면, 해당 액션에 필요한 모든 트랜스포메이션 경로가 분석되어 Job이 만들어짐.
2. Stage
Job은 여러 Stage로 나누어짐.
일반적으로 셔플(Shuffle)이 발생할 때마다 Stage 경계가 생김.
예를 들어, reduceByKey(), groupByKey(), join() 같은 연산은 셔플을 일으켜 데이터를 재분산하기 때문에 Stage가 구분됨.
3. Task
각 Stage는 여러 개의 Task로 구성됨.
파티션 단위로 병렬 처리가 가능하기 때문에, 한 RDD가 n개의 파티션을 가지고 있으면, 해당 Stage에서 n개의 Task가 생성됨.
Task는 최종적으로 Executor에서 실행되며, 파티션 데이터를 처리함.
실행 흐름 단계별 요약
1. 코드 작성 및 스파크 세션/컨텍스트 초기화
스파크 어플리케이션 코드를 작성하여 SparkSession.builder() (또는 SparkContext)를 호출하면, 드라이버가 초기화됨.
이때, 드라이버는 클러스터 매니저와 통신하기 위한 정보(예: 마스터 URL, Yarn 모드 설정 등)를 설정함.
2. 클러스터 매니저를 통한 자원 할당
드라이버가 “Executor” 수, 각 Executor에 할당할 CPU/메모리 등을 요청함.
클러스터 매니저는 이를 기반으로 각 워커 노드에 실제 Executor 프로세스를 시작하도록 지시함.
Executor가 뜨면, 드라이버와 Executor 사이를 연결하는 통신 채널(주로 RPC)이 설정됨.
3. RDD, DataFrame, Dataset 등 트랜스포메이션 정의
드라이버에서 사용자가 코드를 통해 여러 트랜스포메이션을 호출하면, 내부적으로 DAG가 만들어짐. (실제 연산은 일어나지 않음)
DAG 내부에는 연산 의존성과 셔플 경계 정보가 축적됨.
4. 액션 호출 -> Job 생성 & DAG 스케줄링
사용자가 RDD/DataFrame에 대해 collect(), count(), save() 등 액션을 호출하는 순간, 스파크는 해당 DAG를 Job으로 인식하고 스케줄링 과정에 들어감.
DAG 스케줄러는 Job을 여러 Stage로 분할하고, Stage 내부에 필요한 Task들을 생성함.
5. Task 분배 및 Executor 실행
드라이버는 분할된 태스크(Task) 목록을 스케줄러(예: TaskScheduler)에 전달함.
스케줄러는 현재 클러스터 상황(각 Executor 상태, 데이터 로컬리티, 자원 가용성)에 따라 적절한 Executor로 Task를 할당함.
Executor는 Task를 받아서 해당 RDD 파티션 데이터를 로드하고, 트랜스포메이션을 수행함.
6. 셔플과 Stage 전환
어떤 Stage에서 셔플이 필요한 연산을 수행하게 되면, 셔플 파일(중간 결과)이 각 노드 디스크에 분산되어 저장됨.
해당 Stage가 완료되면, 셔플 파일이 다음 Stage의 입력이 되어 재분배(Shuffled RDD) 과정을 거침.
이후 다음 Stage의 Task가 실행되어 최종 결과 또는 후속 처리를 수행함.
7. 결과 수집 및 애플리케이션 종료
Job이 성공적으로 끝나면, 액션 결과가 드라이버에게 반환되거나, 외부 스토리지(HDFS, Cassandra, S3 등)에 저장됨.
모든 Job 실행이 마무리되고, 드라이버가 spark.stop() 등을 통해 애플리케이션을 종료하면, Executor들은 자원을 해제하고 프로세스가 종료됨.
추가 고려 사항
1. 캐싱(Caching)/퍼시스팅(Persisting)
자주 재사용될 것 같은 중간 RDD나 DataFrame을 메모리에 저장(캐싱)하여 불필요한 반복 계산을 줄일 수 있음.
이때 캐싱 전략(메모리/디스크 레벨, StorageLevel)은 성능과 비용 사이에서 적절히 선택해야 함.
2. 데이터 로컬리티(Data Locality)
스파크는 가능한 한 데이터가 존재하는 노드에 있는 Executor에서 Task를 실행시켜 네트워크 I/O를 최소화하려고 함.
따라서 입력 데이터가 어디에 있는지(파일 시스템 블록 위치, HDFS DataNode 등)를 파악하여 스케줄링함.
3. 셔플(Shuffle) 최적화
셔플은 네트워크와 디스크 I/O가 대규모로 발생하기 때문에, 스파크 성능에 큰 영향을 미침.
셔플 파일 압축, 병렬도(partition 개수), 실행 메모리 튜닝 등으로 성능을 개선할 수 있음.
4. Fault Tolerance(장애 복구)
Task가 실패하면, 동일한 파티션 데이터를 다른 Executor에서 다시 수행할 수 있음.
RDD는 계보(Lineage) 정보를 통해 중간 데이터가 손실되었을 때 트랜스포메이션 경로를 재실행하여 복구할 수 있음.
정리
스파크 애플리케이션은 드라이버가 애플리케이션 실행 계획(DAG)을 만들고, 클러스터 매니저로부터 자원을 할당받아 여러 Executor를 띄운 뒤, 각 Stage와 Task를 분배하여 처리하는 과정을 거침.
트랜스포메이션과 액션, DAG, 스테이지, 태스크, 셔플 등의 개념을 잘 이해하고 있으면 스파크가 내부적으로 어떻게 동작하는지 정확히 파악할 수 있고, 그에 따라 클러스터 구조 설계나 성능 최적화 방안을 수립하는 데 큰 도움이 됨.
결국, 스파크의 핵심은 (1) DAG 기반의 지연(lazy) 실행, (2) RDD/DataFrame 파티션 단위 병렬 처리, (3) 셔플 경계마다 Stage를 구분, (4) 자원의 유연한 스케줄링으로 정리할 수 있음.
이를 제대로 이해하고 활용한다면, 스파크를 통해 대규모 데이터를 효율적으로 처리하고, 다양한 배포 환경에서 유연하게 운영할 수 있음.
'Data Engineering > Spark' 카테고리의 다른 글
[Spark] Apache ORC 파일 구조 (0) | 2025.03.09 |
---|---|
[Spark] Apache Parquet 파일 구조 (0) | 2025.03.09 |
[Spark] Adaptive Query Execution (0) | 2025.01.17 |
[Spark] Apache Spark의 Job, Stage, Task 구조 (0) | 2025.01.17 |
[Spark] 스파크의 동작 원리 (0) | 2025.01.17 |
Spark Application 실행 과정
아파치 스파크(Apache Spark)는 대규모 데이터 처리를 위해 설계된 분산 컴퓨팅 프레임워크임.
스파크 애플리케이션은 “드라이버(Driver)”와 “익스큐터(Executor)”가 상호작용하며 작업을 수행하는 구조로 동작함.
이때 기본적으로 스파크 애플리케이션이 생성되고 실행되는 과정을 이해하면, 내부 아키텍처나 최적화 전략을 수립하는 데 큰 도움이 됨.
스파크 애플리케이션의 구성 요소
1. Driver Program(드라이버)
사용자 코드(예: 스칼라, 파이썬, 자바 등)에서 SparkSession 또는 SparkContext를 초기화하면, 드라이버가 생성됨.
드라이버는 스파크 애플리케이션의 메인 진입점으로, 전체 실행 계획(DAG)을 관리하고, 스케줄링하고, 작업(Job) 진행 상황을 모니터링함.
드라이버는 클러스터 매니저(Cluster Manager)에게 자원(코어, 메모리 등)을 요청하고, 해당 자원 위에서 익스큐터를 기동시킴.
2. Cluster Manager(클러스터 매니저)
스파크는 자체 스탠드얼론 모드(Stand-Alone), YARN, Kubernetes, 또는 Mesos 같은 다양한 클러스터 매니저에서 동작할 수 있음.
드라이버가 클러스터 매니저에게 필요한 자원(Executor 개수, 메모리, CPU 등)을 요청하면, 클러스터 매니저가 적절한 워커(Worker) 노드에서 익스큐터 프로세스를 생성하도록 지시함.
3. Executor(익스큐터)
익스큐터는 실제로 태스크(Task)를 수행하는 프로세스임.
드라이버가 할당한 각 작업(Task)을 받아서 실행하고, 작업 결과(중간 결과나 최종 결과)를 메모리에 보관하거나(캐싱, RDD 저장), 디스크에 셔플 데이터를 기록함.
애플리케이션이 종료될 때까지 실행되며, 드라이버가 제출하는 모든 작업을 책임지게 됨.
스파크 애플리케이션 실행 모드
1. 클라이언트 모드(Client Mode)
드라이버 프로세스가 사용자의 로컬 머신(또는 외부 서버)에서 실행됨.
클러스터 매니저는 익스큐터만 클러스터 노드에서 동작하도록 함.
일반적으로 개발 및 테스트 환경에서 많이 사용함.
2. 클러스터 모드(Cluster Mode)
드라이버도 클러스터 내 컨테이너(노드)에서 실행됨.
애플리케이션 제출 시점부터 클러스터에서 자원을 받아 전체가 동작하므로, 특히 YARN 클러스터 모드나 Kubernetes 환경에서 프로덕션 수준으로 많이 활용함.
스파크 내부 동작: RDD 및 DAG 형성
1. RDD(Resilient Distributed Dataset)
스파크의 핵심 추상화 개념임.
RDD는 “트랜스포메이션(Transformation)”과 “액션(Action)”이라는 두 유형의 연산으로 구성됨.
트랜스포메이션은 새로운 RDD를 정의만 하며 실행 결과를 즉시 생성하지 않고, 액션이 호출되었을 때 비로소 실제 연산이 일어남.
2. DAG(Directed Acyclic Graph) 스케줄러
사용자가 트랜스포메이션을 연쇄적으로 호출하면, 내부적으로는 이들을 연결해 DAG가 구성됨.
map(), flatMap(), filter(), join() 같은 트랜스포메이션이 어떻게 연결되는지, 그리고 셔플(Shuffle) 경계가 어디에 있는지 파악하여, 스파크는 전체 연산 단계를 “Stage” 단위로 분할함.
이 Stage가 만들어지고, 각 Stage 내에서 병렬로 실행될 수 있는 최소 단위의 작업(태스크, Task)들을 생성함.
스파크 잡과 스테이지, 태스크
1. Job
액션(Action) 하나가 트리거될 때마다, 스파크 애플리케이션은 하나의 Job을 생성함.
예를 들어 count() 또는 collect() 같은 액션이 호출되면, 해당 액션에 필요한 모든 트랜스포메이션 경로가 분석되어 Job이 만들어짐.
2. Stage
Job은 여러 Stage로 나누어짐.
일반적으로 셔플(Shuffle)이 발생할 때마다 Stage 경계가 생김.
예를 들어, reduceByKey(), groupByKey(), join() 같은 연산은 셔플을 일으켜 데이터를 재분산하기 때문에 Stage가 구분됨.
3. Task
각 Stage는 여러 개의 Task로 구성됨.
파티션 단위로 병렬 처리가 가능하기 때문에, 한 RDD가 n개의 파티션을 가지고 있으면, 해당 Stage에서 n개의 Task가 생성됨.
Task는 최종적으로 Executor에서 실행되며, 파티션 데이터를 처리함.
실행 흐름 단계별 요약
1. 코드 작성 및 스파크 세션/컨텍스트 초기화
스파크 어플리케이션 코드를 작성하여 SparkSession.builder() (또는 SparkContext)를 호출하면, 드라이버가 초기화됨.
이때, 드라이버는 클러스터 매니저와 통신하기 위한 정보(예: 마스터 URL, Yarn 모드 설정 등)를 설정함.
2. 클러스터 매니저를 통한 자원 할당
드라이버가 “Executor” 수, 각 Executor에 할당할 CPU/메모리 등을 요청함.
클러스터 매니저는 이를 기반으로 각 워커 노드에 실제 Executor 프로세스를 시작하도록 지시함.
Executor가 뜨면, 드라이버와 Executor 사이를 연결하는 통신 채널(주로 RPC)이 설정됨.
3. RDD, DataFrame, Dataset 등 트랜스포메이션 정의
드라이버에서 사용자가 코드를 통해 여러 트랜스포메이션을 호출하면, 내부적으로 DAG가 만들어짐. (실제 연산은 일어나지 않음)
DAG 내부에는 연산 의존성과 셔플 경계 정보가 축적됨.
4. 액션 호출 -> Job 생성 & DAG 스케줄링
사용자가 RDD/DataFrame에 대해 collect(), count(), save() 등 액션을 호출하는 순간, 스파크는 해당 DAG를 Job으로 인식하고 스케줄링 과정에 들어감.
DAG 스케줄러는 Job을 여러 Stage로 분할하고, Stage 내부에 필요한 Task들을 생성함.
5. Task 분배 및 Executor 실행
드라이버는 분할된 태스크(Task) 목록을 스케줄러(예: TaskScheduler)에 전달함.
스케줄러는 현재 클러스터 상황(각 Executor 상태, 데이터 로컬리티, 자원 가용성)에 따라 적절한 Executor로 Task를 할당함.
Executor는 Task를 받아서 해당 RDD 파티션 데이터를 로드하고, 트랜스포메이션을 수행함.
6. 셔플과 Stage 전환
어떤 Stage에서 셔플이 필요한 연산을 수행하게 되면, 셔플 파일(중간 결과)이 각 노드 디스크에 분산되어 저장됨.
해당 Stage가 완료되면, 셔플 파일이 다음 Stage의 입력이 되어 재분배(Shuffled RDD) 과정을 거침.
이후 다음 Stage의 Task가 실행되어 최종 결과 또는 후속 처리를 수행함.
7. 결과 수집 및 애플리케이션 종료
Job이 성공적으로 끝나면, 액션 결과가 드라이버에게 반환되거나, 외부 스토리지(HDFS, Cassandra, S3 등)에 저장됨.
모든 Job 실행이 마무리되고, 드라이버가 spark.stop() 등을 통해 애플리케이션을 종료하면, Executor들은 자원을 해제하고 프로세스가 종료됨.
추가 고려 사항
1. 캐싱(Caching)/퍼시스팅(Persisting)
자주 재사용될 것 같은 중간 RDD나 DataFrame을 메모리에 저장(캐싱)하여 불필요한 반복 계산을 줄일 수 있음.
이때 캐싱 전략(메모리/디스크 레벨, StorageLevel)은 성능과 비용 사이에서 적절히 선택해야 함.
2. 데이터 로컬리티(Data Locality)
스파크는 가능한 한 데이터가 존재하는 노드에 있는 Executor에서 Task를 실행시켜 네트워크 I/O를 최소화하려고 함.
따라서 입력 데이터가 어디에 있는지(파일 시스템 블록 위치, HDFS DataNode 등)를 파악하여 스케줄링함.
3. 셔플(Shuffle) 최적화
셔플은 네트워크와 디스크 I/O가 대규모로 발생하기 때문에, 스파크 성능에 큰 영향을 미침.
셔플 파일 압축, 병렬도(partition 개수), 실행 메모리 튜닝 등으로 성능을 개선할 수 있음.
4. Fault Tolerance(장애 복구)
Task가 실패하면, 동일한 파티션 데이터를 다른 Executor에서 다시 수행할 수 있음.
RDD는 계보(Lineage) 정보를 통해 중간 데이터가 손실되었을 때 트랜스포메이션 경로를 재실행하여 복구할 수 있음.
정리
스파크 애플리케이션은 드라이버가 애플리케이션 실행 계획(DAG)을 만들고, 클러스터 매니저로부터 자원을 할당받아 여러 Executor를 띄운 뒤, 각 Stage와 Task를 분배하여 처리하는 과정을 거침.
트랜스포메이션과 액션, DAG, 스테이지, 태스크, 셔플 등의 개념을 잘 이해하고 있으면 스파크가 내부적으로 어떻게 동작하는지 정확히 파악할 수 있고, 그에 따라 클러스터 구조 설계나 성능 최적화 방안을 수립하는 데 큰 도움이 됨.
결국, 스파크의 핵심은 (1) DAG 기반의 지연(lazy) 실행, (2) RDD/DataFrame 파티션 단위 병렬 처리, (3) 셔플 경계마다 Stage를 구분, (4) 자원의 유연한 스케줄링으로 정리할 수 있음.
이를 제대로 이해하고 활용한다면, 스파크를 통해 대규모 데이터를 효율적으로 처리하고, 다양한 배포 환경에서 유연하게 운영할 수 있음.
'Data Engineering > Spark' 카테고리의 다른 글
[Spark] Apache ORC 파일 구조 (0) | 2025.03.09 |
---|---|
[Spark] Apache Parquet 파일 구조 (0) | 2025.03.09 |
[Spark] Adaptive Query Execution (0) | 2025.01.17 |
[Spark] Apache Spark의 Job, Stage, Task 구조 (0) | 2025.01.17 |
[Spark] 스파크의 동작 원리 (0) | 2025.01.17 |