Ausführliche Interpretation des Spark Committers: Apache Spark Native Engine

Dieser Artikel stammt von You Xiduo, einem Big-Data-Technologieexperten bei NetEase Hangyan, Apache Kyuubi PMC-Mitglied und Apache Spark-Committer. Der Inhalt konzentriert sich hauptsächlich auf Apache Spark und Native Engine. Er erklärt, was Native Engine ist und warum wir Native Engine erstellen sollten und wie man eine native Engine erstellt.

Vorwort

Apache Spark ist eine verteilte Computer-Engine, die auf der JVM-Sprache basiert. Die Ausführungsleistung seiner einzelnen SQL-Operatoren wie Aggregation, Join usw. wurde seit langem nicht verbessert. Die Hauptquelle für Leistungssteigerungen bei der Migration von Spark2 zu Spark3 ist AQE. AQE ist eigentlich ein Framework zur Optimierung von Ausführungsplänen und zum Lesen von Shuffle-Daten und hat nichts mit der Operatorleistung selbst zu tun, sodass auch der Optimierungseffekt von AQE vorhanden ist eine Obergrenze. Im Zusammenhang mit Kostensenkung und Effizienzsteigerung werden die Ressourcen von Rechenclustern immer knapper und Basisgarantien ohne zusätzliche Maschinen sind zur Norm geworden. Es wird jedoch immer mehr Rechenaufgaben geben, sodass Sprachen auf niedrigerer Ebene wie C und C++ werden verwendet. Für Rust entstand die Notwendigkeit, Native Engine zu implementieren, um die Rechenleistung von Spark SQL zu beschleunigen. Tatsächlich gibt es im OLAP-Ökosystem bereits viele erfolgreiche Native-Computing-Engines wie ClickHouse, Doris usw. Generell bedeutet Native Engine auch, dass es sich um eine Engine handelt, die auf vektorisierten Berechnungen basiert.

01 Native Engine

Warum brauchen wir Native Engine?

Mittlerweile sind SSDs in Big-Data-Clustern weit verbreitet. Beispielsweise sind die Shuffle-Festplatten, die wir in Spark verwenden, alle SSDs oder noch besser NVMe im Kerncluster, wodurch IO kein Engpass mehr bei der Datenverarbeitung darstellt. Der Engpass wird nie verschwinden, er wird sich nur von einem Indikator zum anderen bewegen. Die meisten aktuellen Rechenengpässe werden durch unzureichende Rechenressourcen verursacht, d. h. CPU, und natürlich macht Speicher manchmal einen Teil aus.

Abbildung 1 JVM vs. C++

Was wäre also, wenn wir weiterhin rund um die JVM optimieren würden? Natürlich ist es möglich, aber es wird sehr, sehr schwierig sein, und es gibt in der Branche keine sehr ausgereiften Lösungen und Projekte.

Beispielsweise weist die aktuelle Optimierungslösung von Spark, Codegen, viele Einschränkungen der JVM auf. Beispielsweise darf der Bytecode einer einzelnen Methode 64 KB nicht überschreiten, JIT optimiert keine Methoden, die 8 KB Bytecode überschreiten usw., was manchmal einen Teil davon ausmacht. In Berechnungsszenarien mit großen Tabellen kann es leicht zu Leistungseinbußen kommen, weshalb Spark-Codegen eine Begrenzung der Anzahl der Felder hat. Andererseits ist es für Entwickler auch schwierig, einige Funktionen moderner CPUs auf der JVM zu nutzen, wie z. B. SIMD, eine CPU-Funktion, die Berechnungen mit einem einzelnen Befehl, mehreren Datenströmen, dem gemeinsamen AVX-Befehlssatz, AVX2 usw. beschleunigt sogar der modernere AVX512. Durch die Entwicklung auf Basis von C++ lässt sich dieses Ziel leicht erreichen: Sie müssen lediglich eine C++-Bibliothek aufrufen und dann beim Kompilieren die entsprechende Befehlssatzoptimierung aktivieren. Und in Bezug auf die Speicherverwaltung ist die Effizienz der JVM-Garbage Collection auch ein Faktor, der sich auf die Aufgabenleistung auswirkt. Im Allgemeinen ist es eine gute Situation, dass die von GC belegte CPU-Zeit innerhalb von 10 % der gesamten CPU-Zeit liegt, aber trotzdem 10 % der gesamten CPU-Zeit werden verschwendet. % Rechenleistung, ganz zu schweigen davon, dass die Leistung bei größerem Speicher, z. B. einem Heap mit mehr als 32 GB, noch schlechter wird.

Daher ist die Verwendung niedrigerer Sprachen zur Entwicklung der Native Engine zur Beschleunigung der Rechenleistung in der Branche zum Mainstream geworden.

Aktuelle Situation der nativen Engine

Abbildung 2 Spark Native Engine

Im Spark-Ökosystem gibt es die Stimme von Native Engine tatsächlich schon seit einiger Zeit. Das erste Produkt sollte Databricks' Photon sein, das bereits 2018 entwickelt wurde, aber selbst die Muttergesellschaft von Spark brauchte 5 Jahre, um es zu entwickeln. Es dauerte einige Zeit Forschung und Entwicklung wurden offiziell kommerziell verfügbar gemacht, was den enormen Aufwand an technischer Arbeit zeigt. Leider ist Photon kein Open-Source-Projekt und wir können sein Design und seine Implementierung nur anhand der veröffentlichten Artikel analysieren.

Meta hat sein eigenes Velox als Open-Source-Version bereitgestellt, das als in C++ implementiertes SQL Engine SDK verstanden werden kann. Es bietet viele Bibliotheken rund um SQL, wie z. B. logische/physikalische Ausführungspläne, Funktionen, Vektordatenstrukturen, Parquet-Lesen und -Schreiben sowie Speicherpoolverwaltung , usw. Entwickler können ihre eigenen SQL-Engines auf Basis von Velox implementieren. Die ursprüngliche Absicht von Velox bestand darin, die für JVM entwickelte Rechen-Engine zu beschleunigen. Das Presto-Team von Meta entwickelt eine Native Engine auf Basis von Velox. Das Intel-Team hat auch das auf Spark + Velox basierende Projekt Gluten erstellt und als Open-Source-Lösung bereitgestellt, um den Photon von Databricks zu vergleichen. Sowohl Photon als auch Gluten können dies tun: Benutzer können eine zweifache Verbesserung der Spark SQL-Aufgabenleistung erzielen, ohne eine einzige Codezeile zu ändern, was sehr aufregend ist.

Zusätzlich zu diesen beiden Projekten gibt es auch Apache Arrow Datafusion, eine auf Rust basierende Native Engine. Kuaishous Open-Source-Blaze basiert auf der von ihm entwickelten Spark Native Engine. Die Community ist nicht mehr aktiv und es gibt relativ wenige Unternehmen Teilnahme am Land. Wir bleiben in einer konservativen abwartenden Haltung (PS scheint in letzter Zeit wieder aktiv zu sein).

02 Einführung in Gluten

Gluten ist ein Open-Source-Projekt, das von Intel und Kyligence initiiert wurde. Gluten-Architektur Gluten ist im Wesentlichen ein Projekt, das auf der Spark-Plug-In-Schnittstelle basiert. Es kann auch als große Spark-Plug-In-Bibliothek verstanden werden, sodass kein Eingriff in die Spark-Codebasis erforderlich ist. Dies ist ein sehr gutes Design. Gleiches gilt für Spark-Downstream-Projekte wie Iceberg und Delta. Dadurch wird die Stabilität des Spark-Kernels sichergestellt.

Abbildung 3 Übersicht über Gluten

Wie in Abbildung 3 dargestellt, ist Gluten eine vektorisierte Ausführungs-Engine, die mehrere Native-Engine-Backends unterstützt . Die aktuelle Community entwickelt sich hauptsächlich um die beiden Backends Velox und ClickHouse. In Zukunft könnte sie Apache Data Fusion oder andere hervorragende Open-Source-Projekte integrieren. Sein Kernprinzip besteht aus zwei Teilen:

  • Übergeben Sie das Ausführungsplanprogramm und realisieren Sie die Übertragung des Ausführungsplans zwischen JVM und Native über Substrait. Substrait kann als sprachübergreifendes Serialisierungs-SDK für relationale Objekte basierend auf Google Protobuf verstanden werden. Velox unterstützt auch Substrait als Ausführungsplaneingabe.
  • Übertragen Sie Daten über das von Spark SQL bereitgestellte Vektorisierungsframework. Die Community-Version von Spark SQL bietet standardmäßig eine zeilenbasierte Implementierung. Das Objekt der Operatoroperation ist Row. Im Vektorisierungsframework ist das Objekt der Operatoroperation ColumnarBatch. Ein ColumnarBatch kann mehrere Datenzeilen enthalten. Jedes Feld in Die ColumnarBatch-Datenstruktur ist Column Vector

Gluten-Plugin-Bibliothek

Abbildung 4 Gluten-Plugin

Wie in Abbildung 4 dargestellt, wird das erste Gluten-Plugin basierend auf dem offenen  Treiber-Plugin  und dem Executor-Plugin von Spark implementiert . Über dieses Portal verbindet Gluten seine Fähigkeiten mit Spark. 1) Laden Sie die dynamische Linkbibliothek über die Java-Schnittstelle, die unter dem Linux-Betriebssystem .so ist, z. B. gluten.so, velox.so, Arrow.so usw. 2) Spark-Plugin dynamisch laden:  

  • SQL-Erweiterung, Ausführungsplan konvertieren, Spark-Plan in Gluten-Plan über die von Spark bereitgestellte Spaltenregel umwandeln
  • CachedBatchSerializer unterstützt RDD Columnar Cache und die entsprechende Nutzungsschnittstelle ist dataset.cache 
  • ShuffleManager ist ein Columnar Shuffle Exchange 
  • Gluten-Benutzeroberfläche, zeigt C++/Java-Kompilierungsinformationen, Informationen zum Gluten-Ausführungsplan-Rollback usw. an.

Einer der größten Vorteile der Verwendung der Gluten-Ausführungsschicht als native Engine auf Basis von Spark besteht darin, dass das Rad nicht neu erfunden werden muss und Sie direkt die kampferprobte Codebasis von Spark und zukünftige Community-Iterationen genießen können. Zum Beispiel ein robustes Planungs-Framework, spekulative Ausführung, Wiederholungsversuche bei Aufgaben-/Stufenfehlern mit unterschiedlicher Granularität; umfangreiche ökologische Verbindungen, wie z. B. YARN/K8s. Und wir müssen uns nur auf die SQL-Leistung konzentrieren, die uns wichtig ist.

Abbildung 5 Gluten-Ausführungsmodell

Wie in Abbildung 5 dargestellt, folgt Gluten vollständig dem Designkonzept von Spark und Task wird in einem Thread als kleinster Ausführungseinheit ausgeführt. Gluten übergibt Ausführungsplanfragmente über JNI-Aufrufe. Nachdem Native sie erhalten hat, kann es seine eigene Native Task erstellen und diese im aktuellen Thread ausführen. Das heißt, für jede Spark-Aufgabe gibt es während der Ausführung eine entsprechende native Aufgabe.

Welche Probleme löst Gluten?

Ich habe bereits einige Grundprinzipien und die Implementierung von Gluten geteilt, sodass Sie vielleicht denken, dass Gluten anscheinend nicht viel bewirkt und es nicht schwierig ist, die Spark Native Engine zu implementieren. Werfen wir einen genaueren Blick darauf, welche Probleme Gluten löst.

Umsetzung des Ausführungsplans

Abbildung 6 Umsetzung des Ausführungsplans

Wie oben erwähnt, konvertiert Gluten Ausführungspläne durch das Einfügen von Spaltenregeln in die Spark-SQL-Erweiterung und gleicht nur die physischen Ausführungspläne von Spark ab, um die Komplexität der Konvertierung zu verringern. Dieser Konvertierungsprozess ist vollständig kompatibel mit dem Spark AQE-Framework . Wir wissen, dass AQE während des laufenden Prozesses Stage als Segmentierungsgranularität verwendet und den neuen übergeordneten Ausführungsplan basierend auf dem untergeordneten Ausführungsplan ständig neu optimiert. Daher jedes Mal, wenn er eintritt In einer neuen Phase wird AQE der Säulenregel ein neues Fragment des Spark-Ausführungsplans zuführen. Gluten verfügt über ein WholeStageTransformer-Konzept, das der Optimierungsidee von Spark WholeStageCodegen ähnelt. WholeStageTransformer konvertiert das von der gesamten Bühne generierte Ausführungsplanfragment mithilfe von Substrait in einen nativen Ausführungsplan, sodass die gesamte Bühne auf Native ausgeführt werden kann Engine, sodass sie während des Ausführungsprozesses nicht mehr benötigt wird. Erzeugt zusätzliche Dateninteraktionen mit der JVM.

Ausführungsplan für JVM + native Koexistenz

Abbildung 7 Koexistenz-Ausführungsplan und Rollback

Natürlich gehen wir davon aus, dass die gesamte Stage auf der Native Engine läuft, aber in der Realität wird es definitiv nicht so ideal sein. Denn selbst bei Hive und Spark, die beide auf JVM-Basis implementiert sind, sind die Ergebnisse der Ausführung desselben SQL manchmal unterschiedlich. Wenn es um Native Engine geht, wird dieser Unterschied nur noch verstärkt. Ein typisches Beispiel ist Hive UDF. Native kann es nicht ausführen. Wie kann ich C++ verwenden, um den in Java implementierten Code auszuführen? Wenn Gluten dann mit einem solchen Operator konfrontiert wird, der in Native nicht unterstützt wird, greift er für die Ausführung auf Spark zurück. Wie in Abbildung 7 dargestellt, ist die linke Seite ein Ausführungsplan für die Koexistenz von JVM und Native. Wenn eine Stufe sowohl Operatoren enthält, die auf der JVM ausgeführt werden, als auch Operatoren, die auf Native ausgeführt werden, fügt Gluten einen ColumnarToRow- oder RowToColumnar-Operator zwischen den beiden Operatoren hinzu, um die Datenstruktur zu überbrücken. Es ist zu beachten, dass die Kosten dieses Operators höher sind als die von Spark, da die beiden Operatoren in Gluten auch das Kopieren von Daten zwischen OffHeap und OnHeap umfassen.

Das Problem tritt erneut auf. Wenn in einer Stufe mehrere Operatoren vorhanden sind, die wiederholt zwischen JVM und Native wechseln, gibt es viele ColumnarToRow- und RowToColumnar-Operatoren, und die Leistung dieser Stufe wird höchstwahrscheinlich abnehmen. Daher unterstützt Gluten auch das granulare Rollback der Stufe. Wenn in einer Stufe mehrere ColumnarToRow-Operatoren vorhanden sind, wird die gesamte Stufe zur Ausführung auf die JVM zurückgesetzt. Dadurch kann sichergestellt werden, dass die Rechenleistung dieser Stufe mit der von Spark übereinstimmt und kein Problem mit Leistungseinbußen auftritt. Daraus können wir auch erkennen, dass der Anteil des Bediener-Rollbacks einer der Schlüsselfaktoren ist, die die Gluten-Leistung beeinflussen.

Dateninteraktion

Abbildung 8 Dateninteraktion

Oben wird beschrieben, wie Gluten Ausführungspläne liefert. Hier werfen wir einen Blick auf den gesamten Datenfluss. Wie in Abbildung 8 dargestellt, verwendet Gluten zwei ColumnarBatch-Iteratoren, von denen jeweils einer von JVM und Native verwaltet wird und über JNI miteinander interagiert. Die tatsächlichen Daten werden auf der nativen Seite verwaltet, d. h. mithilfe der OffHeap-Speicherverwaltung. Was der JVM ausgesetzt wird, ist nur ein Index, der auf diesen ColumnarBatch verweist, und einige grundlegende Metadaten, wie z. B. die Anzahl der Datenelemente und die Anzahl der Felder in diesem ColumnarBatch. Dies macht den gesamten Datenfluss sehr leichtgewichtig. Nur wenn bestimmte Operatoren wie ColumnarToRow auftreten, die die Speicherverteilung der Daten ändern müssen, kopiert Gluten die tatsächlichen Daten nach OnHeap, stellt sie der JVM zur Verfügung und konvertiert sie dann in UnsafeRow. Mit anderen Worten: Es gibt zwei Arten von ColumnarBatch, die in der JVM im Umlauf sind: Eine ist leichtgewichtig und enthält nur Indizes und Metadaten, die andere ist schwergewichtig und enthält tatsächliche Daten.

Einheitliche Speicherverwaltung

Nachdem man weiß, wie die Daten fließen, stellt sich eine größere Frage: Wie verwaltet Gluten den Speicher? Die Daten können sich in der JVM oder nativ befinden. Wie vereinheitlicht man OnHeap- und OffHeap-Speicher? Dies ist die Grundlage für die Stabilitätsgarantie von Spark, z. B. ob SQL-Operatoren Spill erfordern, ob RDD-Blöcke zwischengespeichert werden können usw.

Abbildung 9 Gluten unter dem Spark-Speicher-Framework

Wie in Abbildung 9 dargestellt, folgt Gluten vollständig dem einheitlichen Speicherverwaltungs-Framework von Spark. Der Spark-Speicherpool ist in zwei Teile unterteilt: Ausführungsspeicherpool und Speicherspeicher. Ersterer ist für die Verwaltung des Laufzeitspeichers verantwortlich, z. B. des Speichers, der während der SQL-Sortierung verwendet wird. Aggregation, und letztere ist für die Aufrechterhaltung des Speichers verantwortlich. Echtzeitspeicher, wie RDD-Cache usw. Für den Ausführungsspeicherpool gibt es nach der Verbindung mit Gluten drei Verwendungsszenarien:

  • Spark wird allein verwendet, da es Szenarien geben kann, in denen JVM und Native koexistieren. In diesem Fall muss der Spark-Operator Speicher beantragen, was dem Verhalten der Spark-Community entspricht.
  • Arrow Memory Pool, Gluten implementiert die Speicherverwaltung über Arrow in dem von ihm implementierten JVM-Operator. Beispielsweise wird im RowToColumnar-Operator ein ColumnarBatch durch Zwischenspeichern eines Stapels von Zeilen generiert, sodass Sie im Voraus ein Stück Speicher beantragen müssen.
  • Velox Moemry Pool, der der Schlüssel zum einheitlichen Speicher ist. Velox stellt einen Speicherpool zur Verwaltung der Anwendung und Freigabe von Speicher durch native Operatoren während der Laufzeit bereit. Gluten ruft JNI-Rückrufe an den Task Memory Consumer der JVM auf, indem es einen Listener registriert und so eine Verbindung zu Spark herstellt Speicherverwaltungs-Framework

Der RDD Columnar Cache von Gluten verwendet den Storage Memory Pool. Spark Cache verfügt über das StorageLevel-Attribut, sodass Benutzer wählen können, ob Daten in OnHeap oder OffHeap zwischengespeichert werden sollen. Beispielsweise wählt unser am häufigsten verwendeter dataset.cache standardmäßig OnHeap aus. Natürlich können wir das Passen Sie ein neues StorageLevel an, um das Caching über OffHeap explizit zu deklarieren. In Native Engine empfehlen wir außerdem die Verwendung von OffHeap für das Caching. Daher sollte eine ideale Spark-Aufgabe, die auf der Native Engine läuft, sehr wenig OnHeap verwenden und alle datenbezogenen Operatoren OffHeap verwenden. Dies reduziert auch die durch GC verursachten Leistungseinbußen in Szenarios mit großem Heap.

03Gluten-Leistung

Wir haben Leistungstests für Gluten und Spark basierend auf  einem TPCDS-  Datenvolumen von 1 TB durchgeführt, wie in Abbildung 10 dargestellt. Es gibt insgesamt 99 Testfälle, von denen Gluten in 95 % der Testfälle besser als Spark ist. Aus Gesamtdatensicht schneidet Gluten auch besser ab als Spark, läuft schneller und verbraucht weniger Ressourcen.

Vergleichsartikel Gluten Funke Ergebnisse vergleichen
Gesamtausführungszeit s 2830 7082 Gluten ist 2,5-mal schneller
Gesamt-CPU * Stunden 339,6 655,7 Gluten spart 48,2 %
Spitzenspeichernutzung GB 9.3 14.1 Gluten 34 % sparen

Abbildung 10 TPCDS-1TB

Diese Testdaten sind schlechter als die offiziellen Testdaten von Intel, die eine etwa 2,8-fache Leistungsverbesserung aufweisen. Es gibt viele Faktoren, die sich auf die Leistung auswirken, z. B. CPU, Shuffle-Disk, Netzwerkkarte auf Hardwareebene, Betriebssystemversion, GCC-Version usw. auf Softwareebene. Natürlich können diese Testdaten zeigen, dass Gluten sehr gute Aussichten hat, aber wir müssen noch viel tun, bevor es tatsächlich in der Produktionsumgebung implementiert wird. Schließlich können TPCDS-Testfälle nicht alle Spark-Nutzungsmethoden abdecken, wie z. B. Spark-Schreibtabelle, Datasource v1/v2-Schnittstelle, Dataset-Cache, Hive UDF usw.

04 Zukunftspläne

Neben NetEase ist die Gluten-Community auch stark an Alibaba Cloud, Baidu, Meituan und anderen Unternehmen beteiligt. Die Community iteriert ebenfalls schnell. Wir hoffen, dass wir durch die intensive Teilnahme an der Gluten-Community interne Nutzungsszenarien und Probleme aufdecken können der Community. , wodurch es Teil des Standards wird und die Implementierung beschleunigt wird.

Da die Native Engine gewisse Abhängigkeiten vom Betriebssystem aufweist und einige Hadoop-Cluster, die auf alter Hardware laufen, auch die ältere CentOS7-Version verwenden, wird dies ein Hindernis für die Implementierung darstellen. Daher planen wir, es zunächst im K8s-Cluster zu verwenden und die Native Engine basierend auf dem Ubuntu Docker-Image zu laden, um die Rechenleistung von Spark SQL-Aufgaben zu beschleunigen, ohne dass der Benutzer es merkt.

Schließlich sind Teams mit Ideen herzlich willkommen, mit uns zu kommunizieren.

 
Broadcom kündigte die Beendigung des bestehenden VMware-Partnerprogramms Deepin-IDE-Versionsupdate, ein neues Erscheinungsbild, an. WAVE SUMMIT feiert seine 10. Ausgabe. Wen Xinyiyan wird die neueste Enthüllung haben! Zhou Hongyi: Der gebürtige Hongmeng wird auf jeden Fall Erfolg haben. Der komplette Quellcode von GTA 5 wurde öffentlich durchgesickert. Linus: Ich werde den Code an Heiligabend nicht lesen. Ich werde eine neue Version des Java-Tool-Sets Hutool-5.8.24 veröffentlichen nächstes Jahr. Lasst uns gemeinsam über Furion klagen. Kommerzielle Erkundung: Das Boot ist vorbei. Wan Zhongshan, v4.9.1.15 Apple veröffentlicht Open-Source-Multimodal-Großsprachenmodell Ferret Yakult Company bestätigt, dass 95 G-Daten durchgesickert sind
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4565392/blog/10316086
Recomendado
Clasificación