Teil 1: Wie große Postgres-Compute-Knoten bis zu 2-mal schneller mit geringerer Latenz laufen
von David Wein, Sunil Kamath und Haoyu Huang
Das disaggregierte Speichermodell von Lakebase Postgres bietet eine funktionsreiche, flexible und kostengünstige Plattform. Eine effiziente Zwischenspeicherung von Daten ist entscheidend, um einen hohen Durchsatz und geringe Latenzzeiten zu gewährleisten, während die Daten in einem Objektspeicher wie S3 gesichert sind.
Dieses Caching findet auf zwei Ebenen statt: im verteilten Speicher, wo Postgres-Seiten für einen hohen Schreibdurchsatz und eine schnelle Bereitstellung von Lesezugriffen materialisiert werden, und auf dem Postgres-Compute selbst, um häufig aufgerufene Seiten aus dem DRAM für einen ultraschnellen Zugriff bereitzustellen.
Wir haben intensiv an Verbesserungen des Caching auf der Compute-Seite gearbeitet. In diesem Blogbeitrag stellen wir unsere kurzfristigen Pläne vor und gehen näher auf das ein, was bereits an Kunden ausgeliefert wurde.
Zunächst ein wenig Hintergrundinformationen dazu, wie wir hierher gekommen sind.
Datenbanken sind bekanntlich hungrig nach DRAM (Arbeitsspeicher). Sie nutzen diesen Speicher in erster Linie als Datencache und erwarten, dass Zugriffe auf Zeilen im Cache in Nanosekunden gemessen werden – um Größenordnungen schneller als selbst die schnellsten NVMe-Laufwerke.
Postgres organisiert Daten in Zeilen auf Seiten, und Seiten, auf die aktiv zugegriffen wird, müssen in einen Speicherbereich geladen werden, der als "shared buffers" bekannt ist. Da Postgres Seiten traditionell über das Dateisystem des Betriebssystems speichert, nutzt der OS-Kernel auch seinen flexiblen Page-Cache, um ein Caching zwischen den Postgres-Shared-Buffers und der Festplatte bereitzustellen.
Dieses Schema aus Shared Buffers + Page-Cache funktioniert einigermaßen gut, bringt jedoch einige Nachteile und Herausforderungen mit sich.

Nachdem wir nun einige Hintergrundinformationen geliefert haben, wollen wir darüber sprechen, wie wir diese Herausforderungen bei Databricks lösen.
Unser gewünschter Endzustand ist die effizienteste Nutzung des DRAM auf Ihrem Compute über dynamische Shared Buffers von Postgres, die sich automatisch an Ihre Arbeitslast anpassen und bis zu 75 % des verfügbaren Speichers nutzen.
Wir müssen unsere Compute-Plattform letztendlich so anpassen, dass sie automatisch skalierende Shared Buffers nutzt. Wir möchten unseren Kunden jedoch auch sinnvolle, schrittweise Verbesserungen liefern, sobald diese verfügbar sind. Jede schrittweise Bereitstellung ermöglicht es uns, einen oder mehrere Teile der Roadmap zuverlässig auszuliefern und gleichzeitig den Kunden einen echten Nutzen zu bieten. Auch wenn automatisch skalierende Computes das Ziel sind, haben wir mit festen Computes begonnen, wie im nächsten Abschnitt beschrieben.
Folgendes haben wir implementiert.

Wie Sie sich vielleicht an die technischen Herausforderungen oben erinnern, leitet ein disaggregiertes System wie Lakebase seine Lesevorgänge nicht über das Standard-OS-Dateisystem und dessen Page-Cache um. Denken Sie auch daran, dass die Shared Buffers von Postgres statisch sind und nicht automatisch skalieren können.
Um dies zu lösen, haben wir eine Ebene namens Local File Cache (LFC) erstellt. Der LFC fungierte als Stellvertreter und bildete einen automatisch skalierenden Cache, der Hand in Hand mit den Shared Buffers arbeitete und so viele Daten wie möglich auf dem Compute zwischenspeicherte. Dies war eine clevere und pragmatische Lösung, die Lakebase Postgres den Start der automatischen Skalierung ermöglichte und seit dem Start auf allen Computes im Einsatz ist.
Obwohl sie für Benutzer als ein einziger High-Speed-Compute-Cache dargestellt wird, unterstützt die zugrunde liegende Architektur bis zu zwei Ebenen:
Shared Buffers wurden konservativ abgestimmt, damit sie bei Ausführung mit der minimal konfigurierten CU nicht zu viel Speicher verbrauchen. Die maximale jemals konfigurierte Größe lag bei 1 GB für Shared Buffers, während der LFC den Rest der gesamten Compute-Cache-Kapazität (bis zu 75 % des DRAM) beanspruchte. Jede Anfrage, die zu einem Miss (Fehlschlag) auf beiden Ebenen führt, wird vom Compute-Knoten an die verteilte Speicherebene weitergeleitet.
Bei größeren Arbeitsmengen (Working Sets) zwang die Begrenzung der Shared Buffers auf 1 GB die meisten Cache-Hits über die langsamere LFC-Ebene. Der LFC hat uns gute Dienste geleistet, aber wir beabsichtigen, seine aktuelle Form im Zuge des Übergangs zu vollständig dynamischen Shared Buffers einzustellen.
Hinweis: Feste Computes kamen zuerst Unsere erste Bereitstellung größerer Shared Buffers zielt auf Computes mit fester Größe ab, da Shared Buffers noch nicht dynamisch sind. Bei diesen deaktivieren wir nun den LFC und setzen die Shared Buffers auf 75 % des DRAM. Dies ist heute für Computes mit fester Größe und einer CU >= 80 aktiv. Durch den Wegfall der Pufferbegrenzung von ca. 1 GB bleiben häufig genutzte Seiten („hot pages“) in der schnellsten Speicherebene, anstatt auf den lokalen Dateispeicher ausgelagert zu werden. Um zu überprüfen, ob große Shared Buffers für Ihr Compute aktiviert sind, führen Sie |
Die Aufbewahrung häufig genutzter Daten in Shared Buffers anstelle des OS-Page-Caches behebt auch die zuvor beschriebenen Nachteile. Es gibt keine doppelte Pufferung, sodass 1 GB an zwischengespeicherten Daten 1 GB RAM statt 2 GB verbraucht. Und da sich der Cache in Postgres und nicht im Kernel befindet, können Verdrängungsentscheidungen unter Berücksichtigung des Datenbankzustands getroffen werden. Dies versetzt uns in die Lage, intelligentere Ersetzungsstrategien zu verfolgen, als sie das Betriebssystem bieten kann.
Die Dimensionierung von Shared Buffers auf 75 % des DRAM bei Computes mit fester Größe war nicht so einfach wie eine Konfigurationsänderung. Das liegt an der dritten technischen Herausforderung, der „Process-per-Backend“-Architektur.
Der nächste Abschnitt beschreibt unsere Lösung.
Postgres verwendet eine prozessbasierte Struktur, bei der jedes Backend Shared Buffers in seinen eigenen Adressraum abbildet. Dies erfordert eigene Seitentabelleneinträge (Page Table Entries) – die vom Kernel verwalteten Strukturen, die die Hardware durchläuft, um virtuelle Adressen in physischen Speicher zu übersetzen. Standardmäßig führt Linux diese Zuordnung über 4-KB-Seiten durch.
Einige einfache Zahlen: Jedes Gigabyte an Shared Buffers entspricht 262.144 Seitentabelleneinträgen pro Prozess. Bei 32 GB Shared Buffers und 512 Backends sind das rund 4,3 Milliarden Einträge oder etwa 32 GB an Seitentabellen, um 32 GB Cache abzubilden.
Diese Arbeitsmenge überschreitet auch bei Weitem die Kapazität des Translation Lookaside Buffer (TLB), eines Caches in der Memory Management Unit der CPU, der die Übersetzung von virtuellen in physische Adressen beschleunigt. Selbst ein Treffer im Shared Buffer führt dann zu Leistungseinbußen durch TLB-Misses und das Durchlaufen von Seitentabellen (Page Table Walks).
Um dies abzumildern, empfiehlt die Postgres-Community die Verwendung eines OS-Mechanismus namens Huge Pages (jeweils 2 MB) bei großen Shared Buffers. Der Wechsel zu Huge Pages reduziert die Größe der Seitentabellen um den Faktor 512 und senkt die TLB-Miss-Raten erheblich.
In unseren Benchmark-Tests reduzierte die Konfiguration von Postgres mit Huge Pages die Tail-Read-Latenz um bis zu ~40 % und senkte die CPU-Auslastung um bis zu ~30 %.
Lakebase Postgres wird in schlanken Gast-VMs (virtuellen Maschinen) auf Bare-Metal-Hosts ausgeführt. Die Übersetzung von Speicheradressen umfasst zwei virtualisierte Ebenen. Die Nutzung von Huge Pages erfordert eine konsistente Implementierung über den gesamten Stack hinweg: von der Reservierung auf Host-Ebene über den Hypervisor, der den Speicher der VM bereitstellt, bis hin zum Gast-Kernel. Ein Ausfall auf einer beliebigen Ebene beeinträchtigt die resultierenden Leistungsvorteile.
Wir haben vor Kurzem eine dedizierte Huge-Page-Unterstützung in unserer gesamten VM-Infrastruktur eingeführt. Wir haben uns für die Verwendung expliziter 2-MB-HugeTLB-Seiten entschieden, anstatt uns auf Best-Effort-Transparent-Huge-Pages zu verlassen. VMs, die für große Computes mit fester Größe zugewiesen sind, werden nun mit einem vorab festgelegten Volumen an Huge Pages initialisiert, das für den Postgres-Start ausreicht. Um die Systemressourcen zu optimieren, gibt der Compute-Start automatisch alle überschüssigen Huge Pages frei, die über die von Postgres benötigten hinausgehen.
Tipp: Um zu überprüfen, ob große explizite Huge Pages für Ihr Compute aktiviert sind, führen Sie show huge_pages innerhalb einer Postgres-Verbindung aus. Ein Lakebase-Endpunkt mit 80 CU sollte einen Wert von "on" anzeigen. |
Der Rollout begann vor einigen Wochen Region für Region. Die folgenden Beispiele wurden auf großen Produktions-Endpoints nach dem Neustart gemessen, der die neue Konfiguration aktivierte.
Auf einem großen Endpoint wurde die Änderung am 11. August um ca. 06:10 Uhr UTC aktiv. Die Anzahl der abgerufenen Postgres-Blöcke pro Sekunde verdoppelte sich, was wir hier als Indikator für den Durchsatz verwenden. Der Kunde berichtete von einer geringeren p50- und p99-Latenz im Vergleich zum Vortag, der Vorwoche und dem Vormonat.

Dieser Endpoint hatte einen großen lokalen Datei-Cache konfiguriert. Mit größeren Shared Buffers sanken die GetPage/s des Speichers von etwa 8K pro Sekunde auf etwa 1,5K.

Auf einem anderen großen Endpoint wurde die Änderung am 14. August um ca. 01:30 Uhr UTC aktiv. Der Durchsatz stieg um etwa 43 %.

Die Cache-Trefferquote der Compute-Ressourcen erreichte fast 100 %, wobei Anfragen fast vollständig aus den Shared Buffers bedient wurden.

Bei dieser Arbeitslast sank die CPU-Auslastung nach dem Rollout am 15. August von 20 Kernen auf 4. Die Compute-Cache-Trefferquote stieg auf fast 100 % und der gemessene Durchsatz verdoppelte sich.


Wir arbeiten derzeit daran, größere Shared Buffers für autoskalierende Postgres-Compute-Ressourcen bereitzustellen. Autoscaling bringt zusätzliche Komplexität mit sich: Wir müssen Shared Buffers beim Hochskalieren dynamisch erweitern und beim Herunterskalieren verkleinern – und das alles, während wir genau das erforderliche Volumen an Huge Pages zuweisen.
Um über Compute-Ressourcen mit fester Größe hinauszugehen, haben wir ein Protokoll für das Autoscaling von Huge Pages entwickelt, die dem Gastsystem bereitgestellt werden. Huge Pages werden im Einklang mit dynamischen Shared Buffers skaliert. So stellen wir sicher, dass wir auch bei hoher Parallelität und großen Speichergrößen eine effiziente Adressübersetzung beibehalten. Unser nächster Beitrag (Teil 2) wird sich mit den technischen Details dieser Implementierung dynamischer Shared Buffers befassen, einschließlich des aktuellen Status von Open-Source-Postgres und der Bereiche, in denen wir uns entschieden haben, die Funktion weiterzuentwickeln und einen Beitrag zur Upstream-Community zu leisten.
All diese Leistungsverbesserungen resultieren aus der Lakebase Postgres-Architektur. Die Speicherebene fungiert als maßgebliches System of Record, ein Compute-Knoten ist zustandslos und sein Speicher dient als Caching-Ebene.
Stellen Sie Lakebase Postgres bereit und testen Sie die Leistung. Hier starten.
Lakebase Postgres kann als eigenständige Datenbank verwendet werden, und Sie können sie auch in den Rest der Databricks Data + AI Platform integrieren: Unity Catalog-Governance, Lakehouse-Analysen, Notebooks und KI-Workflows.
(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.