Настройка производительности Spark: контрольный список

Учитывая доказанную мощность и возможности Apache Spark для крупномасштабной обработки данных, мы регулярно используем Spark здесь, в ZGL. Чтобы написать код Spark, который будет выполняться эффективно, чрезвычайно важно знать о некоторых тонкостях настройки и хитростях. В отличие от многих других сообщений в блогах о настройке Spark, этот пост предназначен для предоставления краткого контрольного списка таких соображений, которые вы можете легко проверить в своем коде в случае проблем с производительностью Spark. Таким образом, читатель должен иметь базовое представление о внутренней архитектуре и модели выполнения Spark и должен быть знаком с такими концепциями Spark, как RDD, преобразования и действия. .

Этот список будет обновляться по мере того, как мы продолжим узнавать об оптимизации производительности Spark. Текущая версия создана на основе следующих ресурсов:

Сериализация данных

  • По умолчанию Spark сериализуется с помощью инфраструктуры Java ObjectOutputStream, которая работает с любым классом, реализующим интерфейс java.io.Serializable.
  • Внутренний сериализатор Java является гибким, но часто медленным и создает большие сериализованные объекты.
  • Переключитесь на Kyro Serializer с SparkConf, который быстрее и компактнее.
  • Вы можете зарегистрировать свои собственные классы в Kyro. Kyro по-прежнему будет работать без регистрации пользовательских классов, но это будет расточительно.
  • Возможно, вам потребуется увеличить spark.kyroserializer.buffer, чтобы удерживать большие объекты, которые вы будете сериализовать.

Вывоз мусора

  • Когда Java вытесняет старые объекты, чтобы освободить место для новых, ей необходимо проследить все объекты и отбросить старые. Таким образом, стоимость сборки мусора JVM пропорциональна количеству объектов Java.
  • Структуры данных с меньшим количеством объектов снизят эту стоимость (Массив из Int по сравнению с LinkedList).
  • Сохраняйте объекты в сериализованной форме, чтобы на каждый RDD был только один объект (байтовый массив).
  • Вы можете измерить частоту сборки мусора и затраченное время, добавив -verbose:gc -XX:+PrintGCDetails и -XX:+PrintGCTimeStamps в параметры Java.
  • Слишком много или длительные сборщики мусора означают, что для выполнения сборщика мусора не хватает памяти. Для эффективного использования памяти вы можете очистить кешированные / сохраненные коллекции, когда они больше не нужны.
  • Дополнительно: настройте JVM Young (Eden, Survivor1, Survivor2) и Old Heap.

Управление памятью

  • Память выполнения используется для вычислений (например, перемешивания, объединения, сортировки и объединения).
  • Память хранения используется для кэширования и распространения внутренних данных.
  • Задания, не использующие кеш, могут использовать все пространство для выполнения и избежать утечки информации на диск.
  • Приложения, использующие кэширование, резервируют минимальное пространство для хранения, где данные не могут быть удалены требованиями выполнения.
  • Задайте spark.memory.fraction, чтобы определить, какая часть пространства кучи JVM используется для памяти выполнения / хранения Spark. По умолчанию 60%.
  • Установите флаг JVM -XX:+UseCompressedOops, если у вас меньше 32 ГБ ОЗУ, чтобы указатели имели размер четыре байта вместо восьми.
  • Избегайте использования исполнителей со слишком большим объемом памяти, так как это приведет к задержке процесса сборки мусора JVM.
  • Избегайте использования большого количества очень маленьких исполнителей, чтобы по-прежнему иметь возможность запускать несколько задач на одной JVM.

Разделение и параллелизм

  • В кластере рекомендуется иметь 2–3 задачи на каждое ядро ​​ЦП.
  • Несмотря на то, что Spark пытается вывести разумное количество разделов для ваших коллекций, иногда вам может потребоваться настроить уровень параллелизма путем оптимизации количества разделов.
  • СДР, созданные textFile или jsonFile, разбиваются на разделы на основе используемого основного MapReduce InputFormat. Входные RDD HDFS обычно разделяются в зависимости от количества блоков HDFS, на которых находятся данные.
  • СДР, производные от других коллекций, просто наследуют количество секций от родительской коллекции.
  • Сведите к минимуму количество перетасовок, используя механизмы разделения, которые позволят программе знать, что набор ключей появится вместе на одном узле.
  • Разделение по хешу: ключи с одинаковым значением хеш-функции будут отображаться на одном узле.
  • Разделение диапазона: ключи в пределах одного диапазона появляются на одном узле.
  • Операции Spark, которые включают перемешивание данных по ключевым преимуществам разделения: cogroup (), groupWith (), join () , groupByKey (), commonByKey (), reduceByKey () и lookup ()).
  • Перераспределение (repartition ()) - дорогостоящая задача, поскольку при этом перемещаются данные, но вы можете использовать coalesce () вместо того, чтобы уменьшать количество перегородки.
  • Если коллекция используется один раз, нет смысла в ее повторном разбиении, но повторное разбиение полезно только в том случае, если она используется несколько раз в ключевых операциях.

Выбор действий / преобразований и оптимизация

  • Сведите к минимуму перемешивание при join (), транслируя меньшую коллекцию или хешируя оба RDD по ключам.
  • По возможности используйте узкие преобразования вместо широких. В узких преобразованиях (например, map () и filter ()) данные, необходимые для обработки, находятся в одном разделе, тогда как в широкое преобразование (например, groupByKey (), reduceByKey () и join ()), данные должны быть Processing находится в нескольких разделах, поэтому его нужно перетасовать.
  • Избегайте использования GroupByKey () для ассоциативных сокращающих операций. Вместо этого всегда используйте ReduceByKey (). С помощью ReduceByKey Spark объединяет вывод с общими ключами в каждом разделе перед перетасовкой данных.
  • Чтобы объединить два сгруппированных набора данных и сохранить их сгруппированными, используйте cogroup () вместо шаблона flatMap-join-groupBy.
  • Чтобы перераспределить данные и отсортировать записи в каждом разделе, используйте repartitionAndSortWithinPartitions () вместо вызова перераспределения и последующей сортировки внутри каждого раздела, потому что это может подтолкнуть сортировку к тасовать машины.

Кеширование и сохранение

  • Сохраняйте или кешируйте коллекции, которые будут использоваться более одного раза.
  • Сохранять коллекции после повторного разделения.
  • Если вам нужно кэшировать большие объемы обработанных данных, измените операцию кеширования Spark по умолчанию:
  • По умолчанию Spark кэширует данные в MEMORY_ONLY, поэтому, если памяти недостаточно, Spark просто отбрасывает старые разделы RDD и пересчитывает их, если они необходимы. Вместо этого используйте уровень хранения MEMORY_AND_DISK, который удаляет лишние разделы на диск и при необходимости просто перечитывает их снова.
  • Кэшируйте сериализованные объекты вместо необработанных (через MEMORY_ONLY_SER или MEMORY_AND_DISK_SER). Несмотря на то, что это замедляет процесс кэширования из-за стоимости сериализации, это существенно сокращает время, затрачиваемое на сборку мусора.

Другой

  • OutofMemoryError из-за операций: Увеличьте параллелизм, чтобы каждый ввод задачи был меньше. Поскольку Spark повторно использует исполняющую JVM для многих задач, увеличьте параллелизм до количества ядер.
  • Spark планирует задачи в наиболее удобном месте. Если есть данные, ожидающие обработки, Spark будет ждать освобождения локального процессора. Если это займет слишком много времени, Spark переместит данные на свободный ЦП подальше. Вы можете настроить, как долго искра будет ждать освобождения ЦП, если ваши задачи длинные и у вас плохая локализация.

Если вам понравился этот пост, поделитесь им и дайте несколько

Мы рады возможности делиться своей работой и рады открывающимся возможностям. Мы будем рады вашим отзывам, и для связи с другими людьми, интересующимися этой работой, пожалуйста, свяжитесь с нами. Мы также ищем других замечательных ученых, которые присоединятся к нашей команде, поэтому, пожалуйста, ознакомьтесь с нашими открытыми ролями.

Тарнех Хазаи - ученый из Zero Gravity Labs, а Дэнни Луо - студент-кооператив, который является соавтором этого удивительного сообщения.

Свяжитесь с Таране или любым членом нашей команды (мы нанимаем), подписавшись на нас в Facebook, Twitter, Instagram и LinkedIn.

Следите за всем контентом блога Zero Gravity Labs на Medium