web-dev-qa-db-fra.com

"Conteneur tué par YARN pour dépassement des limites de la mémoire. 10,4 Go de mémoire physique utilisée)" sur un cluster EMR avec 75 Go de mémoire

J'exécute un cluster Spark à 5 nœuds sur AWS EMR de taille m3.xlarge (1 maître 4 esclaves). J'ai réussi à parcourir un fichier CSV compressé à 146 Mo. bzip2 et j'ai obtenu un résultat parfaitement agrégé.

Maintenant, j'essaie de traiter un fichier CSV ~ 5 Go bzip2 sur ce cluster, mais je reçois cette erreur:

16/11/23 17:29:53 WARN TaskSetManager: tâche 49.2 perdue à l'étape 6.0 (TID xxx, xxx.xxx.xxx.compute.internal): ExecutorLostFailure (l'exécuteur 16 a été fermé par l'une des tâches en cours d'exécution) Reason: Container tué par YARN pour avoir dépassé les limites de la mémoire. 10,4 Go de mémoire physique de 10,4 Go utilisés. Envisagez de renforcer spark.yarn.executor.memoryOverhead.

Je ne comprends pas pourquoi j'obtiens une limite de mémoire d'environ 10,5 Go sur un cluster d'environ 75 Go (15 Go par instance de 3m.xlarge) ...

Voici ma config EMR:

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

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

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

  ]
 }
]

D'après ce que j'ai lu, la définition de la propriété maximizeResourceAllocation devrait indiquer à EMR de configurer Spark pour qu'il utilise pleinement toutes les ressources disponibles sur le cluster. C'est-à-dire que je devrais avoir environ 75 Go de mémoire disponible ... Alors, pourquoi est-ce que je reçois une erreur de limite de mémoire d'environ 10,5 Go? Voici le code que je suis en train d'exécuter:

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)

Fondamentalement, rien de plus que le fenêtrage et un groupe pour agréger les données.

Cela commence par quelques-unes de ces erreurs, puis vers l’arrêt des augmentations de la quantité de la même erreur.

J'ai essayé de lancer spark-submit avec --conf spark.yarn.executor.memoryOverhead mais cela ne semble pas résoudre le problème non plus.

24
lauri108

Si vous n'utilisez pas spark-submit et recherchez un autre moyen de spécifier le paramètre yarn.nodemanager.vmem-check-enabled mentionné par Duff , voici 2 autres façons:

Méthode 2

Si vous utilisez un fichier de configuration JSON (que vous transmettez à l'AWS CLI ou à votre script boto3), vous devrez ajouter la configuration suivante:

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

Méthode 3

Si vous utilisez la console EMR, ajoutez la configuration suivante:

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

Voir,

J'ai eu le même problème dans un groupe énorme que je travaille maintenant. L'ajout de mémoire au travailleur ne résoudra pas le problème. Parfois, l’étincelle d’agrégation de processus utilise plus de mémoire qu’elle n’en a et les travaux d’étincelle commencent à utiliser de la mémoire morte.

Un exemple simple est:

Si vous avez un ensemble de données que vous devez reduceByKey, il sera parfois agrégé plus de données dans un opérateur que dans un autre, et si ces données dépassent la mémoire d'un opérateur, vous obtenez ce message d'erreur.

L'ajout de l'option spark.yarn.executor.memoryOverhead vous aidera si vous définissez 50% de la mémoire utilisée par l'utilisateur (juste pour le test et voir si cela fonctionne, vous pouvez en ajouter moins avec davantage de tests).

Mais vous devez comprendre le fonctionnement de Spark avec l'allocation de mémoire dans le cluster:

  1. Spark utilise 75% de la mémoire de la machine. Le reste va à SO.
  2. Spark a deux types de mémoire lors de l'exécution. Une partie est pour l'exécution et l'autre est le stockage. L'exécution est utilisée pour la lecture aléatoire, les jointures, les agrégations, etc. Le stockage est utilisé pour la mise en cache et la propagation des données sur le cluster.

Une bonne chose à propos de l’allocation de mémoire: si vous n’utilisez pas le cache dans votre exécution, vous pouvez configurer l’étincelle pour qu’elle utilise cet espace de stockage afin de travailler avec l’exécution afin d’éviter en partie l’erreur de MOO. Comme vous pouvez le voir dans la documentation de spark:

Cette conception assure plusieurs propriétés souhaitables. Premièrement, les applications qui n'utilisent pas la mise en cache peuvent utiliser tout l'espace pour l'exécution, évitant ainsi les pertes de disque inutiles. Deuxièmement, les applications qui utilisent la mise en cache peuvent réserver un espace de stockage minimal (R) où leurs blocs de données sont immunisés contre l'expulsion. Enfin, cette approche fournit des performances prêtes à l'emploi raisonnables pour une variété de charges de travail sans nécessiter l'expertise de l'utilisateur sur la répartition de la mémoire en interne.

Mais comment pouvons-nous utiliser cela?

Vous pouvez modifier certaines configurations. Ajoutez la configuration MemoryOverhead à votre appel de travail, mais envisagez également d'ajouter ceci: spark.memory.fraction changez pour 0.8 ou 0.85 et réduisez le spark.memory.storageFraction à 0.35 ou 0.2.

D'autres configurations peuvent aider, mais il faut vérifier dans votre cas. Voir toutes ces configurations ici .

Maintenant, ce qui aide dans mon cas.

J'ai un cluster avec 2,5K travailleurs et 2,5 To de RAM. Et nous étions confrontés à une erreur OOM comme la vôtre. Nous augmentons simplement le spark.yarn.executor.memoryOverhead à 2048. Et nous activons l’allocation dynamique . Et lorsque nous appelons le poste, nous ne configurons pas la mémoire des travailleurs, nous laissons cette décision à l'étincelle. Nous venons de définir les frais généraux.

Mais pour certains tests pour mon petit cluster, changer la taille de la mémoire d'exécution et de stockage. Cela a résolu le problème.

7
Thiago Baldim

Essayez la répartition. Cela fonctionne dans mon cas.

La base de données n'était pas si grosse au tout début lorsqu'elle a été chargée avec write.csv(). Le fichier de données représentait environ 10 Mo, comme il se doit, disons totalement plusieurs mémoires de 100 Mo pour chaque tâche de traitement de l'exécuteur .. J'ai vérifié que le nombre de partitions était égal à 2 à la fois. une boule de neige lors des opérations suivantes, rejoignant d'autres tables et ajoutant de nouvelles colonnes. Et puis je suis tombé sur le problème de dépassement de mémoire à une certaine étape ..__J'ai vérifié le nombre de partitions, il en restait 2, dérivé du cadre de données d'origine je suppose . Alors j'ai essayé de le repartitionner à la très début, et il n'y avait plus de problème.

Je n'ai pas encore lu beaucoup de documents sur Spark et YARN. Ce que je sais, c'est qu'il y a des exécuteurs dans les nœuds. Un exécuteur peut gérer plusieurs tâches en fonction des ressources. Je suppose qu'une partition serait atomiquement mise en correspondance avec une tâche. Et son volume détermine l'utilisation des ressources. Spark ne pourrait pas la couper si une partition devenait trop grosse.

Une stratégie raisonnable consiste à déterminer d’abord les nœuds et la mémoire de conteneur (10 Go ou 5 Go). Idéalement, les deux pourraient servir à n'importe quel travail de traitement de données, juste une question de temps. Étant donné le paramètre de mémoire de 5 Go, la ligne raisonnable pour une partition que vous trouvez, disons, est 1000 après le test (cela n'échouera pas pendant le traitement), nous pourrions le faire comme le pseudo code suivant:

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)

J'espère que ça aide!

2
韦光正

J'ai eu le même problème sur un petit cluster exécutant un travail relativement petit sur spark 2.3.1 . Le travail lit le fichier de parquet, supprime les doublons à l'aide de groupBy/agg/first, puis trie et écrit le nouveau parquet. Il a traité 51 Go de fichiers de parquet sur 4 nœuds (4 vcores, 32 Go de RAM).

Le travail échouait constamment sur la phase d'agrégation. J'ai écrit bash l'utilisation de la mémoire des exécuteurs surveillant les scripts et découvert que, au milieu de la phase, un exécutant aléatoire prend la double mémoire pendant quelques secondes. Lorsque j’ai corrélé l’heure de ce moment avec les journaux du GC, elle a été comparée à la totalité du GC qui vide une grande quantité de mémoire.

J'ai enfin compris que le problème est en quelque sorte lié à la GC. ParallelGC et G1 sont constamment à l'origine de ce problème, mais ConcMarkSweepGC améliore la situation. Le problème n'apparaît qu'avec un petit nombre de partitions. J'ai exécuté le travail sur EMR où OpenJDK 64-Bit (build 25.171-b10) était installé. Je ne connais pas la cause première du problème, cela pourrait être lié à la JVM ou au système d'exploitation. Mais, dans mon cas, il n’est certainement pas lié à l’utilisation de tas ou d’off-tas.

UPDATE1

Essayé Oracle HotSpot, le problème est reproduit.

0
Avseiytsev Dmitriy