1. Intorduction
클러스터 관리자의 목적은 논리적 코드를 실제 실행할 수 있도록 물리적 자원을 할당하는 것이다. 물리적 자원 할당에는 다양한 방식이 있지만, 클러스터 관리자는 단일 머신상에서의 운영 시스템을 일반화한 것이며, 단일 머신상의 문제와 동일한 수많은 문제를 해결해야 한다. 운영 시스템의 핵삼 개념을 이해한다면 클러스터 관리자 툴의 필요에 흥미를 느끼게 될 것이다. 왜냐하면, 결국 동일한 개념이 분산 생태계에서 다양한 방식으로 구현된 것 이기 때문이다.
단일 머신에서 운영 시스템(OS)의 역할은 해당 머신의 물리적 하드웨어와 이 하드웨어상에서 실행되는 소프트웨어 사이의 인터페이스라고 정의할 수 있다. 이 인터페이스는 의사 소통을 위한 언어의 정의, 태스크와 프로세스들의 스케줄링, 이들 프로세스에 대한 자원 할당 등과 같은 수많은 어려운 일들이 관련되어 있다. OS에서 추상화라는 "운영 시스템의 사용자와 궁극적으로 소프트웨어를 실행하는 하드웨어 간의 근원적인 복잡함을 숨겨 주는 간단한 인터페이스"를 만든다.
이와 같은 추상화 덕분에, 운영 시스템을 사용하는 사람이 실행하고자 하는 프로그램의 자원을 어떻게 획득할 것인지를 명확하게 이해할 필요가 없어졌다. 수많은 애플리케이션들이 동시에 실행되는 현대의 시스템에서도 사용자들은 이들 애플리케이션 사이에 자원들이 어떻게 공유되는지에 대해 걱정할 필요가 없다. 또한 사용자들은 애플리케이션의 실행 순서나 동시에 자원을 어떻게 접근해야 하는지를 명시적으로 정의할 필요도 없다. 따라서 , 모든 사용자들은 그들이 제출한 애플리케이션이 실행되어 완료되도록, 그리고 정확한 결과를 만들어 내는 일에만 관심을 가지면 된다.
이러한 환경하에서, 운영 시스템은 스케줄링과 자원 관리라는 아주 복잡한 움직임을 처리한다. OS 내부에서 실행되고 있는 프로세스는 실행에 필요한 데이터를 저장하기 위해서 메모리가 필요하며, 메모리에 넣기에는 너무 큰 데이터를 읽어 들이고 기록하기 위해 하드 드라이브에 접근해야 하며, 필요한 연산 작업을 수행하기 위해 CPU에 접근해야 한다. 모든 프로세스가 실행될 수 있도록 만들기 위해, OS는 스케줄러를 실행하여 모든 프로세스에 균등한 CPU 실행 시간 획득을 보장한다.
프로세스에서 필요한 데이터를 가지도록 하기 위해, OS는 각 프로세스에 특정 메모리 블럭을 할당하는 태스크를 처리하며, 개별 프로세스가 디스크에 데이터를 읽고 쓸 수 있도록 인터페이스를 제공한다. OS가 물리적 디바이스들을 어떻게 관리하는지를 정의하는 low level 인터페이스는 해당 자원을 요청하는 개별 프로그램에서는 숨겨져있다.
OS는 이 모든 태스크를 관리하며, 이외에도 여러 가지 작업을 수행한다. 이와 같은 프로세스는 단일 머신에서도 상당히 어려운 기술이지만 분산환경에서는 더더욱 복잡하다. 분산 환경에서는 부가적인 몇 가지 문제를 처리해야 하기 때문이다.
- 확장성(scalability) : 하드웨어 자원을 추가하는 경우, 특히 네트워크 상에서 이와 같은 일은 시스템에 복잡성을 더하게 된다. 잠재적으로 컴포넌트 간의 시간 지연이 추가되며, 네트워크 상에 오버헤드도 함께 발생된다.
- 시스텀의 영속성(durability) : 모든 하드웨어에는 장애가 발생할 가능성이 존재하며, 시스템 내에 하드웨어가 많아진다면, 작동 중인 시간 중 어느 떄라도 컴포넌트 중 하나에 장애가 발생할 가능성이 높아진다. 단일 머신에서는 컴포넌트들이 서로 소통 하지 못하는 일이 발생할 가능성이 낮지만, 분산 환경에서는 네트워크로 연결되어 네트워크 장애 혹은 머신들간에 발생할 수 있는 장애 상황에서도 복구할 수 있도록 부가적인 문제를 처리해야만 한다.
- 가용성(availability) : 개별 컴포넌트에 장애가 발생한 상황에서도 서비스의 중단 없이 시스템이 운영되는 상태를 고가용성 이라고 부른다. 따라서 시스템이 완전히 붕괴되지 않도록 부가적인 문제를 처리해야만 한다.
앞서 설망한 여러 부가적인 문제로 분산 환경에서 자원 관리는 매우 복잡한 문제이며, 필수 불가결한 상황이며, 이를 해결하기 위한 클러스터 관리자는 필요하다고 볼 수 있다. 클러스터 관리자는 클러스터에서 실행되는 프로세스에 자원의 항당과 복원을 조율함으로써 모든 애플리케이션이 필요한 자원을 획득할 수 있도록 보장한다. (클러스터 관리자와 OS는 이런 관점에서 유사하다고 볼 수 있다.)
일반적으로, 클러스터 관리자는 개별 머신에 있는 운영 시스템을 대체하지는 않는다. 그 대신, 클러스 관리자는 다수 머신에 걸친 운영시스템을 emulate하며, 개별 머신에서는 로컬 자원을 더욱 세밀하게 통제하고 물리적 하드웨어에 접근하기 위해 로컬 운영 시스템을 활욜한다. 그러므로 클러스터 관리자는 주로 fair resource scheduling에 관한 좀 더 일반적인 태스크를 처리한다.
동일한 방식으로 단일 머신에 있는 OS는 다수 프로세스가 필요한 자원을 확보하여 동시에 실행되도록 보장하며, 클러스터 관리자는 클러스터 내에서 실행되는 다수 애플리케이션이 최대한 동시에 실행되거나, 최악의 상황이더라도 모든 애플리케이션의 실행이 종료되기까지 필요한 자원을 확보할 수 있도록 보장한다.
이번 포스팅에서는 스파크 작업을 실행하기 위해 필요한 자원을 스파크에서 어떻게 획득하고 활용하는지 살펴볼 것 이다. 추후 알게 되겠지만, 스파크 작업이 실행될 때 스파크는 동시 실행되는 다수의 물리적 프로세스를 생성하며, 각각의 프로세스가 수행되기 위해서는 CPU와 메모리가 필요하다. 해당 클러스터 관리자는 이러한 자원의 묶음을 동적으로 이드르 프로세스에 할당하고, 완료된 후 이어지는 연산에서 이들 자원을 사용 가능하도록 만들 책임을 가진다.
2. 스파크 컴포넌트
주요 컴포넌트에 대해 살펴볼 필요가 있다. 그 이유는 클러스터 관리 툴을 경유해 컴포넌트가 수행되기 때문이다. 애플리케이션을 실행하기 위해서는 스파크 생태계 내에서 수 많은 entity가 협력해야 한다. 상위 수준에서 보면, 모든 작업은 driver에서 시작된다. 기본적으로 driver는 cluster 내에서 다른 entity들과 연결을 유지하며, cluster 내의 worker node에서 실행될 수 있도록 작업을 submit하는 감독 역할을 한다. worker는 하나 이상의 task를 가지고 있으면서 스파크 작업으로 만들어지는 해당 코드를 실질적으로 수행하는 executor 프로세스를 교대로 실행한다. 이러한 entity는 standalone, YARN, Mesos 등의 클러스터 관리자가 인식할 수 있는 범위는 아니다.
2-1. Driver
dirver는 스파크 애플리케이션의 기동과 실행 중인 애플리케이션의 관리를 책임지는 프로세스다. 특히 모든 worker node와 연결을 유지하는 entity이며, 스파크 애플리케이션의 논리 코드를 클러스터에서 실행될 수 있도록 물리적 명령으로 변환한다. 마스터 프로세스로서, 드라이버는 주어진 수많은 일을 수행한다.
가장 먼저, driver는 spark context를 유지, 관리한다. Spark Context는 executor에 task를 할당하고, accumulator나 broadcast 변수 등과 같은 내부 construct를 유지, 관리도 하는 프로그램 state이다. 이와 같은 context는 애플리케이션의 설정값과 사용할 수 있는 자원 등을 추적한다. 그 다음, driver는 스파크 애플리케이션을 실행시키기 위한 자원을 클러스터 관리자에게 요청하기 위해 클러스터 관리자와 소통한다. 이들 자원이 사용 가능하다면, driver는 할당된 worker node로 제출할 스파크 애플리케이션의 논리 코드를 기반으로 실행 계획을 생성한다. 실행 계획 자체는 action과 transformation으로 구성되는 방향성 비순환 그래프(DAG, Directed Acyclical Graph)다. 스파크는 데이터 이동이 최소화되도록 DAG를 최적화하며, DAG 스케줄러라고 알려진 내부 construct가 실제 DAG를 개별 stage로, 그 다음에는 task로 분할한다. (stage는 RDD에 포함되어 있는 데이터에 적용해야하는 변환의 집합이다.)
위 사진은 스파크의 워크플로로 1) 사용자가 Operator DAG를 생성한다. 2) DAG scheduler가 DAG를 해석하여 task로 구성된 stage로 변환한다. stage는 향후 taskset로 묶인다. 3) Task Scheduler는 클러스터 관리툴을 이용하여 개별 머신에 task를 할당한다. 실패한 태스크는 다시 제출된다. 4) wocker가 task를 실행하고, 캐시로 만들어진 데이터를 저장한다.
스파크에서 데이터는 partition으로 나눠지는데, 이는 데이터세트가 하나로 묶여 있더라도 스파크 전체 데이터세트를 한 번에 처리하지 않는다는 뜻이다. 그 대신, 이 데이터는 작은 조각으로 나눠지며, 각 조각들은 병렬도를 높이기 위해 개별적으로 처리된다. DAG scheduler가 하나의 stage를 task로 작게 나누는 경우 RDD의 모든 파티션에 대한 새로운 task를 각각 만들어 내며, 비록 개별 task가 동일한 연산을 수행하더라도 대상 데이터는 서로 다른 조각의 데이터다. 개별 task의 실행 결과는 나중에 하나의 RDD로 통합된다.
DAG scheduler는 DAG의 부분 집합을 task로 나누며, 이후 다른 entity인 Task Scheduler가 클러스터 내에서 이들 task의 스케줄을 처리한다. Task Scheduler는 자원 및 locality의 제약사항을 인지하고 있으며, 이에 따라 executor에 task를 할당한다. Task Scheduler가 한 task를 처리할 위치를 결정하면, 모든 DAG 변환은 Transformation closure와 동일하게 serialize되며, 해당하는 executor가 위치하고 있는 worker node로 네트워크를 통해 전달되고, 그 이후 실제 필요한 연산이 실행된다. Task Scheduler는 실패하거나 실행하지 못한 Task를 재시작하는 일도 책임진다.
2-2. Worker와 Executor
스파크 클러스터에서 worker node는 executor와 task를 실행하는 물리적 머신이다. 사용자들은 절대 명시적으로 worker node와 상호 작용하지는 않지만, 그 아래에서 클러스 관리자는 개별 worker와 의사 소통과 자원 할당을 처리한다. 개별 worker는 사용할 수 있는 고정된 자원이 있으며, 이들 자원은 명시적으로 클러스터 관리자에 할당되어 있다.
worker node에서 애플리케이션이 다수 실행될 수 있는데, 클러스터 관리자가 활용 가능한 자원에 제약을 두는 작업 덕분에 multi-tenancy와 다양한 프로그램의 동시 수행이 가능해진다.
- 각 worker는 하나 이상의 executor 프로세스를 실행한다.
- 각 executor는 CPU, memory, disk 접근을 캡슐화한다.
- 개별 executor는 task의 thread를 생성하고, 로컬 데이터에 접근하기 위해서 메모리상에 RDD 추상화를 유지한다.
스파크 클러스터에서 각 워커 노드는 하나 이상의 스파크 executor를 실행한다.
executor는 스파크 프로그램을 구성 가능한 형태로 실행할 수 있도록 만들어 주는 추상화다. 각 executor는 하나의 JVM을 실행하며, 그에 따라 자체 고정 할당된 자원을 가진다. 전체 executor의 개수와 메모리나 CPU 코어 등과 같은 executor에 할당되어 있는 자원의 개수는 스파크에서 튜닝 가능한 파라미터이며, 실행에 지대한 영향을 미친다.
REFRENCE
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."