Spark Executor, Cores, Memory 설정 - Test Case 적용

2021. 7. 2. 17:12Bigdata Platform/Spark

필자는 Spark SQL을 활용한 솔루션을 운용중인데, Spark에는 실행시 다음과 같은 옵션들이 있다.

 

참고) Spark란? (출저 : 위키독스)

https://wikidocs.net/26513

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

 

예를 들어, 하나의 Spark Shell을 실행시키기 위한 옵션을 부여한다고 가정했을때,

spark-shell --master yarn --num-executors 30 --executor-cores 2 --executor-memory 10G --queue default

다음과 같은 옵션들을 넣게 된다.

 

 

이 말은 즉슨,

--master yarn # yarn을 리소스 관리자로 사용, local모드 혹은 docker기반의 mesos모드 가능
--num-executors 30 # 실행하고자 하는 작업의 executor 갯수
--executor-cores 2 # 실행하고자 하는 작업의 executor당 코어수
--executor-memory 10G # 실행하고자 하는 작업의 executor당 메모리 수
--queue # 작업이 할당될 yarn 리소스 큐

이 설정 값들의 적용 순서는

1) SparkConf

2) spark-shell

3) spark-submit

4) spark-defaults.conf

가 우선순위가 되어서 작업이 실행된다. 

 

 

Spark 작업들을 실행할때 이와 같은 설정을 제외한 (ex. 네트워크 대역폭, Disk R/W등)의 성능은 우리가 조정할 수

없으므로, 작업을 실행할때 최적화를 위한 설정들은 해당 값들로 수행이 된다.

 

 

그러면 설정을 어떻게 해주어야 가장 효율적으로 (자원을 최소로, 속도는 최대로) 사용할 수 있을까? 가 

우리의 주요 Issue가 될것이다.

 

이를 확인하기 위해 Cloudera Documentation을 참고하였다.

https://blog.cloudera.com/how-to-tune-your-apache-spark-jobs-part-2/

 

How-to: Tune Your Apache Spark Jobs (Part 2) - Cloudera Blog

Editor’s Note, January 2021: This blog post remains for historical interest only. It covers Spark 1.3, a version that has become obsolete since the article was published in 2015. For a modern take on the subject, be sure to read our recent post on Apach

blog.cloudera.com

< Cloudera의 Sample Case >

  • 서버는 총 6대 (6node)
  • 서버당 의 CPU(Core)는 16GB
  • 서버당 Memory는 64GB -> 여유 메모리를 제외하여 63GB 사용
  • 6대 노드는 총 96 cores와  378GB Memory를 가짐

 

1. --num-executors 6 --executor-cores 15 --executor-memory 63G

  • executor는 총 6개
  • executor당 15개의 Core 사용
  • executor당 63GB Memory 사용
  • 이는 잘못된 접근, 왜냐?

 

  •  각 서버의 메모리가 64GB 시스템에서 OS나 NodeManager가 사용해야하는 메모리 여유분이 있어야 하기 때문에,   하나의 executor가 메모리를 63GB까지 사용하는 것은 어려움.
  •  한 executor에 15개의 core를 사용하면 HDFS I/O throughput이 낮아지는 등, 성능 저하를 유발할 수 있음.
  •  그래서, executor core의 수는 1보다는 크게, 그리고 5 보다는 작게 설정하는 것이 좋음.

 

2. --num-executors 17 --executor-cores 5 --executor-memory 19G

  • executor는 총 17개
  • executor당 5개의 Core 사용
  • executor당 19GB Memory 사용
  • 이렇게 구성하면 모든 노드에서 Executor가 3개 생성되어 Application Master가 사용하는 Executor를 제외하면
  •  총 2개의 Executor가 실행됨.
     memory는 (노드당 63/3 Executor) = 21.21 * 0.07 = 1.47.21 – 1.47 ~ 19로 계산되었음.

 

 

결론적으로, 

1) --executor-cores의 개수를 1~5 사이값으로 정한다.

 

2) --num-executors를 구한다.

  (--num-executors ) x (--executor-cores) < 서버 전체 CPU 수

 

3) 노드 하나당 할당할 수 있는 executor 개수를 구한다.

   --num-executors / 전체 노드 개수 = 노드 당 executor 개수

 

4) --executor-memory를 구한다.

   노드당 Memory / 노드당 executor 개수 = --executor-memory

라는 공식이 도출될 수 있다고 한다.

 

 

그러면 이에 따라 나는 13노드에 64Core, 512GB 서버를 통해 계산해 보았다.

(하이퍼스레딩과 OS 영역등 기타 사용량 제외)

 

  • 노드당 코어수 : 31
  • 노드당 메모리수 : 255
  • 31 x 13 = 402 (코어 x 노드수)
  • 402 / 2 = 201 (코어를 2~5까지 모두 계산하여 executor갯수를 코어에 맞춰서 나눠서 구함)

           / 3 = 134

           / 4 = 100

           / 5 = 80

  • 255 / 201 = 1.27 (메모리수, 대충 반올림하였음)

           / 134 = 1.9

           / 100 = 2.55

           / 80 = 3

 

이 설정을 참고하여 Spark-SQL 작업을 실행시켰는데, 대략 결과치는 1억2천만건 정도의 Row가 생성되는 프로그램이다.

--num-executors 100 --executor-cores 4 --executor-memory 2.5G 

 

>>> 실행 결과 : 20분 소요

 

 

그러면 코어를 줄이고 메모리를 늘리면 어떨까?

--num-executors 200 --executor-cores 2 --executor-memory 1.3G 

 

>>> 실행 결과 : 22분 소요

 

 

환경에 따라 다를수는 있겠지만, Executor수를 늘리니 오히려 작업이 느려졌다. 그런데 다른 작업에서는 오히려 Core를 낮추고 Executor를 늘리는 것이 더 빨라졌다. 

 

 

그러면 권고 설정보다 메모리를 더 많이 부여하면 어떻게 될까?

--num-executors 80 --executor-cores 2 --executor-memory 10G 

 

>>> 실행 결과 : 17분 소요

 

 

이때까지 설정한 Properties중에 가장 빠르게 실행되었다.

왜 이런 현상이 발생하는 것일까?

 

 

Spark의 구조를 더 파악해봐야겠지만, 깃허브에서 해당 링크를 참고하였다.

https://spoddutur.github.io/spark-notes/distribution_of_executors_cores_and_memory_for_spark_application.html

여기서 말하는 것은 light한 executor는 1core당 설정을, heavy한 executor는 1노드당 설정을 기준으로 하여

결론적으로는 균형있는 executor수를 조정해야 한다는것이다.

 

결국 나의 Case에서도, 내가 map하거나 join할 테이블이 얼마나 heavy한지,

이 executor가 얼마나 한번에 처리해야할 task수가 많은지 적은지에 따라서 결과가 달라질 수 있다는 점을 파악하였다.

 

 

결론은 최적화된 옵션은 환경에 따라 다르고, task나 table에 따라서 다를 수 있으며,

Test Case를 여러번 적용해야 찾을 수 있을 것 같다.

 

그러나,

1) 계산식을 통해 Core, memory, executor 수를 먼저 계산

2) 작업하고자 하는 node(서버)의 사용량과 서버 대수를 계산

3) 실제 task가 적용할 table의 구조와 row수를 파악

을 기본적으로 해야만이 작업할 task에 대한 최적치 값을 산출할 수 있다고 생각한다.