web-dev-qa-db-de.com

"Container wurde von YARN wegen Überschreitung der Speichergrenzwerte abgebrochen. 10,4 GB 10,4 GB physischer Arbeitsspeicher" in einem EMR-Cluster mit 75 GB Speicher

Ich verwende einen Spark-Cluster mit 5 Knoten auf AWS EMR mit jeweils einer Größe von m3.xlarge (1 Master 4 Slaves). Ich habe erfolgreich eine 146-MB-bzip2-komprimierte CSV-Datei durchlaufen und erhielt ein perfekt aggregiertes Ergebnis.

Jetzt versuche ich, eine ~ 5GB bzip2 CSV-Datei in diesem Cluster zu verarbeiten, erhalte jedoch diese Fehlermeldung:

16/11/23 17:29:53 WARN TaskSetManager: Task 49.2 in Stufe 6.0 (TID xxx, xxx.xxx.xxx.compute.internal) ist verloren gegangen: ExecutorLostFailure (Executor 16 wurde durch eine der ausgeführten Aufgaben beendet) Ursache: Container von YARN wegen Überschreitung der Speichergrenzen getötet. 10,4 GB physischer 10,4 GB-Speicher verwendet. Erwägen Sie die Verbesserung von spark.yarn.executor.memoryOverhead.

Ich bin verwirrt, warum ich ein ~ 10,5 GB Speicherlimit für einen ~ 75 GB-Cluster (15 GB pro 3m.xlarge Instanz) bekomme ...

Hier ist meine EMR-Konfiguration:

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

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

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

  ]
 }
]

Durch das Festlegen der maximizeResourceAllocation-Eigenschaft sollte EMR angewiesen werden, Spark so zu konfigurieren, dass alle im Cluster verfügbaren Ressourcen vollständig genutzt werden. Das heißt, ich sollte ~ 75 GB Speicher verfügbar haben ... Warum erhalte ich einen ~ 10,5 GB Speichergrenzwertfehler? Hier ist der Code, den ich ausgeführt habe:

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)

Im Grunde nichts weiter als Fenster und eine Gruppe, um die Daten zu aggregieren.

Es fängt mit einigen dieser Fehler an und erhöht den Betrag des gleichen Fehlers.

Ich habe versucht, spark-submit mit --conf spark.yarn.executor.memoryOverhead auszuführen , aber das scheint das Problem auch nicht zu lösen.

24
lauri108

Ich fühle deinen Schmerz..

Bei Spark auf YARN hatten wir ähnliche Probleme, wenn der Speicher knapp wurde. Wir haben fünf 64-GB- und 16-Kern-VMs, und unabhängig davon, was wir für spark.yarn.executor.memoryOverhead festgelegt haben, konnten wir einfach nicht genug Speicher für diese Aufgaben bekommen - sie würden schließlich sterben, egal wie viel Speicher wir ihnen geben würden. Und dies als relativ einfache Spark-Anwendung, die dazu geführt hat.

Wir haben herausgefunden, dass die physische Arbeitsspeicherauslastung der VMs recht niedrig war, die Nutzung des virtuellen Arbeitsspeichers jedoch extrem hoch war. Wir setzen yarn.nodemanager.vmem-check-enabled in yarn-site.xml auf false, und unsere Container wurden nicht mehr abgebrochen, und die Anwendung schien erwartungsgemäß zu funktionieren.

Durch Nachforschungen habe ich die Antwort gefunden, warum dies hier geschieht: https://www.mapr.com/blog/best-practices-yarn-resource-management

Da auf Centos/RHEL 6 aufgrund des Verhaltens des Betriebssystems eine aggressive Zuweisung von virtuellem Speicher vorliegt, sollten Sie die Überprüfung des virtuellen Speichers deaktivieren oder den Wert für das yarn.nodemanager.vmem-pmem-Verhältnis auf einen relativ höheren Wert erhöhen. 

Diese Seite hatte einen Link zu einer sehr nützlichen Seite von IBM: https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_memory_usage?lang=de

Zusammenfassend hat glibc> 2.10 seine Speicherzuordnung geändert. Auch wenn riesige Mengen an virtuellem Speicher nicht das Ende der Welt sind, funktionieren sie nicht mit den Standardeinstellungen von YARN.

Anstatt yarn.nodemanager.vmem-check-enabled auf false zu setzen, können Sie auch spielen, indem Sie die MALLOC_ARENA_MAX-Umgebungsvariable in hadoop-env.sh auf eine niedrige Zahl setzen.

Ich empfehle, beide Seiten durchzulesen - die Informationen sind sehr praktisch.

37
Duff

Wenn Sie nicht spark-submit verwenden und nach einem anderen Weg suchen, um den durch Duff genannten Parameter yarn.nodemanager.vmem-check-enabled anzugeben, gibt es zwei weitere Möglichkeiten:

Methode 2

Wenn Sie eine JSON-Konfigurationsdatei verwenden (die Sie an die AWS-CLI oder Ihr boto3-Skript übergeben), müssen Sie die folgende Konfiguration hinzufügen:

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

Methode 3

Wenn Sie die EMR-Konsole verwenden, fügen Sie die folgende Konfiguration hinzu:

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

Sehen,

Ich hatte das gleiche Problem in einem riesigen Cluster, das ich gerade arbeite. Das Problem wird nicht dadurch gelöst, dass dem Arbeiter Speicher hinzugefügt wird. In der Prozessaggregation verwendet Spark manchmal mehr Speicher als zuvor, und die Funkenjobs beginnen damit, Off-Heap-Speicher zu verwenden.

Ein einfaches Beispiel ist:

Wenn Sie über ein Dataset verfügen, das Sie reduceByKey benötigen, werden in einem Worker manchmal mehr Daten zusammengefasst als in anderen, und wenn diese Daten den Speicher eines Workers übersteigen, wird diese Fehlermeldung angezeigt.

Das Hinzufügen der Option spark.yarn.executor.memoryOverhead wird Ihnen helfen, wenn Sie 50% des für den Worker verwendeten Arbeitsspeichers festlegen (nur für den Test und wenn es funktioniert, können Sie mit mehr Tests weniger hinzufügen).

Sie müssen jedoch verstehen, wie Spark mit der Speicherzuordnung im Cluster arbeitet:

  1. Die üblichere Art, wie Spark 75% des Maschinenspeichers belegt. Der Rest geht an SO.
  2. Spark hat zwei Arten Speicher während der Ausführung. Ein Teil ist für die Ausführung und der andere Teil ist die Speicherung. Die Ausführung wird für Shuffles, Joins, Aggregationen und usw. verwendet. Der Speicher wird zum Zwischenspeichern und Weiterleiten von Daten im Cluster verwendet.

Eine gute Sache bei der Speicherzuordnung: Wenn Sie bei der Ausführung keinen Cache verwenden, können Sie den Funken so einstellen, dass er diesen Speicherbereich für die Ausführung verwendet, um den OOM-Fehler teilweise zu vermeiden. Wie Sie dies in der Funken-Dokumentation sehen können:

Dieses Design gewährleistet mehrere wünschenswerte Eigenschaften. Erstens können Anwendungen, die keine Zwischenspeicherung verwenden, den gesamten Speicherplatz für die Ausführung verwenden, wodurch unnötige Festplattenüberschüsse vermieden werden. Zweitens können Anwendungen, die Caching verwenden, einen minimalen Speicherplatz (R) reservieren, wenn ihre Datenblöcke nicht geräumt werden können. Schließlich bietet dieser Ansatz eine vernünftige Out-of-the-Box-Leistung für eine Vielzahl von Workloads, ohne dass ein Benutzerwissen über die interne Aufteilung des Speichers erfordert.

Aber wie können wir das nutzen?

Sie können einige Konfigurationen ändern, die MemoryOverhead-Konfiguration zu Ihrem Job-Aufruf hinzufügen. Sie sollten jedoch Folgendes hinzufügen: spark.memory.fraction für 0,8 oder 0,85 ändern und den spark.memory.storageFraction auf 0,35 oder 0,2 reduzieren.

Andere Konfigurationen können hilfreich sein, müssen jedoch in Ihrem Fall überprüft werden. Siehe all diese Konfiguration hier .

Nun, was hilft in meinem Fall.

Ich habe einen Cluster mit 2.5K Workern und 2.5 TB RAM. Und wir standen vor einem OOM-Fehler wie Ihrem. Wir erhöhen nur den spark.yarn.executor.memoryOverhead auf 2048. Und wir aktivieren die dynamische Zuordnung . Und wenn wir den Job anrufen, legen wir den Arbeitern nicht das Gedächtnis fest, wir überlassen es dem Spark, um zu entscheiden. Wir haben gerade den Overhead eingestellt.

Aber für einige Tests für meinen kleinen Cluster, ändern Sie die Größe der Ausführung und des Speichers. Das hat das Problem gelöst.

7
Thiago Baldim

Versuchen Sie die Aufteilung. Es funktioniert in meinem Fall.

Der Datenrahmen war zu Beginn nicht so groß, als er mit write.csv() geladen wurde. Die Datendatei betrug etwa 10 MB, wie gesagt, für jede Verarbeitungsaufgabe im Executor ..__ müssen mehrere 100 MB-Speicher erforderlich sein. Ich habe die Anzahl der Partitionen zu der Zeit auf 2 geprüft. Dann wuchs es Ein Schneeball während der folgenden Vorgänge verbindet sich mit anderen Tabellen und fügt neue Spalten hinzu. Und dann stieß ich in einem bestimmten Schritt in den Speicher, der die Grenzwerte überschritt .. Ich überprüfte die Anzahl der Partitionen, es waren immer noch 2, abgeleitet aus dem ursprünglichen Datenrahmen, denke ich Ganz am Anfang, und es gab kein Problem mehr.

Ich habe noch nicht viel Material über Spark und YARN gelesen. Was ich weiß ist, dass es in Knoten Knoten gibt. Ein Executor kann abhängig von den Ressourcen viele Aufgaben erledigen. Meine Vermutung ist, dass eine Partition atomar einer Aufgabe zugeordnet wäre. Und sein Volumen bestimmt die Ressourcennutzung. Spark konnte es nicht schneiden, wenn eine Partition zu groß wird.

Eine sinnvolle Strategie besteht darin, zuerst die Knoten und den Containerspeicher zu ermitteln, entweder 10 GB oder 5 GB. Im Idealfall könnten beide Datenverarbeitungsaufgaben erledigen, nur eine Frage der Zeit. Bei einer Einstellung von 5 GB ist die vernünftige Zeile für eine Partition, die Sie finden, zum Beispiel 1000 nach dem Testen (es werden keine Schritte während der Verarbeitung fehlschlagen). Wir können dies als den folgenden Pseudocode ausführen:

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)

Ich hoffe es hilft!

2
韦光正

Ich hatte das gleiche Problem mit einem kleinen Cluster, auf dem relativ kleiner Job auf Spark 2.3.1 ausgeführt wurde .. _ Der Job liest die Parkettdatei, entfernt Duplikate mit groupBy/agg/und sortiert dann zuerst neues Parkett. Es wurden 51 GB Parkettdateien auf 4 Knoten (4 Vcores, 32 GB RAM) verarbeitet.

In der Aggregationsphase fiel der Job ständig aus. Ich schrieb den Speicherverbrauch von bash script watch executors und stellte fest, dass ein zufälliger Executor in der Mitte der Stufe für einige Sekunden doppelten Speicher belegt. Wenn ich die Zeit dieses Momentes mit GC-Protokollen korrelierte, stimmte es mit der vollen GC überein, die viel Speicherplatz leerte.

Endlich habe ich verstanden, dass das Problem irgendwie mit GC zusammenhängt. ParallelGC und G1 verursachen dieses Problem ständig, aber ConcMarkSweepGC verbessert die Situation. Das Problem wird nur bei einer geringen Anzahl von Partitionen angezeigt. Ich habe den Job auf EMR ausgeführt, wo OpenJDK 64-Bit (build 25.171-b10) installiert wurde. Ich kenne die Hauptursache des Problems nicht, es könnte sich um eine JVM oder ein Betriebssystem handeln. In meinem Fall hängt dies jedoch definitiv nicht mit der Heap- oder Off-Heap-Nutzung zusammen.

UPDATE1

Versucht Oracle HotSpot, das Problem wird reproduziert.

0