Канфігураванне Spark на YARN

Хабр, прывітанне! Учора на мітапе, прысвечаным Apache Spark, ад рабят з Rambler&Co, было даволі шмат пытанняў ад удзельнікаў, злучаных з канфігураваннем гэтай прылады. Вырашылі па яго слядах падзяліцца сваім досведам. Тэма няпростая - таму прапануем дзяліцца вопытам таксама ў каментарах, можа быць, мы таксама нешта не так разумеем і выкарыстоўваем.

Невялікая ўступная - як мы выкарыстоўваем Spark. У нас ёсць трохмесячная праграма "Спецыяліст па вялікіх дадзеных", і ўвесь другі модуль нашы ўдзельнікі працуюць на гэтым інструменце. Адпаведна, нашая задача, як арганізатараў, падрыхтаваць кластар пад выкарыстанне ў рамках такога кейса.

Асаблівасць нашага выкарыстання складаецца ў тым, што колькасць чалавек, адначасова якія працуюць на Spark, можа быць роўна ўсёй групе. Напрыклад, на семінары, калі ўсе адначасова нешта спрабуюць і паўтараюць за нашым выкладчыкам. А гэта крыху-немала – пад 40 чалавек часам. Напэўна, не так шмат кампаній у свеце, якія сутыкаюцца з такім сцэнарам выкарыстання.

Далей я раскажу, як і чаму мы падбіралі тыя ці іншыя параметры канфіга.

Пачнём з самага пачатку. У Spark ёсць 3 варыянты працаваць на кластары: standalone, з выкарыстаннем Mesos і з выкарыстаннем YARN. Мы вырашылі абраць трэці варыянт, таму што для нас ён быў лагічны. У нас ужо ёсць hadoop-кластар. Нашыя ўдзельнікі добра ўжо знаёмыя з яго архітэктурай. Давайце юзаць YARN.

spark.master=yarn

Далей цікавей. У кожнага з гэтых 3 варыянтаў разгортвання ёсць 2 варыянты дэплою: client і cluster. Зыходзячы з дакументацыі і розных спасылак у інтэрнэце, можна зрабіць выснову, што client падыходзіць для інтэрактыўнай працы - напрыклад, праз jupyter notebook, а cluster больш падыходзіць для production-рашэнняў. У нашым выпадку нас цікавіла інтэрактыўная праца, таму:

spark.deploy-mode=client

Увогуле з гэтага моманту Spark ужо будзе неяк працаваць на YARN, але нам гэтага не было дастаткова. Паколькі ў нас праграма пра вялікія дадзеныя, то часам удзельнікам не хапала таго, што атрымлівалася ў межах раўнамернай нарэзкі рэсурсаў. І тут мы знайшлі цікавую рэч - дынамічную алакацыю рэсурсаў. Калі сцісла, то іста ў наступным: калі ў вас цяжкая задача і кластар вольны (напрыклад, з раніцы), то пры дапамозе гэтай опцыі Spark вам можа выдаць дадатковыя рэсурсы. Неабходнасць лічыцца тамака па хітрай формуле. Удавацца ў падрабязнасці не будзем - яна нядрэнна працуе.

spark.dynamicAllocation.enabled=true

Мы паставілі гэты параметр, і пры запуску Spark лаяўся і не запусціўся. Правільна, таму што трэба было чытаць дакументацыю больш уважліва. Там пазначана, што для таго, каб усё было ок, трэба яшчэ ўключыць дадатковы параметр.

spark.shuffle.service.enabled=true

Навошта ён патрэбен? Калі наш джоб больш не патрабуе такой колькасці рэсурсаў, то Spark павінен вярнуць іх у агульны пул. Самая працаёмкая стадыя амаль у любой MapReduce задачы - гэта стадыя Shuffle. Гэты параметр дазваляе захоўваць дадзеныя, якія ўтвараюцца ў гэтай стадыі і адпаведна вызваляць executors. А executor - гэта працэс, які на воркеры ўсё аблічвае. У яго ёсць нейкая колькасць працэсарных ядраў і нейкая колькасць памяці.

Дадалі гэты параметр. Усё быццам бы зарабіла. Стала заўважна, што ўдзельнікам рэальна стала выдавацца больш рэсурсаў, калі ім было патрэбна. Але ўзнікла іншая праблема - у нейкі момант іншыя ўдзельнікі прачыналіся і таксама хацелі выкарыстоўваць Spark, а там усё занята, і яны былі незадаволеныя. Іх можна зразумець. Пачалі глядзець у дакументацыю. Там аказалася, што ёсць яшчэ нейкая колькасць параметраў, пры дапамозе якіх можна паўплываць на працэс. Напрыклад, калі executor знаходзіцца ў рэжыме чакання - праз які час у яго можна забраць рэсурсы?

spark.dynamicAllocation.executorIdleTimeout=120s

У нашым выпадку - калі вашы executors нічога не робяць на працягу двух хвілін, то, будзьце добрыя, вярніце іх у агульны пул. Але і гэтага параметра не заўсёды хапала. Было бачна, што чалавек ужо даўно нічога не робіць, а рэсурсы не вызваляюцца. Аказалася, што ёсць яшчэ спецыяльны параметр – па сканчэнні якога часу адбіраць executors, якія ўтрымоўваюць закэшаваныя дадзеныя. Па дэфолце гэты параметр стаяў - infinity! Мы яго паправілі.

spark.dynamicAllocation.cachedExecutorIdleTimeout=600s

Гэта значыць, калі на працягу 5 хвілін вашы executors нічога не робяць, аддайце-ка іх у агульны пул. У такім рэжыме хуткасць вызвалення і выдачы рэсурсаў для вялікай колькасці карыстальнікаў стала дастойнай. Колькасць незадаволенасці скарацілася. Але мы вырашылі пайсці далей і абмежаваць максімальную колькасць executors на адзін application – па сутнасці на аднаго ўдзельніка праграмы.

spark.dynamicAllocation.maxExecutors=19

Цяпер, вядома, з'явіліся незадаволеныя з іншага боку - "кластар прастойвае, а ў мяне ўсяго толькі 19 executors", але што парабіць - патрэбен нейкі правільны баланс. Усіх зрабіць шчаслівымі не атрымаецца.

І яшчэ адна невялікая гісторыя, злучаная са спецыфікай нашага кейса. Неяк на практычны занятак спазніліся некалькі чалавек, і ў іх Spark чамусьці не стартаваў. Мы паглядзелі на колькасць вольных рэсурсаў - быццам бы ёсць. Spark павінен стартаваць. Добра, што да таго моманту дакументацыя ўжо недзе запісалася на падкорку, і мы ўспомнілі, што пры запуску Spark шукае сабе порт, на якім стартаваць. Калі першы порт з дыяпазону заняты, то ён пераходзіць да наступнага па парадку. Калі ён вольны, тое захоплівае. І ёсць параметр, які паказвае на максімальную колькасць спробаў для гэтага. Па змаўчанні - гэта 16. Лік менш, чым людзей у нашай групе на занятку. Адпаведна, пасля 16 спроб Spark кідаў гэтую справу і казаў, што не магу стартануць. Мы паправілі гэты параметр.

spark.port.maxRetries=50

Далей раскажу аб некаторых наладах, ужо не моцна звязаных са спецыфікай нашага кейса.

Для хутчэйшага старту Spark ёсць рэкамендацыя тэчку jars, ляжалую ў хатняй дырэкторыі SPARK_HOME, заархіваваць і пакласці на HDFS. Тады ён не будзе марнаваць часу на загрузку гэтых Джарнік па воркерам.

spark.yarn.archive=hdfs:///tmp/spark-archive.zip

Таксама для хутчэйшай працы рэкамендуецца ў якасці серыялайзера выкарыстоўваць kryo. Ён больш аптымізаваны, чым той, што па змаўчанні.

spark.serializer=org.apache.spark.serializer.KryoSerializer

І ёсць яшчэ даўняя праблема Spark, што ён часта валіцца па памяці. Часта гэта адбываецца ў той момант, калі воркеры ўсё палічылі і адпраўляюць вынік на драйвер. Мы зрабілі сабе гэты параметр пабольш. Па змаўчанні, ён 1Гб, мы зрабілі - 3.

spark.driver.maxResultSize=3072

І апошняе, у якасці дэсерту. Як абнавіць Spark да версіі 2.1 на HortonWorks дыстрыбутыве – HDP 2.5.3.0. Гэтая версія HDP утрымоўвае ў сабе прадусталяваную версію 2.0, але мы неяк аднойчы для сябе вырашылі, што Spark даволі актыўна развіваецца, і кожная новая версія фіксуе нейкія багі плюс дае дадатковыя магчымасці, у тым ліку і для python API, таму вырашылі , што трэба рабіць апдэйт.

Спампавалі версію з афіцыйнага сайта пад Hadoop 2.7. Разархівавалі, закінулі ў тэчку з HDP. Паставілі сімлінкі як трэба. Запускаем - не стартуе. Піша вельмі незразумелую памылку.

java.lang.NoClassDefFoundError: com/sun/jersey/api/client/config/ClientConfig

Пагугліўшы, высветлілі, што Spark вырашыў не чакаць пакуль Hadoop разродзіцца, і вырашылі выкарыстоўваць новую версію jersey. Яны самі там сябар з сябрам лаюцца на гэтую тэму ў JIRA. Рашэннем было - спампаваць jersey версіі 1.17.1. Закінуць гэта ў тэчку jars у SPARK_HOME, зноў зрабіць zip і закінуць на HDFS.

Гэтую памылку мы абышлі, але ўзнікла новая і даволі-такі абцякальная.

org.apache.spark.SparkException: Yarn application has already ended! It might have been killed or unable to launch application master

Пры гэтым спрабуем запускаць версію 2.0 – усё ок. Паспрабуй здагадайся, што такое. Мы залезлі ў логі гэтага application і ўбачылі нешта такое:

/usr/hdp/${hdp.version}/hadoop/lib/hadoop-lzo-0.6.0.${hdp.version}.jar

Увогуле, па нейкіх чынніках hdp.version не рэзанвілася. Пагугліўшы, знайшлі рашэнне. Трэба ў Ambari зайсці ў налады YARN і дадаць тамака параметр у custom yarn-site:

hdp.version=2.5.3.0-37

Гэтая магія дапамагла, і Spark узляцеў. Пратэсцілі некалькі нашых jupyter-наўтбукаў. Усё працуе. Да першага занятку па Spark у суботу (ужо заўтра) мы гатовыя!

ДУП. На занятках высветлілася яшчэ адна праблема. У нейкі момант YARN перастаў выдаваць кантэйнеры для Spark. У YARN трэба было паправіць параметр, які па дэфолце стаяў 0.2:

yarn.scheduler.capacity.maximum-am-resource-percent=0.8

Гэта значыць, толькі 20% рэсурсаў удзельнічалі ў раздачы рэсурсаў. Памяняўшы параметры, перазагрузілі YARN. Праблема была вырашана, і астатнія ўдзельнікі таксама змаглі запусціць spark context.

Крыніца: habr.com

Дадаць каментар