Poniższa odpowiedź obejmuje 3 główne aspekty wymienione w tytule - liczbę executorów, pamięć executorów i liczbę rdzeni. Mogą istnieć inne parametry, takie jak pamięć sterownika i inne, do których nie odniosłem się w tej odpowiedzi, ale chciałbym dodać w najbliższej przyszłości.
Sprzęt przypadku 1 - 6 węzłów i każdy węzeł 16 rdzeni, 64 GB pamięci RAM
Każdy moduł wykonujący jest instancją maszyny JVM. Więc możemy mieć wiele programów wykonawczych w jednym Node
Pierwszy 1 rdzeń i 1 GB jest potrzebny dla systemu operacyjnego i demonów Hadoop, więc dostępnych jest 15 rdzeni, 63 GB pamięci RAM dla każdego węzła
Zacznij od wyboru liczby rdzeni :
Number of cores = Concurrent tasks as executor can run
So we might think, more concurrent tasks for each executor will give better performance. But research shows that
any application with more than 5 concurrent tasks, would lead to bad show. So stick this to 5.
This number came from the ability of executor and not from how many cores a system has. So the number 5 stays same
even if you have double(32) cores in the CPU.
Liczba wykonawców:
Coming back to next step, with 5 as cores per executor, and 15 as total available cores in one Node(CPU) - we come to
3 executors per node.
So with 6 nodes, and 3 executors per node - we get 18 executors. Out of 18 we need 1 executor (java process) for AM in YARN we get 17 executors
This 17 is the number we give to spark using --num-executors while running from spark-submit shell command
Pamięć dla każdego executora:
From above step, we have 3 executors per node. And available RAM is 63 GB
So memory for each executor is 63/3 = 21GB.
However small overhead memory is also needed to determine the full memory request to YARN for each executor.
Formula for that over head is max(384, .07 * spark.executor.memory)
Calculating that overhead - .07 * 21 (Here 21 is calculated as above 63/3)
= 1.47
Since 1.47 GB > 384 MB, the over head is 1.47.
Take the above from each 21 above => 21 - 1.47 ~ 19 GB
So executor memory - 19 GB
Numery końcowe - Executory - 17, Rdzenie 5, Pamięć Executora - 19 GB
Sprzęt przypadku 2: ten sam 6 węzłów, 32 rdzenie, 64 GB
5 jest taka sama dla dobrej współbieżności
Liczba executorów dla każdego węzła = 32/5 ~ 6
Więc suma executorów = 6 * 6 węzłów = 36. Ostateczna liczba to 36 - 1 dla AM = 35
Pamięć executorów to: 6 executorów dla każdego węzła. 63/6 ~ 10. Nad głową wynosi 0,07 * 10 = 700 MB. Więc zaokrąglając do 1 GB jako nad głową, otrzymujemy 10-1 = 9 GB
Numery końcowe - Executory - 35, Rdzenie 5, Pamięć Executora - 9 GB
Przypadek 3
Powyższe scenariusze rozpoczynają się od zaakceptowania liczby rdzeni jako ustalonej i przejścia do # wykonawców i pamięci.
Teraz w pierwszym przypadku, jeśli uważamy, że nie potrzebujemy 19 GB, a wystarczy 10 GB, to następujące liczby:
rdzenie 5 Liczba wykonawców dla każdego węzła = 3
Na tym etapie prowadziłoby to do 21, a następnie 19, zgodnie z naszymi pierwszymi obliczeniami. Ale ponieważ myśleliśmy, że 10 jest w porządku (zakładając niewielki narzut), nie możemy zmienić liczby wykonawców na węzeł na 6 (np. 63/10). Ponieważ mamy 6 wykonawców na węzeł i 5 rdzeni, sprowadza się to do 30 rdzeni na węzeł, gdy mamy tylko 16 rdzeni. Musimy więc również zmienić liczbę rdzeni dla każdego modułu wykonawczego.
Więc znowu kalkuluję,
Magiczna liczba 5 dochodzi do 3 (dowolna liczba mniejsza lub równa 5). A więc przy 3 rdzeniach i 15 dostępnych rdzeniach - otrzymujemy 5 wykonawców na węzeł. Więc (5 * 6-1) = 29 wykonawców
Więc pamięć to 63/5 ~ 12. Nadwyżka to 12 * .07 = .84 Więc pamięć executora to 12 - 1 GB = 11 GB
Numery końcowe to 29 executorów, 3 rdzenie, pamięć executora to 11 GB
Dynamiczna alokacja:
Uwaga: Górna granica liczby modułów wykonawczych, jeśli jest włączona alokacja dynamiczna. Oznacza to, że aplikacja Spark może w razie potrzeby pochłonąć wszystkie zasoby. Dlatego w klastrze, w którym działają inne aplikacje, które również potrzebują rdzeni do wykonywania zadań, upewnij się, że robisz to na poziomie klastra. Mam na myśli, że możesz przydzielić określoną liczbę rdzeni dla YARN na podstawie dostępu użytkownika. Więc możesz utworzyć spark_user może być, a następnie podać rdzenie (min / max) dla tego użytkownika. Te limity dotyczą współużytkowania między Spark i innymi aplikacjami działającymi na YARN.
spark.dynamicAllocation.enabled - Gdy jest ustawiona na true - nie musimy wspominać o modułach wykonawczych. Powód jest poniżej:
Liczba parametrów statycznych, które podajemy podczas przesyłania iskry, dotyczy całego czasu trwania zadania. Jeśli jednak pojawi się alokacja dynamiczna, będą to różne etapy, takie jak
Od czego zacząć:
Początkowa liczba executorów ( spark.dynamicAllocation.initialExecutors ) na początek
Ile :
Następnie na podstawie obciążenia (oczekujących zadań), ile żądać. Ostatecznie byłyby to liczby, które podajemy przy wysyłaniu iskry w sposób statyczny. Po ustawieniu początkowych numerów wykonawców przechodzimy do liczb min ( spark.dynamicAllocation.minExecutors ) i max ( spark.dynamicAllocation.maxExecutors ).
Kiedy prosić lub dawać:
Kiedy prosimy o nowych wykonawców ( żądamy spark.dynamicAllocation.schedulerBacklogTimeout ) - przez tak długi czas były oczekujące zadania. więc prośba. liczba wykonawców żądanych w każdej rundzie rośnie wykładniczo w stosunku do poprzedniej rundy. Na przykład aplikacja doda 1 executor w pierwszej rundzie, a następnie 2, 4, 8 i tak dalej w kolejnych rundach. W pewnym momencie pojawia się powyższe maksimum
kiedy udostępniamy executor ( spark.dynamicAllocation.executorIdleTimeout ) -
Proszę, popraw mnie, jeśli coś przeoczyłem. Powyższe jest moim zrozumieniem na podstawie blogu, który udostępniłem, i niektórych zasobów online. Dziękuję Ci.
Bibliografia: