Schnellere Bulk-Loads und sicherere OLTP-Workloads mit der LTAP-Architektur
von Yecheng Yang, Nolan Biscaro, Szu-Po Wang und Pranav Aurora
Operative Datenbanken wie Postgres wurden entwickelt, um hochgradig parallele Abfragen mit geringer Latenz über Teilmengen von Daten hinweg zuverlässig auszuführen. Was diese Zuverlässigkeit häufig beeinträchtigt, sind Massenoperationen – etwa das Laden von Terabytes an Daten oder das Ausführen analytischer Abfragen, die eine gesamte Tabelle scannen. Diese Operationen konkurrieren mit Ihren Anwendungs-Workloads um dieselben Ressourcen, was zu Leistungseinbußen oder Ausfallzeiten führen kann.
Unser Ziel ist es, Lakebase Postgres zum sichersten und zuverlässigsten Ort für Ihre operativen Workloads zu machen. Das erreichen wir, indem wir schwere Batch-Operationen von den primären Compute-Ressourcen auf verteilte Engines wie Spark verlagern, die speziell für diese Aufgaben entwickelt wurden. Ermöglicht wird dies durch die LTAP-Architektur (Lake Transactional/Analytical Processing), mit der sowohl transaktionale als auch analytische Engines auf genau denselben Daten im Lake arbeiten können.
Ohne diese Isolierung sind Teams häufig gezwungen, bei Massenoperationen extrem vorsichtig vorzugehen. Sie opfern die Datenaktualität, führen Ladevorgänge selten und außerhalb der Spitzenzeiten aus und verwalten komplexe Backfills sowie Checkpoints manuell. Durch die Nutzung der LTAP-Architektur verlagern sie Ingestion-Pipelines heute vollständig auf Spark. So wird sichergestellt, dass Anwendungen aktuelle Daten erhalten, ohne die Leistung des Live-Systems zu beeinträchtigen.
Während unserer Beta-Phase nutzte ein Kunde Synced Tables, um täglich etwa 1 Milliarde Zeilen in Lakebase zu laden. Durch den Einsatz der LTAP-Architektur haben wir diese Ladevorgänge drastisch beschleunigt und gleichzeitig dafür gesorgt, dass die operativen Workloads völlig unbeeinträchtigt blieben.

Zuvor dauerte ihr Massenladevorgang über 8 Stunden und lastete CPU und Arbeitsspeicher vollständig aus. Selbst mit der automatischen Skalierung von Lakebase waren sie gezwungen, ihre OLTP-Ressourcen stark zu überdimensionieren, um die Synchronisierung überhaupt zu bewältigen. Das liegt daran, dass bei traditionellen Postgres-Architekturen die primäre Instanz der einzige Verwalter des dauerhaften Zustands ist. Massenladevorgänge müssen durch diesen einzelnen Engpass. Das bedeutet, dass jede importierte Zeile Heap-Pages generiert, Indizes aktualisiert und WAL-Einträge auf genau derselben Instanz schreibt, die auch Ihren Live-Anwendungsverkehr bedient.
Die LTAP-Architektur hat die primäre Instanz vollständig entlastet und den Live-Anwendungsverkehr geschützt. Sie erhalten die volle Leistung einer verteilten Engine und skalieren den Ladedurchsatz nahezu linear mit dem Wachstum Ihrer Daten. Unsere internen Benchmarks zeigen, dass das Laden von 1 TB jetzt weniger als 5 Minuten dauert.

Hinweis: Dieser Benchmark misst die Dauer des Datenladevorgangs (Erstellung der Heap-Pages). Wir arbeiten derzeit aktiv daran, auch den Indexaufbau für diese geladenen Daten zu parallelisieren.

Im weiteren Verlauf dieses Beitrags gehen wir detailliert auf die Herausforderungen beim Massenladen von Daten in eine OLTP-Datenbank wie Postgres ein und zeigen, wie wir diese mithilfe der LTAP-Architektur lösen.
Der native Postgres-Befehl COPY ist für standardmäßige, kleinere Ingests effizient.
Wenn Kunden jedoch ihre operativen und analytischen Datenbestände enger zusammenführen, ändern sich die Größenordnungen. Ein immer häufigerer Workload besteht darin, riesige Gold-Tier-Lakehouse-Tabellen für Anwendungen mit operativen Abfragemustern bereitzustellen. Das Schreiben von Daten in diesem extremen Umfang in eine Produktionsdatenbank stößt auf zwei grundlegende Grenzen:
Das gilt auch dann, wenn Sie den Ladevorgang als verteilten Spark-Job starten. Spark kann Quellpartitionen zwar parallel lesen, aber jede Zeile muss dennoch durch einen einzelnen Postgres-Writer:
COPY an PostgresTraditionelle Massenladevorgänge werden durch einen einzelnen Writer ausgebremst
Die Quellseite kann horizontal skaliert werden, die Zielseite jedoch nicht. Das Hinzufügen von Spark-Executoren beschleunigt zwar den Scan, beseitigt aber nicht den Single-Writer-Engpass. Zudem bedient dieselbe primäre Postgres-Instanz Ihre Online-Anwendungstransaktionen. Ein großer Import konkurriert mit ihnen um CPU, Arbeitsspeicher, I/O, Verbindungen und WAL-Bandbreite. Die Latenz steigt, Teams verlegen Ladevorgänge in ruhige Nebenzeiten und dimensionieren die primäre Instanz oft für den größten Import statt für den täglichen Regelbetrieb.
Die LTAP-Architektur bietet hier einen Ausweg, da die primäre Instanz nicht mehr die einzige Möglichkeit ist, einen dauerhaften Postgres-Zustand zu erzeugen. Transaktionale Compute-Ressourcen sind in Lakebase zustandslos: Der dauerhafte Zustand liegt in einer verteilten Speicher-Schicht, nicht auf der lokalen Festplatte der primären Instanz. In gewisser Weise ist Postgres ein Client des Speichers – es bedient Abfragen und Transaktionen, muss aber nicht der Prozess sein, der jede neue Page erzeugt.

Für Massenladevorgänge bedeutet das: Spark kann den Postgres-Zustand erstellen und in den Speicher schreiben, während die primäre Instanz lediglich das Ergebnis veröffentlicht.
Die operativen Konsequenzen sind:
COPY-Streams anderer Vorgänge ein.Es gibt verschiedene Optimierungen für das Erstellen von Postgres-Dateien und den Aufbau des Primärschlüssel-Index.
Die von Spark erzeugten Pages müssen für die Zieldatenbank gültig sein – genau so, als hätte die primäre Instanz sie erstellt.
Jeder Spark-Executor startet eine isolierte Postgres-Instanz (Sandbox) im Binary-Upgrade-Modus – demselben Mechanismus, den pg_upgrade nutzt, um Katalog-OIDs über Hauptversions-Upgrades hinweg beizubehalten. Wir nutzen dies, um die Katalog-OIDs des Ziels in jede Sandbox zu übertragen. So wird sichergestellt, dass die in den erzeugten Pages eingebetteten OIDs mit denen des Ziels übereinstimmen. Der Driver weist den Sandboxes außerdem überschneidungsfreie OID-Bereiche zu, damit gleichzeitig erstellte Objekte nicht kollidieren können.
In jeder Sandbox baut ein binärer COPY mit FREEZE den Heap-Teil dieses Executors auf. Das Freezing markiert die importierten Tupel als bereits gecommitted, sodass Postgres sie als sichtbar behandeln kann, ohne die Transaktionshistorie der Sandbox abfragen zu müssen.
Jeder Worker berechnet anschließend Prüfsummen für jede erzeugte Page. Die Pageserver von Lakebase validieren diese Prüfsummen beim Einlesen der Dateien und erkennen Beschädigungen, bevor die importierten Pages verbindlich werden.
Zusammen sorgen Binary-Upgrade-Modus, eingefrorene Tupel (Frozen Tuples), koordinierte OIDs und die Prüfsummenvalidierung dafür, dass Spark Pages erzeugt, die das Ziel als gewöhnliche Postgres-Pages lesen kann. Wir implementieren dies über Postgres-Erweiterungen und eine Table Access Method, ohne den Postgres-Core zu verändern.
Die Einweg-Sandboxes ermöglichen zudem Leistungsoptimierungen wie Hilfstabellen für UNLOGGED. Dabei handelt es sich um Optimierungen, nicht um Sicherheitsmechanismen: Sie vermeiden unnötigen WAL- und Lock-Konflikt, da kein Anwendungsverkehr die Sandbox teilt.
| Ist das immer noch „einfach Postgres“? |
|---|
| Ja. Die Dateien, die Spark schreibt, sind Postgres-Pages und kein Importformat, das die primäre Instanz später übersetzen muss. Wir haben Erweiterungen und eine Table Access Method um diesen Pfad herum hinzugefügt – was eine der Superkräfte von Postgres ist und es uns ermöglicht, seine Funktionen zu erweitern, ohne den Quellcode zu ändern. |
Ein B-Baum ist eine geordnete Struktur über dem gesamten Schlüsselraum, und seine Blatteinträge enthalten Schlüssel- und Tupel-ID-Paare, die zurück auf den Heap verweisen.
Normalerweise bezieht Postgres diese Paare durch das Scannen des Heaps. Hier ist der Heap jedoch über hochgeladene Slices verteilt, und das Herunterladen des gesamten Heaps auf einen Index-Building-Worker würde einen Großteil der Vorteile der parallelen Erstellung zunichtemachen.
Der Index Builder benötigt die Heap-Inhalte eigentlich nicht. Er benötigt lediglich den Stream von (key, ctid)-Paaren, den ein Heap-Scan erzeugen würde. Jeder Heap-Worker aus dem vorherigen Schritt exportiert daher seine Schlüsselspalten und Tupel-IDs während des Aufbaus seines Slices in eine separate Objektspeicher-Datei.
Wir speisen diese Datensätze in eine Hilfstabelle ein, die nur die exportierten Schlüsselspalten und die Tupel-ID anstelle der ursprünglichen Zeilen enthält. Während CREATE INDEX scannt eine benutzerdefinierte Tabellenzugriffsmethode die Hilfstabelle identisch mit heapam, schreibt jedoch die ctid in jeden Indexeintrag anstelle der eigenen physischen Tupel-ID der Hilfszeile. Wenn Datensätze geladen werden, wird jede slice-lokale ctid um die kumulative Größe der vorherigen Heap-Slices verschoben, sodass sie auf die finale Position des Tupels im verketteten Heap verweist. Kurz gesagt kann der Standard-B-Baum-Builder von Postgres diese Hilfstabelle unverändert scannen und einen Index über einen Heap erstellen, den er nie heruntergeladen hat. Dadurch wird das Verschieben des gesamten Heaps durch das Verschieben der viel kleineren Schlüssel-und-Tupel-ID-Darstellung ersetzt.
Sobald sich die Heap- und Index-Slices im Objektspeicher befinden, schreibt der Spark-Driver ein Manifest und ruft eine SQL-Funktion auf dem Ziel auf. Der Primary zeichnet den Import als kompakten WAL-Datensatz auf. Ein herkömmlicher COPY würde das gesamte Datenvolumen durch das Safekeeper-Quorum senden. Wir senden nur die Importbeschreibung.
Die Pageserver fordern die hochgeladenen Dateien als autoritative Seiten an und validieren deren Prüfsummen. Sie wärmen die Seiten außerdem auf der lokalen SSD vor, damit beim ersten Lesen keine verlangsamenden Abrufe aus dem Objektspeicher entstehen. Schließlich tauscht der Primary die zusammengestellten Daten atomar in die für Benutzer sichtbare synchronisierte Tabelle aus.
Bis diese Transaktion committet wird, bleibt die importierte Tabelle isoliert. Danach sieht der Primary normale Heap- und B-Baum-Seiten, die über Postgres-Standardformate und -Schnittstellen erstellt wurden.
Wir nutzen die LTAP-Architektur für Synced Tables, was deutlich schnellere Synchronisierungen ermöglicht, wenn Sie Gold-Datensätze aus Ihrem Lakehouse bereitstellen. Wenn Sie manuelle ReverseETL-Jobs aus dem Lakehouse mit Lakebase ausführen, sollten Sie die Nutzung von Lakebase in Betracht ziehen.
Wir freuen uns darauf, dieses Protokoll zu generalisieren, um große Operationen und Wartungen wie das Erstellen von Indizes oder sogar Migrationen abzuwickeln. Lakebase mit der LTAP-Architektur ist der beste Ort für die Ausführung von OLTP-Workloads.
Wenn Sie Lakebase und Synced Tables noch nicht ausprobiert haben, legen Sie noch heute los.
(Dieser Blogbeitrag wurde mit KI-gestützten Tools übersetzt.) Originalbeitrag
Abonnieren Sie unseren Blog und erhalten Sie die neuesten Beiträge direkt in Ihren Posteingang.