Обучая разработчиков Big Data, сегодня рассмотрим, почему в распределенных приложениях Apache Spark случаются OOM-ошибки. Читайте далее, как работает сборка мусора JVM в Spark-приложениях, почему из-за нее случаются утечки памяти и что можно сделать на уровне драйвера и исполнителя для предупреждения OutOfMemoryError.
Сборка мусора JVM и OOM-ошибки в Spark-приложениях
На практике сбои Spark-приложения случаются довольно часто из-за необработанного исключения OutOfMemoryError (OOM), связанного с нехваткой памяти кучи (heap) для виртуальных машин Java, которые запускаются как исполнители или драйверы. По умолчанию размер памяти для исполнителя (executor) Spark по умолчанию равен 1 ГБ. Если этого объема недостаточно, JVM запускает полную сборку мусора (Garbage Collection), чтобы высвободить избыточную память. Но если после 5 последовательных циклов полной сборки мусора высвобождается менее 2% памяти, JVM выдает соответствующее исключение – Out Of Memory Error, которое выглядит следующим образом [1]:
ERROR akka.ErrorMonitor: Uncaught fatal error from thread [sparkDriverakka. actor.default-dispatcher-29] shutting down ActorSystem [sparkDriver] java.lang.OutOfMemoryError: Java heap space Exception in thread "task-result-getter-0" java.lang.OutOfMemoryError: Java heap space
Поскольку OOM-исключение вызывается в результате сборки мусора, то чрезмерная активность Garbage Collector, который занимает слишком много времени и ресурсов, может привести к Out Of Memory Error. Это случается из-за превышения предела накладных расходов памяти и в случае распределенных систем, что актуально для Spark-приложений, может быть решено с помощью G1GC – сборщика мусора серверного типа для многопроцессорных машин с большой памятью. G1GC соответствует целевым показателям времени паузы при сборке мусора, обеспечивая высокую пропускную способность и выполняя операции с кучей одновременно с потоками приложения. Это предотвращает прерывания, пропорциональные размеру кучи или оперативных данных. Включить G1GC в Apache Spark можно, задав конфигурацию executor.extraJavaOptions: -XX: + UseG1GC [2]. Подробнее о видах памяти фреймворка и конфигурации его JVM-настроек мы писали здесь.
Официальная документация Apache Spark отмечает, что сборка мусора JVM становится проблемой, при большом оттоке RDD, хранимых приложением. В этом случае Java пытается избавиться от старых объектов, чтобы освободить место для новых. Фактически, стоимость сборки мусора пропорциональна количеству объектов Java, поэтому использование структур данных с меньшим количеством объектов, например, массив Ints вместо LinkedList, снижает эти расходы. Также можно сохранить объекты в сериализованной форме, чтобы на каждый раздел RDD приходился только один объект (массив байтов), и попробовать сериализованное кэширование. Если рабочая память пользовательских задач накладывается на RDD, кэшированных на узлах Spark-кластера, сборка мусора тоже может провоцировать OOM.
Напомним, пространство кучи Java разделено на две области:
- для хранения короткоживущих объектов, разделенное на регионы Eden, Survivor1, Survivor2;
- для объектов с более длительным сроком службы.
Когда пространство Eden заполнено, в нем запускается второстепенный сборщик мусора, а объекты из Eden и Survivor1 копируются в Survivor2. Регионы Survivor2 и Survivor1 меняются местами. Если объект достаточно старый или Survivor2 заполнен, он перемещается в пространство для объектов с длительном сроком службы. Когда и оно заполняется, вызывается полный сборщик мусора. Если это происходит несколько раз до завершения задачи, то для ее выполнения недостаточно памяти. В этом случае следует задать нужный размер пространства Eden с учетом специфики Spark-приложения.
Например, если задача считывает данные из HDFS, то ее объем памяти можно оценить по размеру блока данных, прочитанного из HDFS. Однако, размер распакованного блока обычно в 2-3 раза больше исходного. Так, если нужно рабочее пространство на 3-4 таких задачи, а размер блока HDFS составляет 128 Мбайт, то размер Eden можно оценить в 4*3*128=1563 Мбайт. Флаги настройки GC для исполнителей можно указать в конфигурации задания, установив spark.executor.defaultJavaOptions или spark.executor.extraJavaOptions [3].
Однако, сборка мусора – не единственная причина OOM-ошибки в Спарк-приложениях, которая может случиться на уровне драйвера и/или на исполнителе из-за множества других факторов, которые мы рассмотрим далее.
Core Spark - основы для разработчиков
Код курса
CORS
Ближайшая дата курса
16 декабря, 2024
Продолжительность
16 ак.часов
Стоимость обучения
48 000 руб.
Почему в Apache Spark случаются исключения OutOfMemoryError и как с ними бороться
На уровне драйвера Apache Spark ошибка OOM может случиться по следующим причинам [4]:
- нехватка памяти на драйвере, который является основным элементом управления Spark-приложением. Как эти проблемы решает новый проект от Databricks, читайте в нашей новой статье.
- слишком большой размер таблицы, которая должна транслироваться, например, для выполнения SQL-запросов.
На уровне исполнителя ошибка OOM происходит из-за следующих факторов [4]:
- слишком большое количество ядер ЦП для исполнителя, заданное в свойстве executor.cores без учета требуемой памяти. На практике чаще всего на каждого исполнителя выделяется 3-5 ядер CPU. Подробнее про конфигурирование Спарк-исполнителей с настройкой памяти и ядер ЦП мы рассказывали здесь и здесь.
- много столбцов в SQL-запросе, что устраняется включением в запрос только нужных столбцов и бакетированием данных;
- недостаток служебной памяти YARN, нужной для служебных данных JVM, внутренних строк или других требований к метаданным. Эта проблема исправляется настройкой конфигурации yarn.executor.memoryOverhead. Обычно накладные расходы памяти YARN вне кучи составляют 10% от общей памяти исполнителя. При этом рекомендуется увеличивать не только память исполнителя, но и накладную память YARN для потоков JVM, внутренних метаданных и пр. через spark-submit [1]:
conf “spark.executor.memory=12g” conf “spark.yarn.executor.memoryOverhead=2048”
Также это можно сделать в конфигурационном файле spark-defaults.conf:
“executor-memory=12g”
Наконец, OOM-ошибки могут возникать из-за перекоса данных и выполнения операций перетасовки (group by, join и т.д.). Shuffle-операции выполняются на уровне исполнителя, а если он занят или на нем выполняется полная сборка мусора, о которой мы сказали выше, то будет выдано исключение OutOfMemoryError. Поэтому рекомендуется реже использовать shuffle-функции, которые включают в перетасовки. А избавиться от перекоса данных можно с помощью с помощью типового или пользовательского перераспределителя, а также готовых методов coalesce() и repartition(), разницу между которыми мы разбирали здесь. О том, как и где найти главную причину OutOfMemoryError в приложениях Apache Spark, читайте в этом материале.
Анализ данных с помощью современного Apache Spark
Код курса
SPARK
Ближайшая дата курса
16 декабря, 2024
Продолжительность
32 ак.часов
Стоимость обучения
96 000 руб.
А освоить тонкости этого фреймворка для разработки распределенных приложений и аналитики больших данных вам помогут специализированные курсы в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:
- Основы Apache Spark для разработчиков
- Анализ данных с Apache Spark
- Потоковая обработка в Apache Spark
- Машинное обучение в Apache Spark
- Графовые алгоритмы в Apache Spark
- https://support.datafabric.hpe.com/s/article/Spark-Troubleshooting-guide-Memory-Management-How-to-troubleshooting-out-of-memory-OOM-issues-on-Spark-Executor
- https://medium.com/disney-streaming/a-step-by-step-guide-for-debugging-memory-leaks-in-spark-applications-e0dd05118958
- https://spark.apache.org/docs/latest/tuning.html
- https://goraidebashree7.medium.com/spark-out-of-memory-error-da89b242d435