Jak dostroić numer executora, rdzenie i pamięć executora?

84

Od czego zaczynasz strojenie wyżej wymienionych parametrów. Czy zaczynamy od pamięci executorów i uzyskujemy liczbę executorów, czy zaczynamy od rdzeni i uzyskujemy numer executora. Podążyłem za linkiem . Mam jednak pomysł na wysokim poziomie, ale nadal nie wiem, jak i od czego zacząć i dojść do ostatecznego wniosku.

Ramzy
źródło

Odpowiedzi:

210

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:

Ramzy
źródło
2
Czytałem gdzieś jest tylko jeden moduł wykonawczy na węzeł w trybie autonomicznym, jakiś pomysł na to? Nie widzę tego w twojej odpowiedzi.
jangorecki
2
W samodzielnym klastrze domyślnie otrzymujemy jeden moduł wykonawczy na pracownika. Musimy bawić się spark.executor.cores, a pracownik ma wystarczającą liczbę rdzeni, aby uzyskać więcej niż jednego executora.
Ramzy
Zauważyłem, że mój pracownik spark.executor.coresużywa wszystkich 32 rdzeni bez konfigurowania , więc może domyślnie używać wszystkich dostępnych.
jangorecki
Tak, domyślna wartość dla rdzeni jest nieskończona, jak mówią. Spark może więc używać wszystkich dostępnych rdzeni, chyba że podasz.
Ramzy
2
@Ramzy Myślę, że należy zauważyć, że nawet przy alokacji dynamicznej nadal powinieneś określić spark.executor.cores, aby określić rozmiar każdego modułu wykonawczego, który ma zaalokować Spark. W przeciwnym razie, ilekroć Spark przydzieli nowy moduł wykonawczy do Twojej aplikacji, przydzieli cały węzeł (jeśli jest dostępny), nawet jeśli potrzebujesz tylko pięciu dodatkowych rdzeni.
Dan Markhasin
6

Zależy to również od twojego przypadku użycia, ważnym parametrem konfiguracji jest:

spark.memory.fraction(Ułamek (miejsce na stercie - 300 MB) używany do wykonywania i przechowywania) z http://spark.apache.org/docs/latest/configuration.html#memory-management .

Jeśli nie używasz pamięci podręcznej / utrwalania, ustaw ją na 0.1, aby mieć całą pamięć dla swojego programu.

Jeśli używasz pamięci podręcznej / utrwalania, możesz sprawdzić pamięć zajętą ​​przez:

sc.getExecutorMemoryStatus.map(a => (a._2._1 - a._2._2)/(1024.0*1024*1024)).sum

Czy czytasz dane z HDFS czy z HTTP?

Ponownie, dostrojenie zależy od twojego przypadku użycia.

Thomas Decaux
źródło
Czy wiesz, jak wyglądałoby polecenie map podczas korzystania z pyspark? Używam sc._jsc.sc (). GetExecutorMemoryStatus (), aby uzyskać status executora, ale nie mogę nic zrobić z tym, co zwraca ...
Thomas
1
Przepraszam @Thomas Decaux , ale czy chodziło Ci o ustawienie spark.memory.storageFraction=0.1? Ponieważ AFAIK spark.memory.fractiondecyduje o ilości pamięci używanej Sparkzarówno do wykonywania i przechowywania (już o tym wspomniałeś), jak i tylko spark.memory.storageFractionpamięci (która jest dostępna do przechowywania + wykonywania) jest odporna na eksmisję pamięci podręcznej . Proszę zobaczyć ten link
y2k-shubham
@Thomas Jeśli w mojej aplikacji mam tylko stały (StorageLevel.DISK_ONLY), to ta opcja ma również zastosowanie, prawda? Wpływa tylko na ułamek pamięci, ale nie wpływa na żadne wycieki dysku?
jk1