Einführung in Apache Spark
Wofür wird Apache Spark benötigt?
Apache Spark hat zum Ziel, die Arbeit mit Big-Data zu erleichtern.
Doch wie wird dies bewerkstelligt?
Indem parallelisierte Programmierung verwendet wird: hierbei werden verschiedene Computer - gleichzeitig, dh parallellaufend - für diverse Berechnungs-Aufgaben am Datensatz verwendet. Dadurch, dass mehrere Computer gleichzeitig an derselben Rechnungs-Aufgabe arbeiten, werden die Berechnungen schneller ausgeführt. Das ist besonders nützlich, wenn Berechnungen auf Gigabytes an Daten getätigt werden müssen (dh in einer “Big-Data”-Umgebung).
Apache Spark oder pandas
?
Vor dem Beginn eines Data-Science Projektes, sollte folgende Faustregel angewendet werden:
- Falls der lokale Computer-Speicher genügend gross ist, um einen Datensatz zu laden, wähle das
pandas
-Modul. - Falls mit Big-Data gearbeitet wird - also mit Giga- oder Terrabytes an Daten - empfiehlt sich Apache Spark.
Apache Spark Funktionalitäten
Apache Spark bietet zur Arbeit mit Big-Data ein grosses Ökosystem an Funktionalitäten an. Dazu gehören:
- Datenbereinigung,
- Machinelles Lernen,
- In Python kann hier das
MLlib
-Modul von Spark verwendet werden.
- In Python kann hier das
- Graphiken-Verarbeitung,
- Speichern von Daten,
- Exportieren von Daten in verschiedene Daten-Formate,
- Daten-Streaming,
- SQL-Abfragen.
Historischer Kontext
Apache Spark wurde 2009 von einer Gruppe an PhD-Studenten an der Universtität Berkeley gegründet. Es ist ein Open Source Projekt.
Um die Nutzung von Apache Spark zu fördern, entwickelte dasselbe Team an PHD-Studenten die Webseite Data Bricks, welches eine kommerzielle Plattform zur vereinfachten Nutzung von Apache Spark darstellt (mit “Pay-as-you-Go”-Preisen).
Die Webseite Data Bricks ist jedoch nicht notwendig, um mit Apache Spark zu arbeiten!
Voraussetzungen zur Nutzung
- Man sollte eine virtuelle Umgebung auf seinem lokalen Computer aufsetzen, um Apache Spark uneingeschränkt nutzen zu können.
- Hier kommt der Vorteil von Anbieter Data Bricks ins Spiel, wenn man sich für diese kommerzielle Lösung entscheidet, da die Server von Databricks das aufsetzen einer virtuellen Umgebung automatisch übernehmen.
Apache-Spark Definitionen
Wie bei jeder neuen Informatik-Technologie, kommt Apache Spark mit einer Menge an technischen Begriffen, welche zunächst geklärt werden müssen, bevor wir in ein Code-Beispiel eintauchen. Hier ein Überblick zu den wichtigsten Termen:
- Spark: “Spark” beschreibt das spezifische Verfahren, um die Rechungsfähigkeit auf verschiedene Computer zu verteilen, damit diese parallellaufende Berechnungen - auf mehreren Computern gleichzeitig - durchführen können.
- Driver: Der “Driver” ist diejenige Computer-Instanz, welcher der Boss / Haupt-Aufseher aller anderen parallellaufenden Computern ist.
- Worker: Der “Worker” definiert einer der vielen parallellaufenden Computern. Er erhält seine Aufgabe vom “Driver” / Boss-Aufseher-Computer.
- Typischerweise gibt es eine Anzahl
\(N\)
an “Workers”.- Diese Anzahl
\(N\)
variiert, je nachdem, wie gross die (vom Driver) zugewiesene Berechnungs-Intensität ist.
- Diese Anzahl
- Typischerweise gibt es eine Anzahl
- Executor: Innerhalb einer “Worker”-Instanz gibt es den “Executor”, welcher für das eigentliche Ausführen einer (vom Driver) zugewiesenen Berechungs-Aufgabe zuständig ist.
- Wichtige Bemerkung: Es kann sein (muss aber nicht!), dass innerhalb eines “Workers” mehrere “Executors” eine Berechnungs-Aufgabe ausführen.
- Wenn man Data Bricks benutzt, wird jedoch nur 1 “Executor” pro “Worker” verwendet.
- Wichtige Bemerkung: Es kann sein (muss aber nicht!), dass innerhalb eines “Workers” mehrere “Executors” eine Berechnungs-Aufgabe ausführen.
- Spark Cluster: siehe Bild auf ZF
- Job: Das ist eine spezifische Berechnungs-Aufgabe, welcher der “Worker” ausführen muss (per Befehl vom “Driver”).
- Stages: Falls ein “Job” aus mehreren Berechnungs-Aufgaben besteht, dann besitzt der “Job” - per Definition - eine (oder mehrere) “Stages”.
- Beispiel: “Stage 1” (= Sub-Berechnung 1) + “Stage 2” (= Sub-Berechnung 2) = “Job 1” –> In diesem Fall besteht der “Job” insgesamt aus “2 stages”.
- Shuffle: Im Kontext des obigen Beispiels, kann es sein, dass “Stage 2” zunächst von der Berechnung von “Stage 1” abhängt, sodass ein Austausch an Daten zwischen “Stage 1” & “Stage 2” stattfindet, damit die Berechnung von “Stage 2” überhaupt durchgeführt werden kann.
- Beispiel: “Stage 1” wäre das zählen aller Beobachtungen im Datensatz, während “Stage 2” die Berechnung des Mittelwerts darstellt. Per Definition, wird zur Berechnung des Mittelwerts die Anzahl an Beobachtungen im Datensatz benötigt (was wir in “Stage 1” machen), weshalb es zum “shuffle” kommt, also dem Austausch an Daten zwischen “Stage 1” & “Stage 2” (um die Berechnung des Mittelwertes in “Stage 2” überhaupt durchführen zu können).
- Shuffle: Im Kontext des obigen Beispiels, kann es sein, dass “Stage 2” zunächst von der Berechnung von “Stage 1” abhängt, sodass ein Austausch an Daten zwischen “Stage 1” & “Stage 2” stattfindet, damit die Berechnung von “Stage 2” überhaupt durchgeführt werden kann.
- Beispiel: “Stage 1” (= Sub-Berechnung 1) + “Stage 2” (= Sub-Berechnung 2) = “Job 1” –> In diesem Fall besteht der “Job” insgesamt aus “2 stages”.
- Task: Ein “Job” kann in die Anzahl an “Stages” heruntergebrochen werden, je nachdem, wie viele Berechnungs-Aufgaben ein “Worker” ausführen muss. “Stages” können jedoch noch weiter heruntergebrochen werden, nämlich auf “tasks”. Diese repräsentieren die kleinste Berechnungsebene in Spark.
- Beispiel: “Task 1” + “Task 2” = “Job 1” –> In diesem Fall besteht der “Job” insgesamt aus “2 stages”, wobei jeweils eine einzelne “Stage” jeweils eine (Sub-) “Task” darstellt.
Wie wird Apache Spark verwendet?
Apache Spark verfügt über 2 APIs, aus denen wir wählen können, um Daten mit Python und SQL zu manipulieren:
- Das RDD API (2009),
- Oder das DataFrame API (2015).
Beide APIs verwenden dabei eine ähnliche Syntax, wie das pandas
Python-Modul zur Verarbeitung von Daten. Es wird empfohlen, den DataFrame API über den (veralteten) RDD API zu bevorzugen. Hierbei ist allerdings zu bemerken, dass der DataFrame API auf dem RDD API aufbaut.
In Python wird das Modul PySpark
verwendet, um mit dem DataFrame API zu interagieren.
RDD API
RDD ist ein Akronym, welches für Resilient Distributed Dataset steht.
Historischer Kontext:
Als Apache Spark entstand, bildete die Syntax vom RDD API den Kern von Apache Spark. Selbst wenn heute das RDD-API älter ist, baut das modernere DataFrame API auf die alte RDD-Architektur auf.
Um effizient mit einem der beiden Apache Spark APIs umgehen zu können, ist es deshalb wichtig, sich zunächst mit dem alten RDD API auseinanderzusetzen, insbesondere mit den 3 Wörtern, aus denen das “RDD”-Akkronym besteht (zur Erinnerung: RDD steht für “resilient distributed dataset”).
Konkret also interessieren uns 2 Dinge:
- Was genau bedeutet “resilient”?
- Und was genau ist ein “distributed dataset”?
Definition von “Distributed Dataset”
Das Wort “dataset” im Term “distributed dataset” ist ziemlich selbsterklärend: hierbei handelt es sich um eine Ansammlung von vielen einzelnen Datenwerten zu einem vollständigen Datensatz.
Im Kontext von Big-Data wird nun bei einem “distributed dataset” gemeint, dass es sich hierbei um ein Kollektiv an parallellaufenden Computern handelt, welche jeweils alle einen Teil unseres Datensatz verarbeiten.
Konkret bedeutet dies, dass unser (Gigabyte grosser) Datensatz vor der Datenverarbeitung zunächst auf die einzlnen Computer-Instanzen verteilt wird, sodass jede Instanz bloss auf einen “chunk” des Datensatzes (auch “data partition” genannt) Berechnungen durchführt.
Definition von “Resilient”
Apache Spark führt parallelisierte Berechnungen aus, welche die Eigenschaft verfügen, “resilient” zu sein.
Ein Beispiel zu einer Berechnung, die “resilient” ist, wäre:
Während der Verarbeitung der Gigabyte an Daten, kann es durchaus vorkommen, dass einer der vielen Berechnungs-Computer (= “Worker”) ausfällt. Dieser Ausfall wird jedoch nicht dazu führen, dass der komplette Spark “Job” (= spezifische Berechnungs-Aufgabe) fehlschlägt, genau deswegen, weil die parallelisierte Berechnung “resilient” ist.
Falls allerdings der “Boss” (= Spark “Driver”) ausfällt, wird jedoch die gesamte Berechnung (= Spark “Job”) fehlschlagen!
Die Kern-Idee hinter dem Apache Spark Framework?
Eines der Kernkonzepte von Apache Spark beruht darauf, dass es - bei der Datenverarbeitung - zwei unterschiedliche Arten von Berechnungen gibt:
- Transformationen: beim Ausführen solcher Datenverarbeitungs-Operationen wird nichts passieren (dh kein Spark “Job” wird ausgeführt), weil Transformationen von Spark als “lazy” angesehen werden. Eine Zelle in einem Jupyter-Notebook wird zwar ausgeführt, diese wird allerdings keinen Output anzeigen.
- Bemerkung: Zu den Transformationen gehören zum Beispiel “filter”- & “sort”-Operationen.
- “Actions”: Im Kontrast zu Transformationen, wird beim Ausführen von “Actions” hingegen eine Berechnung durchgeführt und ein Output wird sichtbar.
Ein Beispiel zu Transformationen:
Nehmen wir an, wir hätten einen Gigabyte grossen COVID-19 Datensatz für die USA auf Staaten-Level. Wir wollen nun:
- Erstens - die Daten nach den neusten COVID-19 Daten sortieren, und
- Zweitens - davon nur diejenigen Daten von LA filtrieren.
Wenn wir diese beiden Operationen als “Spark-Code” eingeben, wird dieser in nur 0.15ms erfolgreich ausgeführt und KEIN Output wird sichtbar! Es scheint nichts passiert zu sein. Und tatsächlich: nichts ist passiert!
Wieso führt Spark hier keinen Code aus?
Weil wir eine Berechnung vom Typ “Transformation” verwenden. Konkret bedeutet dies, dass Spark hier Berechnungen “lazily” ausführt.
Um einen Output sichtbar zu machen, haben wir 2 Optionen:
- Wir führen eine Berechnung vom Typ “action” auf den Datensatz aus, und / oder
- Wir müssen die Berechnung vom Typ “Transformation” auf den Datensatz speziell auslösen / “triggern”.
Was genau versteht man unter einer “lazy” ausgeführten Datenverarbeitung?
Das bei einer Berechnung vom Typ “Transformation” nichts geschieht, hat aufgrund der Grösse des Datensatzes zu tun (in Giga- oder Terrabytes). Wenn wir eine Datenverarbeitungs-Methode auf einem solch grossen Datensatz anwenden würden, dann würde - ohne die “lazy” Datenverarbeitung - Berechnungen Zeile für Zeile über den gesamten Datensatz durchgeführt werden und - im besten Fall - ein Resultat nach langer Zeit outputten.
Falls wir jedoch über zu wenig Rechenspeicher verfügen, würde - im schlimmsten Fall - eine solche Berechnug fehlschlagen.
Und genau deswegen gibt es eine “lazy” Datenverarbeitung.
Diese “lazy”-Fähigkeit - in Kombination mit der Eigenschaft, parallellisierte Rechenoperationen durchzuführen - erlaubt es Spark, eine effiziente Handhabung intensiver Rechenoperationen auf den Daten zu garantieren und eignet sich somit als “gutes” Framework für Big-Data.