Kapitel 4. Materialisierte Ansichten
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In haben wir in den vorherigen Kapiteln nur kurz über materialisierte Ansichten gesprochen. Materialisierte Ansichten sind das wichtigste Konzept, das du verstehen musst, bevor du mit Streaming-Datenbanken etwas anfangen kannst. Materialisierte Ansichten in Datenbanken wurden erstmals in den frühen 1990er Jahren eingeführt. Sie wurden ursprünglich als Funktion in einigen OLTP-Datenbanken entwickelt, um die Abfrageleistung zu verbessern, indem die Ergebnisse komplexer Abfragen vorberechnet und gespeichert wurden. Materialisierte Ansichten bieten eine Möglichkeit, die Ergebnisse einer Abfrage als physische Tabelle zu speichern, die regelmäßig oder bei Bedarf aktualisiert werden kann, um die Daten aktuell zu halten. Dieser Ansatz hilft, den Aufwand für die wiederholte Ausführung teurer Abfragen zu reduzieren, indem die Nutzer/innen die Daten stattdessen aus der materialisierten Ansicht abrufen können.
Bei der Stream-Verarbeitung werden materialisierte Ansichten nicht nur periodisch oder bei Bedarf aktualisiert. Sie werden immer asynchron im Hintergrund aktualisiert. Wenn neue Daten eintreffen, wird die materialisierte Ansicht sofort aktualisiert und die Ergebnisse werden gespeichert. Wir haben dieses Muster bereits in früheren Kapiteln erläutert. Die asynchrone Aktualisierung entspricht dem Streaming, die synchrone Aktualisierung dem Batch.
Martin Kleppmanns Video mit dem Titel "Turning the Database Inside-Out" beschreibt, dass materialisierte Ansichten nicht nur vorverarbeitete Daten sind, sondern auch direkt aus den Einträgen im Transaktionsprotokoll erstellt werden. Materialisierte Ansichten haben die Datenstromverarbeitung maßgeblich beeinflusst, indem sie das Konzept der vorberechneten und kontinuierlich aktualisierten Abfrageergebnisse eingeführt haben. Materialisierte Ansichten lösen einige der Herausforderungen bei der Stream-Verarbeitung und bieten Vorteile wie eine verbesserte Abfrageleistung, weniger Datenduplikate und vereinfachte Analysen.
Ansichten, materialisierte Ansichten und inkrementelle Updates
Mit materialisierten Ansichten wird die Verarbeitungslogik zur Erzeugung bestimmter Abfrageergebnisse von der Hauptverarbeitungspipeline getrennt. Diese Trennung kann zu einem modulareren und überschaubareren Code führen, der die Wartung und Erweiterung des Stream-Verarbeitungssystems erleichtert.
Um materialisierte Ansichten zu verstehen, müssen wir zunächst die traditionellen Ansichten verstehen. Sowohl traditionelle Sichten als auch materialisierte Sichten befinden sich in einer Datenbank. Traditionelle Sichten (oder einfach nur "Sichten") werden durch eine SQL-Anweisung definiert, die ausgeführt wird, wenn der Kunde eine Auswahl in der Sicht trifft. Die Ergebnisse einer Ansicht werden nicht gespeichert. Dadurch erhöht sich die Latenzzeit für Abfragen, die aus der Ansicht auswählen, weil die Ergebnisse nicht vorverarbeitet werden. Um das besser zu verstehen, lass uns noch einmal eine Analogie verwenden: Du hast ein schlaues Streifenhörnchen namens Simon (siehe Abbildung 4-1).
Du fragst Simon: "Wie viele Nüsse befinden sich derzeit in meinem Garten? Simon läuft in deinen Garten und zählt die Nüsse, dann kommt er zurück und sagt dir die Zahl. Wenn du Simon erneut fragst, wie viele Nüsse in deinem Garten sind, läuft er ein zweites Mal hinaus, um alle Nüsse zu zählen und dir die Zahl zu nennen. Beide Male musstest du warten, bis Simon die Nüsse gezählt hat, bevor du die Zahl erhalten hast, auch wenn sie sich nicht geändert hat. Das entspricht einer traditionellen Sichtweise und wird in Abbildung 4-2 mathematisch dargestellt.
Du beschließt, dass das nicht effizient ist. Stattdessen weist du Simon an, die Gesamtzahl der Nüsse auf ein Blatt Papier zu schreiben und es in einer Kiste aufzubewahren. Dann fragst du Simon, wie viele Nüsse es sind, aber er kann nicht antworten, weil er zu sehr damit beschäftigt ist, nach Veränderungen in der Anzahl der Nüsse auf dem Hof zu suchen. Also setzt du ein anderes Streifenhörnchen ein, das nicht so schlau ist wie Simon, damit es dir einfach die Zahl in der Kiste sagt. Nennen wir ihn Alvin. Diese Analogie ist so etwas wie eine materialisierte Sichtweise.
In dieser Analogie sind die Streifenhörnchen die SQL-Anweisungen. Die Box im zweiten Szenario ist die Speicherung, die Ansichten materialisiert, um die Ergebnisse zu speichern, die vorausgezählt wurden. In diesem Szenario ist Simon (der die Nüsse vorzählt) schlauer als Alvin, der den Wert in der Box präsentiert (siehe Abbildung 4-3). Alvin präsentiert den Wert mit einer geringen Latenzzeit und kann ihn ohne großen Aufwand gleichzeitig an viele Kunden weitergeben.
Ein wichtiger Teil der Analogie zur materialisierten Ansicht ist, dass Simon die Nüsse nicht von der ersten bis zur letzten Nuss zählt, sondern nach inkrementellen Veränderungen in der Anzahl der Nüsse sucht. Dazu gehört auch, wie viele aus dem Garten entfernt wurden und wie viele hinzugekommen sind (oder von den Bäumen gefallen sind).
Inkrementelle Änderungen bezieht sich auf den Prozess, kleine, gezielte Änderungen an bestehenden Daten vorzunehmen, anstatt den gesamten Datensatz von Grund auf neu zu berechnen. Diese Aktualisierungen werden in der Regel vorgenommen, um die Daten im Laufe der Zeit konsistent und aktuell zu halten, ohne dass der Rechenaufwand für die Neuberechnung des gesamten Datensatzes anfällt.
Die inkrementelle Funktion wird in Abbildung 4-4 mathematisch dargestellt. X steht für den aktuellen Zustand der Nüsse auf dem Hof und ∆* für die inkrementelle Veränderung der Nüsse auf dem Hof. X ist bereits gespeichert, während das schlaue Streifenhörnchen ∆X erfasst und dann zum aktuellen Zustand X hinzufügt, um den nächsten Zustand zu erreichen.
Um inkrementelle Änderungen zu erfassen, muss Simon immer asynchron auf neue Änderungen achten - ähnlich wie in einer Streaming-Umgebung.
Erinnere dich an CDC (Change Data Capture) aus Kapitel 1. CDC ist ein Paradebeispiel für inkrementelle Änderungen. CDC ist eine Technik zur Erfassung und Verfolgung von Änderungen, die im Laufe der Zeit an einer Datenbank oder Datenquelle vorgenommen wurden, indem das WAL in einer OLTP-Datenbank gelesen wird. Anstatt den gesamten Datenbestand von Grund auf zu verarbeiten, identifiziert und erfasst CDC nur die inkrementellen Änderungen: Einfügungen, Aktualisierungen und Löschungen.
Datenerfassung ändern
gibt es eine Beziehung zwischen CDC und materialisierten Ansichten. Materialisierte Ansichten übernehmen die harte Arbeit der Vorberechnung, indem sie auf inkrementelle Änderungen achten und die Ergebnisse speichern. Zuvor stellt CDC die inkrementellen Änderungen aus dem WAL in einer OLTP-Datenbank bereit. Das bedeutet, dass wir eine materialisierte Ansicht verwenden können, um die CDC mit den inkrementellen Änderungen vorzuverarbeiten.
Um auf unsere Streifenhörnchen-Analogie zurückzukommen, haben wir Simon gebeten, die Nüsse in einem Garten zu zählen. Erweitern wir das Beispiel ein wenig, um zu sagen, dass es viele Arten von Nüssen im Garten gibt. Jede Nuss hat diese Eigenschaften:
-
Farbe
-
Standort (Breitengrad, Längengrad)
Nüsse können ihre Farbe verändern, wenn sie älter werden, und sie können von anderen Tieren bewegt oder entfernt werden. Simon behält diese Veränderungen im Auge, indem er jede Nuss auf der Liste der Nüsse auf dem Papier in der Box einträgt, aktualisiert oder löscht. Wenn ein Kunde die Liste abfragt, sieht er also nur den aktuellen Status jeder Nuss im Hof.
Wir veranschaulichen dieses Szenario technisch in Abbildung 4-5. Hier sind einige wichtige Punkte im Diagramm:
-
Der WAL in der primären/OLTP-Datenbank ganz links wird repliziert, um ein Replikat der primären Datenbank zu erstellen.
-
Mit einem CDC-Konnektor wird die WAL auch in ein Topic in einer Streaming-Plattform geschrieben. Das Topic veröffentlicht den WAL der primären Datenbank, damit andere Systeme ihn abonnieren können.
-
Sink-Konnektoren können aus dem Thema konsumieren und Replikate in anderen Datenbanksystemen erstellen.
-
Stream-Prozessoren können in ihrem Cache das gleiche Replikat der Datenbank erstellen.
Mit dieser Technik kannst du ein Replikat der ursprünglichen OLTP-Datenbank aus einer benutzerseitigen Anwendung in einem beliebigen nachgelagerten Datenspeicher oder einer Stream Processing Engine erstellen. Wir konzentrieren uns auf die Stream-Processing-Engine, weil sie den Echtzeit-Anwendungsfall erfüllt und keine Batch-Semantik erzwingt.
In Kapitel 3 haben wir Push- und Pull-Abfragen eingeführt. Wenn wir die Streifenhörnchen-Analogie anwenden, ist Simon die Push-Abfrage und Alvin die Pull-Abfrage.
Push- versus Pull-Abfragen
Erweitern wir die Analogie zu den Streifenhörnchen. Mit der Push-Abfrage (auch bekannt als Simon) können wir das Ergebnis von Alvin abfragen, ohne die Latenzzeit in Kauf nehmen zu müssen, die wir bekommen, wenn wir das Ergebnis synchron berechnen.
Wir kehren zum ursprünglichen Anwendungsfall zurück, in dem Simon die Anzahl der Nüsse im Garten zählt. Simon arbeitet asynchron, d.h. er beobachtet, ob sich die Anzahl der Nüsse ändert und speichert alle Änderungen in der Box. In gewisser Weise schiebt Simon das Ergebnis in die Box. Alvin stellt den Inhalt der Box dem Kunden synchron zur Verfügung. Zur Abfragezeit holt Alvin das Ergebnis aus der Box und stellt es dem Kunden zur Verfügung. Um es zusammenzufassen:
-
Simon ist eine Push-Abfrage, die asynchron läuft.
-
Alvin ist eine Pull-Abfrage, die synchron läuft.
Simon übernimmt die meiste Arbeit bei der Berechnung des Ergebnisses, so dass sich Alvin darauf konzentrieren kann, die Ergebnisse mit geringer Latenzzeit zu liefern, sobald er eine Anfrage erhält. Das funktioniert sehr gut, aber es gibt einen Nachteil: Der Client, der die Pull-Abfrage aufruft, hat nicht viel Spielraum, um weitergehende Fragen zu stellen. Ihm steht nur die Anzahl der Nüsse zur Verfügung, mit denen er in Echtzeit Erkenntnisse gewinnen kann. Was ist, wenn der Kunde einen Durchschnittswert, den Höchstwert oder eine Verknüpfung mehrerer Tabellen wünscht? In diesem Fall verhindert die Push-Abfrage, dass der Kunde tiefergehende Fragen stellen kann.
In Abbildung 4-6 musst du, um die Abfrageflexibilität zu erhöhen, einen Kompromiss mit der Latenz eingehen, weil du die Serving Engine zwingst, mehr Arbeit zu leisten. Wenn eine benutzerseitige Anwendung die Abfrage aufruft, willst du, dass sie mit der geringsten Latenz ausgeführt wird, weil du davon ausgehst, dass viel mehr Endbenutzer die Anwendung nutzen werden. Wenn du hingegen die höchste Flexibilität haben willst, um die Daten zu zerlegen und Erkenntnisse zu gewinnen, solltest du davon ausgehen, dass nur wenige erfahrene Endnutzer diese Abfragen ausführen.
Wenn du darüber nachdenkst, würden Anwendungen, die die geringsten Latenzen benötigen, am meisten von der Verwendung von Push-Abfragen anstelle von Pull-Abfragen profitieren. Abbildung 4-7 zeigt, wie du zwischen Push- und Pull-Abfragen abwägen kannst.
Der Kasten in der Mitte stellt die materialisierte Ansicht dar. Sie gleicht die schwere Arbeit der Push-Abfragen mit der Flexibilität der Pull-Abfrage aus. Wie du Push- und Pull-Abfragen ausbalancierst, hängt von deinem Anwendungsfall ab. Wenn das Kästchen auf der Linie nach unten wandert, bietet die materialisierte Ansicht weniger flexible Abfragen, ist aber leistungsfähiger. Umgekehrt werden die Pull-Abfragen umso flexibler, je weiter die Box nach oben wandert, aber die Abfragen werden mit höheren Latenzen ausgeführt. Push- und Pull-Abfragen arbeiten zusammen, um die richtige Balance zwischen Latenz und Flexibilität zu finden (siehe Abbildung 4-8).
Aber gibt es eine Möglichkeit, sowohl eine hohe Flexibilität als auch eine niedrige Latenzzeit zu erreichen, ohne dass zwei SQL-Abfragen erforderlich sind? Wir können dies erreichen, indem wir materialisierte Ansichten verwenden, die Änderungen an ein WAL weitergeben. Das wäre die Erfahrung des Clients:
-
Der Kunde sendet eine Push-Abfrage. Diese Abfrage erstellt eine materialisierte Ansicht.
-
Der Client abonniert dann die Änderungen in der materialisierten Ansicht, genau wie beim Abonnieren einer WAL.
Bei diesem Ansatz sendet der Kunde eine Push-Abfrage anstelle einer Pull-Abfrage. Indem der Client auch Änderungen an der Push-Abfrage vornehmen kann, erhältst du die nötige Flexibilität für Ad-hoc-Abfragen. Da die Änderungen der materialisierten Ansicht abonniert werden, ist auch die Latenz der Abfrage kein Problem mehr, da die inkrementellen Änderungen bei ihrem Eintreffen an den Client weitergeleitet werden. Das bedeutet, dass der Client keine Pull-Abfrage mehr aufrufen und auf deren Ergebnis warten muss, was die Latenzzeit verringert. Es ist nur noch eine SQL-Abfrage erforderlich, damit der Client analytische Daten in Echtzeit erhält.
Dieses Muster ist heute schwierig, weil Push- und Pull-Abfragen normalerweise in getrennten Systemen ausgeführt werden. Die Push-Abfrage wird in der Regel im Stream-Prozessor ausgeführt, während die Pull-Abfrage im OLAP-System ausgeführt wird, das die Endnutzer bedient. Außerdem werden Push- und Pull-Abfragen in der Regel von verschiedenen Ingenieurteams verfasst. Streaming-Data-Ingenieure schreiben die Push-Abfrage, während Analysten oder die Entwickler der nutzerorientierten Anwendungen die Pull-Abfragen aufrufen.
Um aus diesem Dilemma herauszukommen, brauchst du ein System, das über:
-
Stream-Processing-Funktionen wie die Erstellung materialisierter Ansichten
-
Die Möglichkeit, die materialisierten Ansichten in einer Streaming-Plattform, ähnlich wie bei einem WAL, für Themen bereitzustellen
-
Die Fähigkeit, Daten auf optimale Weise zu speichern, um Daten zu bedienen
-
Die Fähigkeit, synchrone und asynchrone Serving-Methoden anzubieten
Diese Funktionen sind nur in Streaming-Datenbanken verfügbar. Sie haben die Möglichkeit, Streaming-Plattformen und Datenbanken miteinander zu verbinden, indem sie dieselbe SQL-Engine sowohl für Daten in Bewegung als auch für Daten im Ruhezustand verwenden. Wir werden in Kapitel 5 näher darauf eingehen.
Die gängigste Lösung für Echtzeitanalysen ist der Einsatz einer Stream Processing-Plattform wie Apache Flink und eines RTOLAP-Datenspeichers wie Apache Pinot (sieheAbbildung 4-9).
Abbildung 4-9 zeigt den Weg, auf dem die Daten in einer OLTP-Datenbank zu einem RTOLAP-System gelangen, das sie an einen Kunden weiterleitet. Schauen wir uns diese Architektur genauer an:
-
Die Entitäten werden in der OLTP-Datenbank als Tabellen dargestellt, die einem domänenorientierten Design folgen.
-
Die Anwendung fügt Datensätze in die Tabelle ein, aktualisiert oder löscht sie. Diese Änderungen werden in der Datenbank WAL aufgezeichnet.
-
Ein CDC-Konnektor liest die WAL und schreibt die Änderungen in ein Topic auf einer Streaming-Plattform. Die Streaming-Plattform externalisiert die OLTP-WAL, indem sie die Änderungen in Topics/Partitionen veröffentlicht, die das WAL-Konstrukt nachahmen. Diese können von Verbrauchern gelesen werden, um Replikate der Tabellen aus der ursprünglichen OLTP-Datenbank zu erstellen.
-
Der Stream-Prozessor ist ein solches System, das das Topic liest und interne Replikate von Tabellen mit Hilfe von materialisierten Ansichten erstellt. Wenn die materialisierte Ansicht asynchron aktualisiert wird, gibt er die Änderungen in einem anderen Topic aus.
-
Der RTOLAP-Datenspeicher liest das Topic, das die Ausgabe der materialisierten Ansicht enthält, und optimiert die Daten für analytische Abfragen.
In Abbildung 4-9 führt der Stream-Prozessor die Push-Abfrage in Schritt 4 aus und die Pull-Abfrage wird in Schritt 5 aufgerufen. Auch hier wird jede Abfrage in getrennten Systemen ausgeführt und von verschiedenen Ingenieuren verfasst.
In Abbildung 4-10 werden die Komplexität und die Aufteilung zwischen Push- und Pull-Abfragen noch deutlicher. Die Push-Abfrage führt die mühsame Aufgabe der komplexen Transformationen durch und speichert das Ergebnis in einer materialisierten Ansicht. Die materialisierte Ansicht speichert die Änderungen an ihrem lokalen Speicher in einem Topic in einer Streaming-Plattform, die die materialisierte Ansicht der Serving-Schicht des RTOLAP-Systems zur Verfügung stellt.
Infolgedessen hat der Endnutzer, der die Schnittstelle zum RTOLAP-System nutzt, nicht die Möglichkeit, die Vorverarbeitungslogik zu definieren, die erforderlich ist, damit die Pull-Abfrage mit geringer Latenz läuft (siehe Abbildung 4-11).
Wenn der Endnutzer, der die Pull-Abfrage verfasst, auch die Optimierungslogik für die Streaming-Daten bereitstellen würde, könnten diese Szenarien vermieden werden. Leider treten diese Situationen aufgrund des aktuellen Stands der Streaming-Architekturen sehr häufig auf.
Das Problem verschlimmert sich noch, wenn wir versuchen, CDC-Daten direkt in ein RTOLAP System zu replizieren.
CDC und Upsert
Der Begriff Upsert ist ein Portmanteau aus den Wörtern Update und Insert und beschreibt die Logik, die eine Anwendung beim Einfügen und/oder Aktualisieren einer Datenbanktabelle anwendet.1 Upsert beschreibt eine Logik, bei der eine Anwendung überprüft, ob ein Datensatz in einer Datenbanktabelle existiert. Wenn der Datensatz existiert, indem nach seinem Primärschlüssel gesucht wird, ruft er eine Aktualisierungsanweisung auf. Wenn der Datensatz nicht vorhanden ist, ruft die Anwendung eine Einfügeanweisung auf, um den Datensatz der Tabelle hinzuzufügen.
Wir haben gelernt, dass CDC-Daten inkrementelle Änderungen wie Einfügungen, Aktualisierungen und Löschungen enthalten. Die Upsert-Logik behandelt zwei der drei Arten von Änderungen in einem CDC-Datenstrom (auf die Löschänderung kommen wir später zurück).2
Upsert-Vorgänge können in bestimmten Szenarien indirekt die Leistung und Genauigkeit von Select-Abfragen verbessern. Auch wenn Upserts in erster Linie auf Datenänderungen abzielen, können sie sich positiv auf die Leistung und Genauigkeit von Select-Abfragen auswirken, indem sie die Datenintegrität erhalten und die Speicherung der Daten optimieren. Hier erfährst du, wie Upserts zu diesen Verbesserungen beitragen können:
- Datenintegrität und Genauigkeit
-
Upserts helfen, die Datenintegrität zu wahren, indem sie doppelte Datensätze verhindern und sicherstellen, dass die Daten genau und konsistent sind. Wenn Select-Abfragen Daten aus einer Datenbank mit korrekten Upsert-Vorgängen abrufen, ist die Wahrscheinlichkeit höher, dass sie genaue und zuverlässige Informationen zurückgeben.
- Vereinfachte Pull-Abfragen
-
Die Auswahl aus einer Tabelle mit geeigneten Upsert-Operationen vereinfacht die Abfragen beim Nachschlagen. Die Deduplizierung oder das Filtern der neuesten Datensätze verkompliziert die SQL-Abfrage und erhöht die Latenzzeit bei der Ausführung.
Upsert-Operationen verhalten sich wie eine Push-Abfrage, um die Pull-Abfrage zu optimieren und zu vereinfachen. Dies ist einer der Faktoren, die das Gleichgewicht zwischen Push- und Pull-Abfragen steuern. Um dies besser zu verstehen, gehen wir in Abbildung 4-12 durch ein CDC-Szenario.
-
Eine Transaktion wird von einer Anwendung gesendet, um einen Datensatz in einer Tabelle in einer OLTP-Datenbank entweder einzufügen, zu aktualisieren oder zu löschen. Nehmen wir an, der Anwendungsfall ist die Aktualisierung des Bestands an grünen T-Shirts, also ist die fragliche Tabelle die Tabelle Produkte.
-
Die Aktualisierung wird in die WAL der OLTP-Datenbank geschrieben.
-
Nehmen wir an, dass der Konnektor, der die WAL liest, gerade gestartet wurde. In diesem Fall müsste der Konnektor einen aktuellen Schnappschuss der Tabelle Produkte machen, um den aktuellen Status zu erhalten.
-
Wenn der Connector nicht über diesen Snapshot verfügt, können die nachgelagerten Systeme kein exakt gespiegeltes Replikat der Tabelle Produkte erstellen.
-
Indem er einen Schnappschuss der Tabelle macht, erstellt der Connector Seed-Ereignisse, die logisch einer Einfügung für jeden Datensatz in der Tabelle Produkte entsprechen.
-
Sobald dieser Snapshot im Thema verfügbar ist, können wir ein Tabellenreplikat erstellen. Du kannst keine Replikate mit nur inkrementellen Änderungen erstellen.
-
-
Wenn der Stream-Prozessor startet und das Thema zum ersten Mal abruft, liest er es von Anfang an. Andernfalls beginnt er mit dem Lesen aus einem gespeicherten Offset. Durch das Lesen des Themas von Anfang an kann der Stream-Prozessor ein Replikat der Tabelle Produkte erstellen. Auch hier ist es nicht möglich, ein Replikat der Tabelle mit nur inkrementellen Änderungen zu erstellen.
-
Komplexe Transformationen werden im Stream-Prozessor implementiert. Dazu muss der Stream-Prozessor eine materialisierte Ansicht erstellen, die ein Replikat der Tabelle Produkte darstellt.
-
Umwandlungsoperationen werden auf oder zwischen tabellarischen Konstrukten wie materialisierten Ansichten durchgeführt. Wenn keine Umwandlung erforderlich ist, ist es nicht nötig, eine materialisierte Ansicht zu erstellen, und der Stream kann direkt vom Eingabe- zum Ausgabethema weitergeleitet werden.
-
-
Das Output-Topic ist dem Input-Topic insofern ähnlich, als es einen Schnappschuss der Daten enthält, der als Grundlage für alle nachfolgenden Replikate dient. Allerdings wurde es im Stream-Prozessor umgewandelt. Für CDC-Daten müssen die Inhalte der Topics in dieser Pipeline in der Lage sein, nachgelagerte Replikate zu seeden.
-
Wenn der RTOLAP-Datenspeicher direkt aus dem Topic liest, muss er die Upsert-Logik selbst übernehmen. Dazu muss er auch die Daten im Topic verstehen, um Einfüge-, Aktualisierungs- und Löschvorgänge zu identifizieren, damit er sie anschließend auf die bestehende interne Tabelle anwenden kann.
-
Dieser Schritt ist eine Alternative zu Schritt 6. In diesem Fall sendet der Stream-Prozessor die Daten direkt an den RTOLAP-Datenspeicher. Bei RTOLAPs, die Upsert nicht unterstützen, muss der Stream-Prozessor die Upsert-Logik anstelle des RTOLAP-Systems ausführen.
Da Upsert-Operationen per Definition nur Einfügungen und Aktualisierungen unterstützen, werden Löschvorgänge in der Regel ausgelassen. Einige Systeme implementieren Upsert-Operationen so, dass sie auch eine Löschlogik enthalten. Andere, wie z. B. Apache Pinot, kennzeichnen einen gelöschten Datensatz nur so, dass die vorherigen Versionen wiederhergestellt werden können. In diesen Fällen ist es wichtig, die RTOLAP-Implementierung von upsert zu verwenden, bei der die RTOLAP direkt aus dem Output-Topic lesen muss. Einige RTOLAPs verfügen möglicherweise nicht über die Löschfunktion, sodass die Arbeit im Stream-Prozessor erledigt werden muss.
Warnung
In Schritt 3 geht es darum, den Snapshot der Produkttabelle im Thema zu speichern. In Kapitel 1 haben wir über Themen gesprochen, die eine Aufbewahrungsfrist haben, nach der ältere Datensätze abgeschnitten werden. Für CDC-Daten ist eine andere Art von Topic erforderlich, ein sogenanntes Compacted Topic, in dem der Trunkierungsprozess den letzten Datensatz jedes Primärschlüssels beibehält. Auf diese Weise bleiben ältere Daten erhalten und ermöglichen die Materialisierung nachgelagerter Tabellenreplikate, einschließlich der historischen Datensätze.
Zusammenfassend lässt sich sagen, dass es zwei Orte gibt, an denen die Upsert-Logik implementiert werden kann - im RTOLAP-System oder im Stream-Prozessor. Der einfachere und bevorzugte Ansatz ist, dass der RTOLAP aus dem Output Topic liest und die Upsert-Logik selbst anwendet. Das Output-Topic bietet auch einen Puffer für den Fall, dass der Stream-Prozessor schneller Daten produziert, als der RTOLAP verbrauchen kann.
Upsert hebt hervor, wie schmerzhaft es ist, wenn sich zwei Echtzeitsysteme um die Zuständigkeit für eine solch komplexe Logik streiten oder ausweichen müssen. Diese Schwierigkeiten werden zu weiteren Konflikten zwischen Dateningenieuren und analytischen Endnutzern führen.
CDC kann im Streaming schwer zu konzeptualisieren sein, weil es an so vielen Konstrukten und komplexer Logik beteiligt ist. Zum Beispiel ist sie mit WALs in einer OLTP-Datenbank verbunden, sie benötigt verdichtete Topics in Streaming-Plattformen, um die Historie zu erhalten, sie braucht Upsert, um Pull-Abfragen zu vereinfachen und zu beschleunigen, und sie muss in Views materialisiert werden. Die Schwierigkeiten gehen noch weiter, wenn mehrere Systeme zwischen der ursprünglichen OLTP-Quelle und dem RTOLAP-Datenspeicher beteiligt sind, nur um ein Replikat der Tabelle "Produkte" zu erstellen. Wie wir bereits erwähnt haben, gibt es Möglichkeiten, diese Systeme zu konsolidieren und Redundanzen und Komplexität zu reduzieren. Streaming-Datenbanken sind eine Möglichkeit, diese Konsolidierung zu erreichen.
Bei Transformationen, die eine Anreicherung beinhalten, müssen mehrere Streams im Stream-Prozessor zusammengeführt werden. Erinnere dich an die zwei Arten von Streams: change streams und append-only streams. Änderungsdatenströme enthalten Änderungsdaten für Entitäten in der Geschäftsdomäne, wie Produkte und Kunden. Append-only-Streams enthalten Ereignisse wie die Clickstream-Daten aus der Anwendung. Gehen wir noch einmal durch die Streaming-Daten-Pipeline, um zu sehen, wie dies umgesetzt werden kann.
Ströme verbinden
Wie bereits erwähnt hat, werden Transformationsoperationen auf oder zwischen tabellarischen Konstrukten durchgeführt, die Änderungsströme (materialisierte Ansichten) und reine Anhänge enthalten. Append-only Streams sind wie Change Streams, bei denen nur Einfügungen erlaubt sind. Tatsächlich kann man alle tabellarischen Konstrukte in Datenbanken als Sequenzen von Änderungen betrachten, die in die tabellarische Struktur hinein- und aus ihr herausführen.
Einer der Hauptgründe, warum du einen Append-Only-Stream nicht in einer materialisierten Ansicht darstellen würdest, ist, dass materialisierte Ansichten Ergebnisse speichern müssen. Da Append-Only-Streams nur Einfügungen sind und ständig wachsen, würde dir irgendwann der Speicherplatz ausgehen, so wie du auch keine Klick-Ereignisse in eine Datenbank schreiben würdest, weil auch dort der Speicherplatz ausgehen würde.
Da sowohl Change Streams als auch Append-Only Streams als tabellarische Konstrukte dargestellt werden, benennen viele verschiedene Streaming-Systeme diese Konstrukte unterschiedlich. In diesem Buch werden wir die folgenden Begriffe in Bezug auf Tabellen in einem Stream-Prozessor verwenden:
- Tabellen anhängen
-
Ein tabellarisches Konstrukt, das nur Append-Streams enthält. Diese Konstrukte werden nicht durch einen Statusspeicher unterstützt. Diese Konstrukte repräsentieren Daten, die den Stream-Prozessor durchlaufen.
- Tabellen ändern
-
Ein tabellarisches Konstrukt, das eine materialisierte Ansicht darstellt. Änderungstabellen werden durch einen Statusspeicher unterstützt.
Auf die gleiche Weise müssen wir auch die Themen in einer Streaming-Plattform unterscheiden. Wenn wir die Art der Streaming-Daten in den Themen kennen, wissen wir, wie sie verarbeitet oder in einem Tabellenkonstrukt dargestellt werden können. Wir verwenden diese Begriffe, um Themen in einer Streaming-Plattform zu identifizieren:
- Themen anhängen
- Themen ändern
-
Themen, die Änderungsereignisse oder CDC-Ereignisse enthalten. Manche Kafka-Ingenieure würden diese auch als "Tabellen-Themen" bezeichnen.
Mit diesen Begriffen können wir besser beschreiben, wie Streams miteinander verbunden werden, denn die Logik kann verwirrend sein. Es ist wichtig, SQL als Sprache für die Definition von Joins und Transformationen zu verwenden, weil SQL die universelle Sprache für die Bearbeitung von Daten ist und die SQL-Engine Streams und Datenbanken kombinieren muss. Die gemeinsame Nutzung einer SQL-Engine zur Bearbeitung von Daten in Bewegung und von Daten im Ruhezustand führt zu einer Streaming-Datenbank.
Apache Calcit
beginnen wir mit der Verknüpfung der Append-Tabelle und der Change-Tabelle, die wir in Kapitel 2 beschrieben haben. Die SQL in Beispiel 4-1 basiert auf Apache Calcite, einem Datenmanagement-Framework, das zum Aufbau von Datenbanken mit relationaler Algebra verwendet wird. Relationale Algebra ist eine formale und mathematische Methode zur Beschreibung von Operationen, die mit relationalen Datenbanken durchgeführt werden können. Es handelt sich um eine Reihe von Regeln und Symbolen, die uns helfen, in Tabellen gespeicherte Daten, auch Relationen genannt, zu manipulieren und abzufragen.
Apache Calcite enthält viele der Teile, aus denen mathematische Operationen bestehen, lässt aber einige wichtige Funktionen aus: die Speicherung von Daten, Algorithmen zur Datenverarbeitung und ein Repository zur Speicherung von Metadaten. Wenn du eine Datenbank von Grund auf aufbauen willst, ist Apache Calcite ein Baustein dafür. Tatsächlich nutzen viele der bestehenden Echtzeitsysteme Calcite: Apache Flink, Apache Pinot, Apache Kylin, Apache Druid, Apache Beam und Apache Hive, um nur einige zu nennen.
Calcite hält sich bewusst aus dem Geschäft mit der Speicherung und Verarbeitung von Daten heraus. ...[D]as macht es zu einer ausgezeichneten Wahl für die Vermittlung zwischen Anwendungen und einer oder mehreren Datenspeicherungen und Datenverarbeitungsmaschinen. Es ist auch eine perfekte Grundlage für den Aufbau einer Datenbank: Füge einfach Daten hinzu.
Apache Calcite Dokumentation
Genau das werden wir hier tun - einfach Daten hinzufügen. Wir kehren zu unserem Clickstream-Anwendungsfall zurück, bei dem wir drei Datenquellen haben, jede in ihrem eigenen Thema in einer Streaming-Plattform.
Beispiel 4-1. Verknüpfung mit Tabellenthemen
CREATE
SINK
clickstream_enriched
AS
SELECT
E
.
*
,
C
.
*
,
P
.
*
FROM
CLICK_EVENTS
E
JOIN
CUSTOMERS
C
ON
C
.
ip
=
E
.
ip
and
JOIN
PRODUCTS
P
ON
P
.
product_id
=
E
.
product_id
WITH
(
connector
=
'kafka'
,
topic
=
'click_customer_product'
,
properties
.
bootstrap
.
server
=
'kafka:9092'
,
type
=
'upsert'
,
primary_key
=
'id'
)
;
CLICK_EVENTS
ist eine Append-Tabelle, die aus einem Append-Topic stammt. Sie enthält Klick-Ereignisse aus einer benutzerseitigen Anwendung.CUSTOMERS
ist eine Änderungstabelle, die aus einem Änderungsthema stammt. Sie enthält Änderungsereignisse aus einer OLTP-Datenbank, die über einen CDC-Connector erfasst wurden.PRODUCTS
ist eine Änderungstabelle, die aus einem Änderungsthema stammt. Sie enthält auch Änderungsereignisse aus einer OLTP-Datenbank über den CDC-Connector. Hier nehmen wir an, dass der Wert der Produkt-ID aus der Klick-URL extrahiert und in einer separaten Spalte namensproduct_id
abgelegt wurde.
Solange SQL unterstützt wird, können Stream-Processing-Plattformen Daten in Topics in tabellarischen Strukturen darstellen, so dass SQL und Tools wie Calcite verwendet werden können, um komplexe Transformationen zu definieren. Beispiel 4-1 ist ein Inner-Join, der übereinstimmende Datensätze in allen drei Tabellen -CLICK_EVENTS
, CUSTOMERS
und PRODUCTS
- zusammenführt.
Die Ausgabe jeder Streaming-SQL, die Streams aggregiert oder zusammenführt, ist eine materialisierte Ansicht. In diesem Fall fügen wir zusammen:
CLICK_EVENTS
-
Eine Tabelle mit Klickereignissen anhängen
CUSTOMERS
-
Eine Änderungstabelle/materialisierte Ansicht aller Kunden
PRODUCTS
-
Eine weitere Änderungstabelle/materialisierte Ansicht der Produkte
Hier sind die Eigenschaften der verschiedenen Arten von Tabellen-Joins:
- Tabelle anhängen an Tabelle anhängen
-
Dies geschieht immer in einem Fenster, da sonst der Speicherplatz für den Status nicht ausreicht.
- Tabelle ändern zu Tabelle ändern
-
Ein Fenster ist nicht erforderlich, da das Ergebnis der Verknüpfung in den State Store passt, wenn es die richtige Größe hat.
- Tabelle ändern in Tabelle anhängen
-
Auch dies geschieht in einem Fenster, da sonst der Platz im State Store knapp wird.3
Beachte, dass immer dann, wenn ein Append-Only-Stream Teil einer Verknüpfung ist, ein Fenster benötigt wird, um die Daten im State Store zu begrenzen.
Wenn du bei der Stream-Verarbeitung mit SQL eine Left-Join-Operation zwischen Streams durchführst, die einer Append-Tabelle und einer Change-Tabelle entsprechen, wird das Ergebnis von der Append-Tabelle bestimmt.
In SQL sieht eine solche Verknüpfung wie folgt aus:
SELECT ... FROM append_table_stream LEFT JOIN change_table_stream ON join_condition;
Hier stehen append_table_stream
und change_table_stream
für die beiden Eingangsströme, die du zusammenführen willst, und join_condition
gibt die Bedingung an, die bestimmt, wie die beiden Ströme zusammengeführt werden.
Der linke Stream (append_table_stream
), der in der Klausel FROM
zuerst angegeben wird, bestimmt das Ergebnis der Verknüpfung. Das Ergebnis enthält alle Ereignisse aus dem linken Stream und für jedes Ereignis im linken Stream die passenden Ereignisse aus dem rechten Stream (change_table_stream
), basierend auf der join_condition
.
Veranschaulichen wir uns das anhand von zwei Streams aus unserem Clickstream-Beispiel: Klicks und Kunden. Jedes Ereignis im Clickstream steht für einen Klick mit einer Kunden-ID und jedes Ereignis im Customer Stream für einen Kunden mit einer Kunden-ID. Um die beiden Streams anhand der Kunden-ID zu verknüpfen, würdest du die SQL-Abfrage wie folgt schreiben:
SELECT
k
.
product_id
,
c
.
customer_name
FROM
click
k
LEFT
JOIN
customers
c
ON
k
.
customer_id
=
c
.
customer_id
;
In diesem Beispiel ist der Stream click
der linke Stream, der das Ergebnis der Verknüpfung bestimmt. Für jedes Kundenereignis im Stream click
ruft die Abfrage den entsprechenden Kundennamen aus dem Stream customers
ab, basierend auf der passenden Kunden-ID.
Es ist wichtig zu wissen, dass die Verknüpfung bei der Stream-Verarbeitung kontinuierlich und dynamisch ist. Wenn neue Ereignisse in den Eingabedatenströmen eintreffen, wird das Join-Ergebnis kontinuierlich aktualisiert und als Ergebnisstrom ausgegeben. So kannst du Streaming-Daten mit SQL in Echtzeit verarbeiten und analysieren.
Clickstream Anwendungsfall
Gehen wir einen Schritt zurück, damit wir das vollständige Diagramm in Abbildung 4-13 Schritt für Schritt verstehen können.
-
Ein Kunde aktualisiert seine Daten.
-
Die Informationen werden in einer OLTP-Datenbank gespeichert.
-
Ein CDC-Prozess läuft auf der OLTP-Datenbank, erfasst Änderungen an der Tabelle
CUSTOMERS
und schreibt sie in ein CDC-Topic. Dieses Thema ist ein komprimiertes Thema, das als Replikat der TabelleCUSTOMERS
betrachtet werden kann. So können andere Systeme ihre Replikate der TabelleCUSTOMERS
erstellen.
-
-
Derselbe Kunde klickt auf ein Produkt in einer E-Commerce-Anwendung.
-
Das Klickereignis wird in ein Thema geschrieben. Wir schreiben Klickereignisse nicht in eine OLTP-Datenbank, weil Klickereignisse nur Einfügungen sind. Wenn wir sie in einer OLTP-Datenbank erfassen, kann es passieren, dass der Datenbank die Speicherung ausgeht.
-
Der Stream-Prozessor liest aus den CDC- und Klick-Themen.
-
Dies sind die Nachrichten aus dem
CUSTOMERS
change table topic im Stream-Prozessor. Sie werden in einem Statusspeicher gespeichert, dessen Größe von der Fenstergröße abhängt (oder, z. B. im Fall von Kafka Streams oder ksqlDB, vollständig in einer KTable gespeichert). -
Dies sind die Meldungen aus dem
CLICK_EVENTS
append table topic im Stream-Prozessor. -
Es wird ein Left-Join zwischen den
CLICK_EVENTS
append table messages und denCUSTOMERS
change table messages durchgeführt. Das Ergebnis der Verknüpfung wirdCLICK_EVENTS
mit den entsprechendenCUSTOMER
Informationen (falls vorhanden) angereichert.
-
-
Der Stream-Prozessor schreibt seine Ausgabe in die folgenden Themen.
-
Dies ist ein Änderungsthema und enthält die Änderungen der CDC
CUSTOMER
. Dies wäre ein überflüssiges Thema, da das Thema in 1b die gleichen Daten enthält. Wir lassen es hier, damit das Diagramm ausgewogen bleibt. -
Dies ist ein Anhänge-Thema, das die ursprünglichen
CLICK_EVENT
Daten enthält, die mit denCUSTOMER
Daten angereichert wurden.
-
-
Die Themen werden in den RTOLAP-Datenspeicher gezogen und in Echtzeit bereitgestellt.
-
Es handelt sich um ein Replikat der ursprünglichen Tabelle
CUSTOMERS
in der OLTP-Datenbank, das aus dem Änderungsthema erstellt wurde. -
Diese enthält die angereicherten
CLICK_EVENTS
Daten.
-
-
Der Nutzer ruft Abfragen gegen den RTOLAP-Datenspeicher auf.
-
Der Nutzer kann die Tabelle
CUSTOMERS
direkt abfragen. -
Der Nutzer kann die angereicherten Daten von
CLICK_EVENTS
abfragen, ohne die Daten selbst verknüpfen zu müssen, da die Verknüpfung bereits im Stream-Prozessor erfolgt ist.
-
Wie wir bereits angedeutet haben, kannst du den Join entweder im Stream-Prozessor oder durch den Benutzer implementieren. In diesem Fall haben wir uns dafür entschieden, die Daten von CLICK_EVENTS
und CUSTOMER
vorzuvereinen, um die Abfrageleistung aus Sicht des Nutzers zu verbessern. Die harte Arbeit des Zusammenfügens wird vom Stream-Prozessor erledigt, damit sich RTOLAP auf schnelle Abfragen mit geringer Latenz konzentrieren kann. In diesem Szenario erstellt der Stream-Prozessor eine materialisierte Ansicht, die in das Topic in 5b geschrieben wird. Der RTOLAP erstellt aus dem Topic in 5b ein Replikat der materialisierten Ansicht in sich selbst. In der RTOLAP-Datenbank müssen wir möglicherweise ein Aufbewahrungsschema implementieren, das ältere angereicherte CLICK_EVENTS
löscht, damit die Speicherung nicht ausläuft.
Alternativ hätten wir den Stream-Prozessor auch einfach umgehen und das RTOLAP die Verknüpfung durchführen lassen können, wenn der Nutzer die Abfrage aufruft. Dadurch wäre es nicht erforderlich, eine materialisierte Ansicht zu erstellen und ein weiteres komplexes Streaming-System zu verwalten. Aber diese Abfrage wäre langsam und würde das RTOLAP-System stark belasten.
Wie können wir also die architektonische Komplexität reduzieren und trotzdem die Leistung von Materialized Views erhalten? Hier können wir die Stream-Verarbeitung mit Echtzeitdatenbanken zusammenführen, indem wir Streaming Datenbanken verwenden.
Zusammenfassung
Ich werde die kühne Behauptung aufstellen, [dass] alle Datenbanken, die du bisher gesehen hast, Streaming-Datenbanken sind.
Mihai Budiu, "Building a Streaming Incremental View Maintenance Engine with Calcite", März 2023
Traditionell wurden Stream-Processing und Datenbanken als getrennte Einheiten betrachtet, wobei Stream-Processing-Systeme kontinuierlich fließende Echtzeitdaten verarbeiten und Datenbanken persistente, abfragbare Daten verwalten. Materialisierte Ansichten stellen diese Trennung jedoch in Frage, indem sie die Kluft zwischen den beiden Systemen überbrücken.
Materialisierte Ansichten ermöglichen die Erstellung von vorberechneten, dauerhaften Zusammenfassungen von Daten, die aus Streaming-Quellen stammen. Diese Ansichten dienen als Zwischenspeicher, in dem die berechneten Ergebnisse oder Zusammenfassungen so gespeichert werden, dass sie leicht abgefragt werden können. Das bedeutet, dass wir uns für die Echtzeitanalyse nicht mehr ausschließlich auf Stream-Processing-Systeme verlassen müssen, sondern dass wir materialisierte Ansichten nutzen können, um zusammengefasste Daten zu speichern und abzufragen, ohne dass sie ständig neu verarbeitet werden müssen.
Durch die Kombination der Vorteile von Stream Processing und Datenbanken bieten materialisierte Ansichten mehrere Vorteile. Erstens bieten sie die Möglichkeit, komplexe Analysen auf Streaming-Daten effizienter und skalierbarer durchzuführen. Anstatt den gesamten Datensatz für jede Abfrage neu zu verarbeiten, speichern materialisierte Ansichten die vorberechneten Ergebnisse und ermöglichen so eine schnellere und reaktionsschnellere Abfrage.
Außerdem erleichtern materialisierte Ansichten die nahtlose Integration von Streaming- und Stapelverarbeitungsparadigmen. Sie können verwendet werden, um Zwischenergebnisse von Stream-Processing-Pipelines zu speichern, und bilden so eine Brücke zwischen dem kontinuierlichen Fluss von Streaming-Daten und den stapelorientierten Analysen, die normalerweise in Datenbanken durchgeführt werden. Diese Integration trägt zur Vereinheitlichung der Verarbeitungsmodelle bei und vereinfacht die Gesamtarchitektur von datenintensiven Systemen.
Insgesamt verwischen materialisierte Ansichten die Grenzen zwischen Streaming-Verarbeitung und Datenbanken, indem sie es uns ermöglichen, persistente, abfragbare Zusammenfassungen von Streaming-Daten zu nutzen. Indem sie die Vorteile beider Systeme kombinieren, ermöglichen sie effiziente und skalierbare Echtzeitanalysen, die nahtlose Integration von historischen und Echtzeitdaten und die Konvergenz von Streaming- und Batch-Verarbeitungsparadigmen. Die Verwendung von materialisierten Ansichten eröffnet spannende Möglichkeiten für den Aufbau intelligenter und reaktionsfähiger Datensysteme, die mit der Dynamik von Streaming-Daten umgehen können und gleichzeitig schnelle und flexible Abfragefunktionen bieten.
Wir haben jetzt zwei Konstrukte in OLTP-Datenbanken eingeführt, die sie in die Nähe von Streaming-Technologien bringen:
- Das WAL
-
Ein Konstrukt, das Änderungen an Datenbanktabellen erfasst.
- Die materialisierte Ansicht
-
Eine asynchrone Abfrage, die Daten vorverarbeitet und speichert, um Abfragen mit geringer Latenz zu ermöglichen.
In Kapitel 1 haben wir das Zitat von Martin Kleppmann vorgestellt: "Die Datenbank von innen nach außen drehen". Wir haben die Datenbank tatsächlich von innen nach außen gekehrt, indem wir:
-
Das WAL-Konstrukt im OLTP wird auf der Streaming-Plattform, wie Kafka, veröffentlicht.
-
Die Funktion der materialisierten Ansicht wird in einer zustandsbehafteten Stream-Processing-Plattform nachgebildet. Dadurch wurden komplexe Transformationen der OLTP-Datenbanken überflüssig, die sich auf die Erfassung von Transaktionen und die Bereitstellung von Daten konzentrieren mussten, indem sie in die Streaming-Schicht ausgelagert wurden.
Wir haben jetzt die Grundlage, um im nächsten Kapitel über Streaming-Datenbanken zu sprechen. Hier werden wir das Streaming-Paradigma wieder auf den Kopf stellen, indem wir WALs und Materialized Views zurück in die Datenbank bringen. Mit anderen Worten: Wir werden "Streaming-Architekturen von außen innen".
1 Ein Portmanteau ist ein Wort, das sich aus zwei oder mehr Wörtern oder Wortteilen ergibt, so dass das Portmanteau-Wort eine Kombination aus der Bedeutung seiner Teile ausdrückt.
2 In vielen Datenbanksystemen besteht die Operation UPDATE
aus einem DELETE
und einem INSERT
Schritt; daher beinhaltet UPSERT
in diesen Systemen auch eine Operation DELETE
.
3 In Kafka Streams und ksqlDB kannst du materialisierte Ansichten (KTable oder GlobalKTable) für die Append-Tabelle verwenden. In diesem Fall ist kein Fenster erforderlich, weil die Ausgabe wieder ein Stream ist.
Get Streaming-Datenbanken now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.