YARN 스케줄링
자원이 제한되어 있고 클러스터는 매우 바쁘고 어떤 애플리케이션은 요청이 처리될 때까지 기다려야 한다.
YARN 스케줄러의 역할은 정해진 정채에 따라 애플리케이션에 자원을 할당하는 것이다. 일반적으로 스케줄링은 난해한 문제고 유일한 최선
의 정책은 있을 수 없다. 이러한 이유로 YARN은 스케줄러와 설정 정책을 사용자가 직접 선택하도록 기능을 제공하고 있다.
스케줄러 옵션
-
FIFO : 애플리케이션을 큐에 하나씩 넣고 제출된 순서에 따라 순차적으로 실행한다. 즉, 큐에 처음으로 들어온 애플리케이션 요청을 먼저 할당하고, 이 요청을 처리한 후 큐에 있는 다음 애플리케이션 요청을 처리하는 방식으로 순차적으로 실행한다. 간단하지만 공유 클러스터 환경에서는 적합하지 않다. 대형 애플리케이션이 수행될 때는 클러스터의 모든 자원을 점유해버릴 수 있기 때문에 다른 애플리케이션은 자기 차례가 올 때까지 계속 대기해야 한다. 공유 클러스터 환경에서는 캐퍼시티 스케줄러나 페어 스케줄러를 사용하는 것이 더 좋다. 이 두 스케줄러는 장시간 수행되는 잡을 계속 처리하는 동시에 작은 비정형 질의도 중간에 실행하여 적당한 시간 내에 사용자가 결과를 얻을 수 있도록 허용한다.
-
capacity : 작은 잡을 제출되는 즉시 분리된 전용 큐에서 처리해준다. 물론 해당 큐는 잡을 위한 자원을 미리 예약해두기 때문에 전체 클러스터의 효율성은 떨어진다. 또한 대형 잡은 FIFO 스케줄러보다 늦게 끝나게 된다.
-
Fair : 실행 중인 모든 잡의 자원을 동적으로 분배하기 때문에 미리 자원의 가용량을 예약할 필요가 없다. 대형 잡이 먼저 시작되면 이때는 실행 중인 잡이 하나밖에 없기 때문에 클러스터의 모든 자원을 얻을 수 있다. 대형 잡이 실행되는 도중에 작은 잡이 추가로 시작되면 페어 스케줄러는 클러스터 자원의 절반을 이 잡에 할당한다. 따라서 각 잡은 클러스터의 자원을 공평하게 사용할 수 있게 된다.
두 번째 잡이 시작된 후 공평하게 자원을 받을 때까지 약간의 시간차가 있을 수 있다는 점을 유의해야 한다. 첫 번째 잡이 사용하고 있는 컨테이너의 자원이 완전히 해제될 때까지 기다려야 하기 때문이다. 작은 잡이 완료된 후에는 자원 요청이 더 이상 없기 때문에 대형 잡은 클러스터의 전체 가용량을 다시 확보할 수 있게 된다. 전체적으로 보면 클러스터의 효율성도 높고 작은 잡도 빨리 처리되는 효과가 있다.
Capacity Scheduler
Capacity Scheduler는 회사의 조직 체계에 맞게 하둡 클러스터를 공유할 수 있다. 각 조직 전체 클러스터의 지정된 가용량을 미리 할당받는다. 각 조직은 분리된 전용 큐를 가지며 클러스터 가용량의 지정된 부분을 사용하도록 설정할 수 있다. 큐는 1단계 이상의 계층 구조로 분리될 수 있으므로 각 조직은 조직에 속한 서로 다른 사용자 그룹 사이에도 클러스터의 가용량을 공유하도록 할 수 있다. 단일 큐 내부에 있는 애플리케이션들은 FIFO 방식으로 스케줄링 된다.
하나의 단일 잡은 해당 큐의 가용량을 넘는 자원은 사용할 수 없다. 그러나 큐 안에 다수의 잡이 존재하고 현재 가용할 수 있는 자원이 클러스터에 남아 있다면 캐퍼시티 스케줄러는 해당 큐에 있는 잡을 위해 여분의 자원을 할당할 수 있따. 물론 이렇게 하면 큐의 가용량을 초과하게 된다. 이러한 방식을 큐 탄력성이라고 한다.
일반적인 운용에서 캐퍼시티 스케줄러는 컨테이너를 선점하기 위해 강제로 죽이는 방법을 사용하지는 않는다.(하지만 리소스 매니저는 애플리케이션이 컨테이너를 반환하도록 하여[가용량의 균형을 유지하기 위해] 캐퍼시티 스케줄러가 우선권을 선점하게 할 수 있다.) 그러므로 요청한 가용량에 미달한 큐가 있으면 필요한 요청은 늘어난다. 이 때 다른 큐의 컨테이너가 완료되어 자원이 해제된 경우에만 해당 큐에 가용량을 돌려준다. 다른 큐의 가용량을 너무 많이 잡아먹지 않도록 큐에 최대 가용량을 설정하는 방법으로 이러한 문제를 해결할 수 있다. 이것이 바로 큐 탄력성의 단점이다.
root
|_ prod
|_ dev
|_ eng
|_ science
capacity-scheduler.xml 이라는 캐퍼시티 스케줄러 설정 파일의 예며 root 큐 아래에 prod와 dev라는 두 개의 큐가 있고 각각 40%와 60%의 가용량을 가진다. 각 큐는 yarn.scheduler.capacity.<queue-path>.<sub-property> 와 같은 형식의 설정 속성을 정의한 후 설정할 수 있다. 여기서 <queue-path> 에는 root.prod와 같이 큐의 계층 구조를 정의하면 된다.
Capacity Scheduler 큐 배치
어플리케이션을 큐에 배치하는 방법은 어플리케이션의 종류에 따라 달라진다. 예를 들어 맵리듀스는 mapreduce.job.queuename 속성에 원하는 큐의 이름을 지정할 수 있다. 지정한 이름의 큐가 없다면 제출 시점에 에러가 발생한다. 큐를 지정하지 않으면 어플리케이션은 기본 큐인 default에 배치된다.
캐퍼시티 스케줄러는 계층 구조로 된 전체 이름을 인식할 수 없기 때문에 게층 이름의 마지막 부분만 큐의 이름으로 사용해야 한다.
폐어 스케줄러 설정
폐어 스케줄러는 실행 중인 모든 애플리케이션에 동일하게 자원을 할당한다. 동일한 큐에 있는 어플리케이션에 공평하게 자원을 할당하는 방법을 보여주고 있다. 하지만 균등 공유는 큐 사이에만 실제로 적용된다.
사용자 A, B 가 있다고 가정했을 때 두 사용자는 자신의 큐를 가지고 있다. 사용자 A가 잡을 하나 시작하면 아직 B의 요청이 없기 때문에 모든 자원을 점유 할 수 있다. A의 잡이 끝나기 전에 사용자 B가 잡을 하나 시작하면 앞에서 본 것과 같이 한 동안 각 잡은 전체 자원의 절반씩을 사용하게 된다. 사용자 A와 B의 잡이 여전히 실행되는 중에 사용자 B가 두 번째 잡을 시작하면 이 잡은 B의 다른 잡과 자원을 공유하게 된다. 따라서 사용자 B의 각 잡은 전체 자원의 4분의 1씩을 사용할 수 있고 사용자 A의 잡은 계속 절반을 사용하게 된다. 결국 자원은 사용자 사이에만 균등하게 공유된다고 할 수 있다.
사용할 스케줄러는 yarn.resourcemanager.scheduler.class 속성에 설정한다. 기본 스케줄러는 캐퍼시티 스케줄러지만(CDH와 같은 일부 하둡 배포판의 기본 스케줄러는 폐어 스케줄러다.) 원하는 스케줄러를 지정하고 싶으면 yarnsite.xml 파일의 yarn.resourcemanager.scheduler.class 속성에 스케줄러의 전체 클래스 이름을 org.apache.hadoop.yarn.server.resourcemanager.scheduler.fail.FailScheduler와 같이 지정하면 된다.
Fair Scheduler 큐 설정
폐어 스케줄러는 클래스경로에 있는 fair-scheduler.xml 이라는 할당 파일에 원하는 속성을 설정한다. 할당 파일의 이름은 yarn.scheduler.fail.allocation.file 속성을 지정하여 변경할 수 있다. 할당 파일이 없으면 페어 스케줄러는 앞에서 설명한 것처럼 작동한다. 즉, 각 어플리케이션은 해당 사용자의 이름의 큐에 배치된다. 사용자 큐는 해당 사용자가 처음 어플리케이션을 제출할 때 동적으로 생성된다.
또한 각 큐별 설정도 할당 파일에서 정의한다. 페어 스케줄러는 캐퍼시티 스케줄러에서 지원했던 것과 비슷한 계층적인 큐 설정도 가능하다.
가중치를 설정할 때는 기본 큐와 사용자별 큐와 같이 동적으로 생성되는 큐를 모두 고려해야 한다. 할당 파일에 가중치를 지정하지 않으면 각 큐는 기본으로 1의 가중치를 갖는다.
각 큐에 서로 다른 스케줄링 정책을 설정할 수 있다. 기본 정책은 최상단의 defaultQueueSchedulingPolicy 항목에 설정한다. 이를 생략하면 페어 스케줄링이 적용된다. 이름과 상관없이 페어 스케줄러는 큐에 FIFO 정책도 지원한다. 우성 자원 공평성(DRF) 정책도 사용할 수 있다.
큐 배치
페어 서케줄러는 어플리케이션을 큐에 할당할 때 규칙 기반 시스템을 이용한다. queuePlacementPolicy 항목은 규칙 목록을 포함하고 있는데, 맞는 규칙이 나올 때까지 순서대로 시도한다. 첫 번째 규칙인 specified는 지정된 큐에 어플리케이션을 배치한다. 큐를 지정하지 않았거나 지정된 큐가 존재하지 않으면 이 규칙은 적용되지 않고 그 다음에 나오는 규칙으로 다시 시도한다. primaryGroup 규칙은 사용자의 유닉스 그룹의 이름을 가진 큐에 어플리케이션을 배치한다. 큐가 존재하지 않으면 큐를 자동으로 생성하는 대신 다음 규칙으로 넘어간다. default 규칙은 catch-all로, 항상 dev.eng 큐에 어플리케이션을 배치한다.
queuePlacementPolicy 항목은 완전히 생략할 수 있으며, 이 때 다음과 같이 specified 규칙이 기본으로 적용된다. 다시 말해 큐를 명시적으로 지정하지 않으면 사용자 이름의 큐를 사용한다.
모든 어플리케이션을 동일한 큐에 저장하는 단순한 큐 배치 정책도 있다. 이 정책을 사용하면 사용자가 아닌 모든 어플리케이션에 균등한 자원 공유가 가능하다.
또한 yarn.scheduler.fair.user-as-default-queue 속성을 false로 설정하면 할당 파일을 사용하지 않고도 정책을 지정할 수 잇다. 이 때 사용자별 큐가 아닌 default 큐에 모든 어플리케이션이 배치된다. 추가로 yarn.scheduler.fair.allow-undeclared-pools 속성을 false로 설정하면 사용자는 동적으로 큐를 생성할 수 없게 된다.
선점
바쁘게 돌아가는 클러스터에서는 빈 큐에 잡이 제출되더라도 클러스터에서 이미 실행되고 있는 다른 잡이 자원을 해제해주기 전까지 잡을 시작할 수 없다. 잡의 시작 시간을 어느 정도 예측 가능하게 만들기 위해 페어 스케줄러는 선점(preemption)이라는 기능을 제공한다.
선점은 스케줄러가 자원의 균등 공유에 위배되는 큐에서 실행되는 컨테이너를 죽일 수 있도록 허용하는 기능으로, 큐에 할당된 자원은 균등 공유 기준을 반드시 따라야 한다. 중단된 컨테이너는 반드시 다시 수행되어야 하므로 클러스터의 전체 효율은 떨어지게 된다는 점을 유의해야 한다.
선점은 yarn.scheduler.fair.preemption 속성을 true로 설정하여 전체적으로 활성화시킬 수 있다. 최소 공유와 균등 공유 등 두 개의 선점 타임아웃 설정이 있다. 초 단위로 설정하며 타임아웃의 기본값은 설정되어 있지 않다. 컨테이너의 선점을 허용하기 위해서는 두 개의 설정 중 반드시 하나는 지정해야 한다.
큐가 최소 보장 자원을 받지 못한 채 지정된 최소 공유 선점 타임아웃(minimum share preemption timeout)이 지나면 스케줄러는 다른 컨테이너를 선취할 수 있다. 모든 큐의 기본 타임아웃은 할당 파일의 최상위 항목인 defaultMinSharePreemptionTimeout으로 설정할 수 있으며, 큐를 기준으로 지정하고 싶으면 각 큐의 minSharePreemptionTimeout 항목에 설정하면 된다.
유사하게, 큐가 균등 공유의 절반 이하로 있는 시간이 균등 공유 선점 타임아웃(fair share preemption timeout)을 초과하면 스케줄러는 다른 컨테이너를 선취할 수 있다. 모든 큐의 기본 타임아웃은 할당 파일의 최상위 항목인 defaultFairSharePreemptionTimeout으로 설정할 수 있으며, 큐를 기준으로 지정하고 싶으면 각 큐의 fairSharePreemptionTimeout 항목에 설정하면 된다. 임계치의 기본값은 0.5인데, defaultFairSharePreemptionThreshold(전체)와 fairSharePreemptionThreshold(큐 기준) 속성을 설정하면 그 값을 변경할 수 있다.
지연 스케줄링
YARN 의 모든 스케줄러는 지역성 요청을 가장 우선시 한다. 바쁜 클러스터에서 어떤 어플리케이션이 특정 노드를 요청하면 요청하는 시점에 그 노드에 다른 컨테이너가 실행되고 있을 가능성이 높다. 이런 상황에서는 지역성 요구 수준을 조금 낮춘 후 동일한 랙에 컨테이너를 할당하는 방법도 있다. 하지만 실제로 확인해보면 몇 초보다는 길지만 조금만 기다리면 요청한 지정 노드에서 컨테이너를 할당받을 수 있는 기회가 급격하게 증가한다는 사실을 알 수 있다. 또한 이렇게 되면 클러스터의 효율성도 높아지게 된다. 이러한 기능을 지연 스케줄링(delay scheduling)이라고 부르며, 캐퍼시티 스케줄러와 페어 스케줄러는 모두 이러한 기능을 제공하고 있다.
YARN의 모든 노드 매니저는 주기적(기본은 1초)으로 리소스 매니저에 하트비트 요청을 보낸다. 하트비트를 통해 노드 매니저가 실행 중인 컨테이너의 정보와 새로운 컨테이너를 위한 가용한 자원에 대한 정보를 주고받는다. 따라서 각 하트비트는 어플리케이션 이 실행할 컨테이너를 얻을 수 있을 중요한 스케줄링 기회가 된다.
지연 스케줄링을 사용할 때 스케줄러는 처음 오는 스케줄링 기회를 바로 사용하지는 않는다. 대신 지역성 제약 수준을 낮추기 전에 허용하는 스케줄링 기회의 최대 횟수까지 기다린 후 그 다음에 오는 스케줄링 기회를 잡는다. 따라서 각 하트비트는 어플리케이션이 실행할 컨테이너를 얻을 수 있을 중요한 스케줄링 기회가 된다.
캐퍼시티 스케줄러의 지연 스케줄링은 yarn.scheduler.capacity.node-locality-delay 속성에 양의 정수를 지정하는 방식으로 설정된다. 이 값은 스케줄링 기회의 횟수를 의미하며, 지연 스케줄링이 실행되면 동일한 랙의 다른 노드를 찾도록 제약 수준을 낮추는 기회를 놓치게 된다.
페어 스케줄러는 스케줄링 기회의 횟수를 클러스터 크기의 비율로도 정할 수 있다. 예를 들어 yarn.scheduler.fair.locality.threshold.node 속성의 값이 0.5로 설정되어 있으면 스케줄러는 클러스터 전체 노드의 절반에 해당하는 스케줄링 기회가 올 때까지 기다린 후 동일한 랙의 다른 노드를 허용한다. 이와 대응하는 속성으로는 요청한 랙 대신 다른 랙을 허용하는 임계치를 설정하는 yarn.scheduler.fair.locality.threshold.rack 속성이 있다.
우선 자원 공평성
메모리와 같은 단일 유형의 자원을 분배할 때는 가용량이나 공평성의 개념을 결정하는 것은 어렵지 않다. 두 사용자가 어플리케이션을 실행할 때 메모리양을 기준으로 두 어플리케이션을 쉽게 비교할 수 있다. 하지만 다수의 자원이 있으면 일이 매우 복잡해진다.
YARN의 스케줄러가 이러한 문제를 처리하는 방법은 각 사용자의 우세한 자원을 확인한 후 이를 클러스터 사용량의 측정 기준으로 삼는 것이다. 이러한 접근 방법을 우성 자원 공평성(Dominant Resource Fairness, DRF)라고 부른다.
클러스터의 전체 CPU는 100개고 10TB의 메모리를 가지고 있다고 가정하자. 어플리케이션 A는 cpu 2개와 300GB 메모리의 컨테이너를 , 어플리케이션 B는 CPU 6개와 100GB 메모리의 컨테이너를 각각 요청했다. 어플리케이션 A의 요청은 전체 클러스터의 2%와 3%이므로 메모리의 비율(3%)이 CPU(2%) 보다 높다. 어플리케이션 B의 요청은 6% 와 1%이므로 CPU가 더 우세하다. 따라서 컨테이너 B의 요청을 우세 자원을 기준으로 비교해보면 A보다 두 배(6% vs 3%)나 높다. 따라서 균등 공유 정책을 따르면 컨테이너 A는 B의 절반에 해당하는 자원을 할당 받게 될 것이다.
DRF 기능은 기본적으로 비활성화되어 있다. 따라서 자원을 계산할 때 CPU는 무시되고 메모리만 고려된다. 캐퍼시티 스케줄러의 drf 기능은 capacityscheduler.xml 파일에 있는 yarn.scheduler.capacity.resource-calculator 속성에 org.apache.hadoop.yarn.util.resource.DominantResourceCalculator 를 지정하면 활성화 된다.
페어 스케줄러의 DRF는 할당 파일의 최상위 항목인 defaultQueueSchedulingPolicy를 drf로 정의하면 활성화된다.