"Контейнер, убитый YARN, для превышения пределов памяти. 10,4 ГБ физической памяти 10,4 ГБ" используется в кластере EMR с 75 ГБ памяти

Я запускаю 5 node Spark-кластер на AWS EMR каждый размером m3.xlarge(1 ведущий 4 slave-устройства). Я успешно прошел через сжатый CSV файл с размером 146 МБ bzip2 и получил совершенно агрегированный результат.

Теперь я пытаюсь обработать файл CSV размером ~ 5 ГБ bzip2 в этом кластере, но я получаю эту ошибку:

16/11/23 17:29:53 WARN TaskSetManager: потерянная задача 49.2 в стадии 6.0 (TID xxx, xxx.xxx.xxx.compute.internal): ExecutorLostFailure (исполнитель 16 вышел из-за одной из запущенных задач) Причина: Контейнер, убитый YARN, превышает пределы памяти. Используется 10,4 ГБ физической памяти 10,4 ГБ. Подумайте о том, как активировать spark.yarn.executor.memoryOverhead.

Я в замешательстве, почему я получаю ограничение памяти на 10,5 ГБ на кластере размером 75 ГБ (15 ГБ на 3 м.большой экземпляр)...

Вот моя конфигурация EMR:

[
 {
  "classification":"spark-env",
  "properties":{

  },
  "configurations":[
     {
        "classification":"export",
        "properties":{
           "PYSPARK_PYTHON":"python34"
        },
        "configurations":[

        ]
     }
  ]
},
{
  "classification":"spark",
  "properties":{
     "maximizeResourceAllocation":"true"
  },
  "configurations":[

  ]
 }
]

Из того, что я прочитал, установка свойства maximizeResourceAllocation должна сообщить EMR, чтобы настроить Spark для полного использования всех ресурсов, доступных в кластере. То есть, у меня должно быть ~ 75 ГБ памяти... Так почему я получаю ошибку ограничения памяти на 10,5 ГБ? Вот код, который я запускаю:

def sessionize(raw_data, timeout):
# https://www.dataiku.com/learn/guide/code/reshaping_data/sessionization.html
    window = (pyspark.sql.Window.partitionBy("user_id", "site_id")
              .orderBy("timestamp"))
    diff = (pyspark.sql.functions.lag(raw_data.timestamp, 1)
            .over(window))
    time_diff = (raw_data.withColumn("time_diff", raw_data.timestamp - diff)
                 .withColumn("new_session", pyspark.sql.functions.when(pyspark.sql.functions.col("time_diff") >= timeout.seconds, 1).otherwise(0)))
    window = (pyspark.sql.Window.partitionBy("user_id", "site_id")
              .orderBy("timestamp")
              .rowsBetween(-1, 0))
    sessions = (time_diff.withColumn("session_id", pyspark.sql.functions.concat_ws("_", "user_id", "site_id", pyspark.sql.functions.sum("new_session").over(window))))
    return sessions
def aggregate_sessions(sessions):
    median = pyspark.sql.functions.udf(lambda x: statistics.median(x))
    aggregated = sessions.groupBy(pyspark.sql.functions.col("session_id")).agg(
        pyspark.sql.functions.first("site_id").alias("site_id"),
        pyspark.sql.functions.first("user_id").alias("user_id"),
        pyspark.sql.functions.count("id").alias("hits"),
        pyspark.sql.functions.min("timestamp").alias("start"),
        pyspark.sql.functions.max("timestamp").alias("finish"),
        median(pyspark.sql.functions.collect_list("foo")).alias("foo"),
    )
    return aggregated
 spark_context = pyspark.SparkContext(appName="process-raw-data")
spark_session = pyspark.sql.SparkSession(spark_context)
raw_data = spark_session.read.csv(sys.argv[1],
                                  header=True,
                                  inferSchema=True)
# Windowing doesn't seem to play nicely with TimestampTypes.
#
# Should be able to do this within the ``spark.read.csv`` call, I'd
# think. Need to look into it.
convert_to_unix = pyspark.sql.functions.udf(lambda s: arrow.get(s).timestamp)
raw_data = raw_data.withColumn("timestamp",
                               convert_to_unix(pyspark.sql.functions.col("timestamp")))
sessions = sessionize(raw_data, SESSION_TIMEOUT)
aggregated = aggregate_sessions(sessions)
aggregated.foreach(save_session)

В принципе, не более чем оконное и groupBy для агрегирования данных.

Он начинается с нескольких из этих ошибок и останавливает увеличение количества той же ошибки.

Я пробовал запускать искра-submit с помощью - conf spark.yarn.executor.memoryOverhead, но это также не решает проблему.

Ответ 1

Я чувствую твою боль..

У нас были похожие проблемы с нехваткой памяти в Spark на YARN. У нас есть пять 64-гигабайтных, 16-ядерных виртуальных машин, и независимо от того, на что мы установили spark.yarn.executor.memoryOverhead, мы просто не могли получить достаточно памяти для этих задач - они в конечном итоге умрут независимо от того, сколько памяти мы им дадим. И это как относительно простое приложение Spark, которое вызывало это.

Мы выяснили, что использование физической памяти было довольно низким на виртуальных машинах, но использование виртуальной памяти было чрезвычайно высоким (несмотря на жалобы на физическую память журналов). Мы установили yarn.nodemanager.vmem-check-enabled в yarn-site.xml на false, и наши контейнеры больше не были убиты, и приложение, казалось, работало как ожидалось.

Проведя дополнительные исследования, я нашел ответ на вопрос, почему это происходит здесь: https://www.mapr.com/blog/best-practices-yarn-resource-management

Поскольку в Centos/RHEL 6 происходит агрессивное выделение виртуальной памяти из-за поведения ОС, вам следует отключить проверку виртуальной памяти или увеличить значение yarn.nodemanager.vmem-pmem-ratio на относительно большее значение.

На этой странице была ссылка на очень полезную страницу от IBM: https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

Таким образом, glibc> 2.10 изменил распределение памяти. И хотя выделение огромных объемов виртуальной памяти не конец света, он не работает со стандартными настройками YARN.

Вместо установки yarn.nodemanager.vmem-check-enabled в значение false, вы также можете поиграть, установив для переменной среды MALLOC_ARENA_MAX меньшее значение в hadoop-env.sh. Этот отчет об ошибке содержит полезную информацию об этом: https://issues.apache.org/jira/browse/HADOOP-7154

Я рекомендую прочитать обе страницы - информация очень удобная.

Ответ 2

Если вы не используете spark-submit, и вы ищете другой способ указать параметр yarn.nodemanager.vmem-check-enabled упомянутый Duff, вот еще два способа:

Способ 2

Если вы используете файл конфигурации JSON (который вы передаете в AWS CLI или в ваш сценарий boto3), вам нужно добавить следующую конфигурацию:

[{
"Classification": "yarn-site", 
  "Properties": {
    "yarn.nodemanager.vmem-check-enabled": "false"
   }
}]

Способ 3

Если вы используете консоль EMR, добавьте следующую конфигурацию:

classification=yarn-site,properties=[yarn.nodemanager.vmem-check-enabled=false]

Ответ 3

Увидеть,

У меня была такая же проблема в огромном кластере, что я сейчас работаю. Проблема не будет решена для добавления памяти к рабочему. Иногда в процессе агрегации искры будет использовать больше памяти, чем она есть, и искровые задания начнут использовать память с кучи.

Один простой пример:

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

Добавление опции spark.yarn.executor.memoryOverhead поможет вам, если вы установите для 50% памяти, используемой для рабочего (только для теста и посмотрите, работает ли она, вы можете добавить меньше с большим количеством тестов).

Но вам нужно понять, как Spark работает с распределением памяти в кластере:

  1. Более распространенный способ использования Spark - 75% памяти устройства. Остальное идет к SO.
  2. Spark имеет два типа памяти во время выполнения. Одна часть предназначена для выполнения, а другая - для хранения. Выполнение используется для Shuffles, Joins, Aggregations и т.д. Хранилище используется для кэширования и распространения данных по кластеру.

Одна хорошая вещь о распределении памяти, если вы не используете кеш в своем исполнении, вы можете установить искру, чтобы использовать это пространство для хранения, чтобы работать с выполнением, чтобы частично избежать ошибки OOM. Как вы можете видеть это в документации искры:

Эта конструкция обеспечивает несколько желательных свойств. Во-первых, приложения, которые не используют кеширование, могут использовать все пространство для выполнения, избавляя от ненужных утечек диска. Во-вторых, приложения, которые используют кеширование, могут зарезервировать минимальное пространство для хранения (R), где их блоки данных не защищены от высылки. Наконец, этот подход обеспечивает разумную готовность к работе для множества рабочих нагрузок, не требуя экспертных знаний о том, как память разделяется внутри страны.

Но как мы можем это использовать?

Вы можете изменить некоторые конфигурации, добавить конфигурацию MemoryOverhead в свой рабочий вызов, но подумайте также об этом: spark.memory.fraction change for 0.8 или 0.85 и уменьшите значение spark.memory.storageFraction до 0,35 или 0,2.

Другие конфигурации могут помочь, но вам нужно проверить свой случай. Se все эти конфигурации здесь.

Теперь, что помогает в моем случае.

У меня есть кластер с 2.5K рабочими и 2.5TB ОЗУ. И мы столкнулись с ошибкой OOM, как ваша. Мы просто увеличиваем spark.yarn.executor.memoryOverhead до 2048. И мы spark.yarn.executor.memoryOverhead динамическое распределение. И когда мы называем работу, мы не устанавливаем память для рабочих, мы оставляем это для Искры решать. Мы просто установили Накладные расходы.

Но для некоторых тестов для моего небольшого кластера, изменяя размер исполнения и памяти. Это решило проблему.

Ответ 4

Попробуйте переделить. Это работает в моем случае.

Фрейм данных был не таким большим в самом начале, когда он был загружен write.csv(). Файл данных составлял около 10 МБ или около того, как может потребоваться, скажем, всего несколько 100 МБ памяти для каждой задачи обработки в исполнителе. Я проверил количество разделов, чтобы быть 2 в то время. Затем он рос как снежный ком во время следующих операций, соединяясь с другими таблицами, добавляя новые столбцы. И тогда я столкнулся с проблемой превышения памяти на определенном этапе. Я проверил количество разделов, оно по-прежнему составляло 2, исходя из исходного фрейма данных. Поэтому я попытался перераспределить его в самом начале, и проблем больше не было.

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

Разумная стратегия - сначала определить узлы и контейнерную память, 10 ГБ или 5 ГБ. В идеале, оба могут выполнять любую работу по обработке данных, только вопрос времени. Учитывая настройку памяти 5 ГБ, разумную строку для одного раздела, который вы найдете, скажем, 1000 после тестирования (он не пропустит ни одного шага во время обработки), мы могли бы сделать это следующим псевдокодом:

RWS_PER_PARTITION = 1000
input_df = spark.write.csv("file_uri", *other_args)
total_rows = input_df.count()
original_num_partitions = input_df.getNumPartitions()
numPartitions = max(total_rows/RWS_PER_PARTITION, original_num_partitions)
input_df = input_df.repartition(numPartitions)

Надеюсь, это поможет!

Ответ 5

У меня была та же проблема на небольшом кластере, который выполнял относительно небольшую работу по искровому зажиганию 2.3.1. Задание читает файл паркета, удаляет дубликаты с помощью groupBy/agg/first, затем сортирует и записывает новый паркет. Он обработал 51 ГБ паркетных файлов на 4 узлах (4 vcores, 32Gb RAM).

Работа постоянно терпела неудачу на стадии агрегации. Я написал использование памяти исполнителей сценариев bash, и выяснил, что в середине этапа один случайный исполнитель начинает брать двойную память на несколько секунд. Когда я сопоставлял время этого момента с GC-журналами, он соответствовал полному GC, который опустошает большой объем памяти.

Наконец я понял, что проблема как-то связана с GC. ParallelGC и G1 постоянно вызывают эту проблему, но ConcMarkSweepGC улучшает ситуацию. Проблема возникает только при небольшом количестве разделов. Я запустил задание на EMR, где был установлен OpenJDK 64-Bit (build 25.171-b10). Я не знаю причину проблемы, она может быть связана с JVM или операционной системой. Но это определенно не связано с использованием кучи или вне кучи в моем случае.

Update1

Пробовал Oracle HotSpot, проблема воспроизводится.

Ответ 6

У меня такая же проблема в EMR с Spark 2.3.1 и долгое время борется. Это связанная пряжа и контейнер. Пробовал всевозможные способы, но до сих пор не работал.

У меня такой же конфигурационный кластер в EC2 с Apache Spark 2.3.1 и Hadoop 2.7, который не использует ни один контейнер и пряжу, просто режим автономного кластера Spark. В этом кластере не было проблем.