Blog
Spark-Speicherverwaltung Teil 1 - Gehen Sie an die Grenzen

Einer der Engpässe von Hadoop Map-Reduce-Anwendungen besteht darin, dass die Daten zwischen den einzelnen Phasen irgendwo persistent gespeichert werden müssen - was viel Zeit für die Durchführung von E/A-Operationen vergeudet.
Eines der Verkaufsargumente von Spark ist, dass es 10-100x weniger Zeit benötigt, um eine ähnliche Aufgabe wie Hadoop Map-Reduce zu erledigen. Der Trick besteht darin, Daten zuverlässig im Arbeitsspeicher zu speichern. Dadurch wird der wiederholte Zugriff darauf (z.B. für iterative Algorithmen) unvergleichlich schneller.
Effiziente Speichernutzung ist entscheidend für eine gute Leistung, aber auch das Gegenteil ist der Fall - ineffiziente Speichernutzung führt zu schlechter Leistung.
In jeder Big Data-Anwendung ist der Speicher eine äußerst wichtige Ressource. Bevor wir weitermachen, sollten wir uns einen Überblick über die möglichen Auswirkungen der Wahl des richtigen Speichers für unsere Daten verschaffen.
Jim Gray hat eine interessante Art, dies zu erklären:
Er vergleicht die Art und Weise, wie unser Gehirn auf Informationen zugreift (über neurologische Pfade), mit der Art und Weise, wie ein Prozessor Daten aus seinen Registern liest (er tut dies fast augenblicklich). Ähnlich können wir das Lesen von Daten von einer Festplatte mit einer zweijährigen Reise zum Pluto vergleichen.
(Heutzutage würden wir wahrscheinlich moderne SSD-Laufwerke in Betracht ziehen, aber die Änderung einer Größenordnung würde keinen großen Unterschied machen - wir würden nur zu einem näheren Planeten gelangen).
Der Punkt ist, dass die Kosten für den Zugriff auf Daten auf einer Festplatte um einige Größenordnungen höher sind - wir müssen so viele Dinge wie möglich im Speicher verarbeiten.
Aber nichts ist umsonst und funktioniert von Anfang an perfekt.
Um optimierte Spark-Aufträge zu erhalten, müssen die Entwickler einige Zeit damit verbringen, zu verstehen, wie der Speicher verwaltet wird und wie man die richtigen Anpassungen vornimmt.
Es gibt drei Hauptargumente dafür, wie man es schlichten kann:
- zwischen Ausführung und Speicherung
- über Aufgaben hinweg, die parallel laufen
- über Operatoren hinweg, die innerhalb derselben Aufgabe laufen
Bevor wir die einzelnen Fälle analysieren, lassen Sie uns den Testamentsvollstrecker betrachten.
Übersicht über den Executor-Speicher
Ein Executor ist der JVM-Prozess der Spark-Anwendung, der auf einem Arbeitsknoten gestartet wird. Er führt Aufgaben in Threads aus und ist für die Aufbewahrung relevanter Datenpartitionen verantwortlich. Jeder Prozess hat einen zugewiesenen Heap mit verfügbarem Speicher (Executor/Driver).
Beispiel: Bei den Standardkonfigurationen (spark.executor.memory=1GB, spark.memory.fraction=0.6) stehen einem Executor etwa 350 MB für Ausführungs- und Speicherregionen (einheitliche Speicherregion) zur Verfügung. Die übrigen 40% sind für die Speicherung verschiedener Metadaten, Benutzerdatenstrukturen, die Absicherung gegen OOM-Fehler usw. reserviert. Beachten Sie auch, dass es einen fest kodierten Teil des sogenannten reservierten Speichers (300 MB * 1,5) gibt, der für die Speicherung interner Spark-Objekte verwendet wird. Die genauen Berechnungen sind oft nicht ganz intuitiv - ausführliche Beispiele finden Sie unter diesem und jenem Thema.
Spark-Tasks arbeiten in zwei Hauptspeicherbereichen:
- Ausführung - verwendet für Shuffles, Joins, Sortierungen und Aggregationen
- Speicher - wird zum Zwischenspeichern von Datenpartitionen verwendet
Der Ausführungsspeicher ist tendenziell "kurzlebiger" als der Speicher. Er wird sofort nach jeder Operation gelöscht, um Platz für die nächsten Operationen zu schaffen.
Was die Speicherung anbelangt, so gibt es zwei Hauptfunktionen für die Persistenz von Daten - die RDDs cache() und persist().
cache() ist ein Alias für persist(StorageLevel.MEMORY_ONLY)
Wie Sie später bei RDD sehen werden, können Partitionen im Speicher oder auf der Festplatte existieren - im gesamten Cluster, zu jedem beliebigen Zeitpunkt (Sie können dies auf der Registerkarte Speicher in der Spark-Benutzeroberfläche sehen).
Daher kann die Verwendung von cache()für Datensätze, die größer sind als der Speicher des Clusters, gefährlich sein. Jede RDD-Partition muss möglicherweise gelöscht und anschließend neu erstellt werden (was teuer ist). In solchen Fällen wäre es besser, persist() mit einer geeigneten Option zu verwenden.
Off-Heap
Auch wenn die beste Leistung erzielt wird, wenn ausschließlich im On-Heap-Speicher gearbeitet wird, bietet Spark auch die Möglichkeit, für bestimmte Operationen Off-Heap-Speicher zu verwenden.
Off-Heap bezieht sich auf Objekte (serialisiert zu einem Byte-Array), die vom Betriebssystem verwaltet werden, aber außerhalb des Prozess-Heaps im Native Memory gespeichert sind (daher werden sie nicht vom Garbage Collector verarbeitet). Der Zugriff auf diese Daten ist etwas langsamer als der Zugriff auf den On-Heap-Speicher, aber immer noch schneller als das Lesen/Schreiben von einer Festplatte. Der Nachteil ist, dass der Benutzer sich manuell um die Verwaltung des zugewiesenen Speichers kümmern muss.
Ein häufiges Problem bei größeren Speicherkonfigurationen ist, dass die Anwendung aufgrund von GC-Scans (manchmal auch als "GC-Stürme" bezeichnet) zum Einfrieren neigt. Der Hauptvorteil der Aktivierung von Off-Heap-Speicher besteht darin, dass wir dieses Problem durch die Verwendung von nativem Systemspeicher (der nicht von der JVM überwacht wird) entschärfen können.
Die Off-Heap-Speichernutzung ist für Ausführungs- und Speicherregionen verfügbar (seit Apache Spark 1.6 bzw. 2.0).
Eigenschaften
Das Wesentliche:
spark.executor.memory- gibt den Prozessspeicher-Heap des Executors an (Standardwert 1 GB)spark.driver.memory- gibt den Prozessspeicher-Heap des Treibers an (Standardwert 1 GB)spark.memory.fraction- ein Bruchteil des Heap-Speicherplatzes (minus 300 MB * 1,5), der für Ausführungs- und Speicherregionen reserviert ist (Standardwert 0,6)
Off-Heap:
spark.memory.offHeap.enabled- die Option, Off-Heap-Speicher für bestimmte Operationen zu verwenden (Standardeinstellung false)spark.memory.offHeap.size- die Gesamtmenge an Speicher in Bytes für die Off-Heap-Zuweisung. Es hat keinen Einfluss auf die Nutzung des Heap-Speichers. Stellen Sie also sicher, dass Sie die Gesamtgrenzen Ihres Executors nicht überschreiten (Standardwert 0).
Fortsetzung folgt...
Im nächsten Teil dieses Artikels werden wir uns drei Speicherprobleme ansehen, mit denen Spark zu kämpfen hat, und wie Spark SQL Sie dabei unterstützen kann.
Quellen
Lagerung: Alternate futures von Jim Gray
Unsere Ideen
Weitere Blogs
Contact



