2 Punkte von GN⁺ 3 시간 전 | Noch keine Kommentare. | Auf WhatsApp teilen
  • Die Property Graphs in Postgres 19 sind eine SQL/PGQ-Funktion, mit der bestehende Tabellen als Knoten und Kanten deklariert und feste Beziehungsmuster per MATCH abgefragt werden; Daten werden dabei weder kopiert noch wird eine separate Graph-Ausführungs-Engine aufgebaut
  • Graphmuster werden in relationale Joins kompiliert und nutzen daher den bestehenden Optimizer, Indizes und Statistiken unverändert; langsame Traversierungen lassen sich damit wie normale Joins analysieren und optimieren
  • Dimensionstabellen lassen sich natürlich als Knoten und reine Join-Tabellen als Kanten abbilden; Faktentabellen mit mehreren Fremdschlüsseln und eigenen Attributen modelliert man dagegen besser als Hub-Knoten, die Events darstellen
  • Eine einzelne Tabelle kann mit unterschiedlichen Aliasen gleichzeitig als Knoten und mehrere Kanten deklariert werden, sodass anhand der bestehenden Fremdschlüssel in results keine separaten Kantentabellen erzeugt und gespeichert werden müssen
  • Postgres 19 unterstützt keine Pfade variabler Länge und eignet sich daher nicht für kürzeste Pfade, N-Hop-Erreichbarkeit oder PageRank, sondern für Suchen mit fester Länge, bei denen die Beziehungsstruktur vorab bekannt ist

Ein relationales Schema als Graph lesen

  • In einem normalisierten relationalen Schema enthalten Dimensionstabellen wie drivers, constructors und circuits Entitäten, während Faktentabellen Events wie Rennergebnisse oder Qualifying-Sessions erfassen
  • Jede Zeile ist ein potenzieller Knoten, und jeder Fremdschlüssel ist eine potenzielle Kante, die auf eine andere Zeile verweist
    • Ein ER-Diagramm entspricht einem Graph auf Schemaebene
    • Die tatsächlichen Zeilen und Fremdschlüsselbeziehungen entsprechen einem Graph auf Instanzebene
  • Die Anfrage „finde den constructor zu jedem Ergebnis“ ist eine Graph-Traversierung, die man in relationalem SQL als results JOIN constructors ON ... schreibt
  • SQL/PGQ fügt keinen neuen Graphen selbst hinzu, sondern ermöglicht es, Fragen in Graphform zu formulieren, statt Joins direkt aufzulisten
  • Der Ansatz von RelBench lädt Tabellen in pandas und baut daraus im Speicher PyTorch-Graphen auf; Postgres Property Graphs nutzen dagegen die bestehenden Tabellenbeziehungen direkt in der Datenbank
  • Auch Beispiele von PyG verwenden meist Flat Files oder In-Memory-Graphen
  • Den mit dem Formula-1-Datensatz getesteten Code findet man im GitHub-Repository

Aufbau eines Property Graph

  • CREATE PROPERTY GRAPH ist ein benanntes Deklarationsobjekt, das auf bestehenden Tabellen erstellt wird
    • VERTEX TABLES legt fest, welche Tabellenzeilen als Knoten gelesen werden
    • EDGE TABLES legt fest, welche Tabellenzeilen als Verbindungsbeziehungen gelesen werden
  • Für jede Knotentabelle werden die folgenden Elemente definiert
    • KEY: der Bezeichner des Knotens, meist der Primärschlüssel
    • LABEL: der Name des Knotentyps, der in MATCH verwendet wird
    • PROPERTIES: die Liste der Spalten, auf die in Graphabfragen zugegriffen werden kann
  • Für jede Kantentabelle werden SOURCE und DESTINATION angegeben; beide Werte referenzieren jeweils den Schlüssel eines Knotens
  • CREATE PROPERTY GRAPH verschiebt oder kopiert keine Daten
    • Die Zeilen bleiben in ihren ursprünglichen Tabellen
    • Die Deklaration definiert nur, wie die bestehende Fremdschlüsselstruktur als Graph gelesen wird
  • Die strukturellen Elemente eines Property Graph bestehen nur aus zwei Arten: Knoten und Kanten
    • Labels und Properties sind Attribute von Knoten oder Kanten
    • In psql ist Element Kind für jedes Element entweder vertex oder edge

Feste Muster mit MATCH abfragen

  • Graphabfragen werden geschrieben, indem ein MATCH-Muster in GRAPH_TABLE(...) angegeben wird
  • Das folgende Muster beschreibt eine Beziehung, die vom driver über result zur race führt
MATCH (d IS driver)<-[IS of_driver]-(res IS result)-[IS in_race]->(ra IS race)  
  • Die COLUMNS-Klausel legt fest, welche Spalten die Graphabfrage nach außen zurückgibt
  • Im äußeren SELECT kann das Ergebnis von GRAPH_TABLE(...) wie eine normale Tabelle abgefragt werden
  • Da Muster Richtung und Labels enthalten, lassen sich Beziehungen wie Sätze lesen; dadurch ist die Struktur leichter zu erkennen als bei einem entsprechenden Multi-Join

Zur Laufzeit in relationale Joins übersetzte Struktur

  • MATCH ist keine separat an Postgres angehängte Graph-Ausführungs-Engine, sondern Syntax, die in relationale Joins kompiliert wird
  • Führt man EXPLAIN auf dem driver–result–race-Muster aus, erscheinen vier Hash-Joins auf den zugrunde liegenden Tabellen
  • Wie bei manuell geschriebenen Joins werden die folgenden Komponenten genutzt
    • der bestehende Postgres-Optimizer
    • bestehende Indizes
    • bestehende Statistiken
  • Wenn eine Graph-Traversierung langsam ist, sind Ursache und Optimierungsmethode dieselben wie bei langsamen normalen Joins
  • Auch in psql kann man Graphen mit tabellenähnlichen Befehlen untersuchen
    • \dG: zeigt die Liste der Property Graphs an
    • \d f1: zeigt jeden Knoten und jede Kante, die zugrunde liegende Tabelle, die Elementart sowie Start- und Zielknoten von Kanten an
    • \d+ f1: rekonstruiert das vollständige CREATE PROPERTY GRAPH-Statement und enthält auch aus dem Primärschlüssel der Kantentabelle abgeleitete Kantenschlüssel

Unterschied zwischen Keys und Properties

  • Eine als KEY eines Knotens angegebene Spalte wird nicht automatisch zu einer Property
  • Wenn driver_id nur als Knotenschlüssel angegeben wurde und man mit d.driver_id = 1 filtert, schlägt dies fehl, weil diese Spalte keine abfragbare Property ist
  • Der Key identifiziert den Knoten, legt ihn aber nicht für Abfragen offen
  • Wenn ID-Spalten gefiltert oder zurückgegeben werden sollen, müssen sie explizit in die Liste PROPERTIES aufgenommen werden

Einschränkungen bei Pfaden variabler Länge in Postgres 19

  • Postgres 19 unterstützt keine Elementmuster-Quantifizierer wie „folge einer Kante ein- bis dreimal“
ERROR: element pattern quantifier is not supported  
  • Um zwei Hops zu durchsuchen, müssen in MATCH zwei Kantenmuster explizit angegeben werden
  • Pfade beliebiger Länge lassen sich mit der Property-Graph-Syntax nicht ausdrücken
  • Suchen mit offener Tiefe müssen über eine rekursive CTE auf den zugrunde liegenden Tabellen verarbeitet werden; damit verlässt man die Property-Graph-Syntax

Bestehende Tabellen auf Knoten und Kanten abbilden

  • Dimensionstabellen sind Knoten

    • Entitätstabellen mit stabilem Primärschlüssel und Attributen wie drivers, constructors oder circuits entsprechen direkt Knoten
    • Man verwendet den Primärschlüssel als Knotenschlüssel und legt die benötigten Spalten als Properties offen
  • Reine Join-Tabellen sind Kanten

    • Viele-zu-viele-Bridge-Tabellen wie student_courses(student_id, course_id) haben von Natur aus die Aufgabe, zwei Entitäten zu verbinden, und entsprechen daher natürlich Kanten
    • Ein Fremdschlüssel wird als Startknoten, der andere als Zielknoten deklariert
    • Da jede Zeile der Join-Tabelle selbst eine Beziehung ist, ist keine separate Datentransformation für Graphen nötig
    • In dieser Struktur sind students und courses Knoten, während student_courses zur Kante enrolled_in wird
  • Faktentabellen sind Event-Knoten

    • Eine results-Zeile verweist auf die drei Entitäten driver, race und constructor und besitzt außerdem eigene Daten wie grid, position, points und status
    • SQL/PGQ-Kanten sind binäre Beziehungen mit einem Start- und einem Zielpunkt; daher kann eine ganze Zeile mit drei Fremdschlüsseln nicht zu einer einzelnen Kante gemacht werden
    • Wenn die Faktenzeile selbst Gegenstand der Analyse ist, sollte diese Tabelle besser als Knoten deklariert werden
    • Der results-Knoten enthält das Event und dessen Attribute
    • Schmale Kanten wie results_driver, results_race und results_constructor verbinden ihn mit externen Entitäten
    • Dieses Modell erzeugt eine Hub-Struktur wie driver <- result -> race, in der man am result-Knoten anhalten, filtern oder Attribute abfragen kann
    • Wenn die Beziehung selbst im Fokus steht, modelliert man sie als Kante; wenn eine bestimmte Zeile oder ein Event mit eigenen Attributen im Fokus steht, als Knoten
    • Join-Tabellen stellen Beziehungen dar, Faktentabellen Events

Eine einzelne Tabelle gleichzeitig als Knoten und Kante verwenden

  • Die Unterscheidung „Knoten oder Kante“ gilt nicht für die zugrunde liegende Tabelle, sondern für jede Elementdeklaration im Graphen
  • Eine einzelne Tabelle kann mit unterschiedlichen Aliasen sowohl in VERTEX TABLES als auch in EDGE TABLES deklariert werden
  • Während results als result-Knoten verwendet wird, kann dieselbe Tabelle als folgende Kanten-Aliase wiederverwendet werden
    • results AS res_driver: verbindet result mit driver
    • results AS res_race: verbindet result mit race
    • results AS res_constr: verbindet result mit constructor
  • Jeder Kanten-Alias nutzt die bereits in results vorhandenen Primärschlüssel- und Fremdschlüsselspalten
  • Es ist nicht nötig, separate Tabellen results_driver, results_race oder results_constructor zu erstellen; Aliase speichern keine Daten
  • Eine Faktentabelle mit drei Fremdschlüsseln muss nicht als eine Kante mit mehreren Endpunkten deklariert werden, sondern als drei Kanten-Aliase, die jeweils genau einen Start- und Zielpunkt haben
  • Dieser Ansatz verwendet drei deklarative Aliase auf einer einzigen zugrunde liegenden Tabelle statt drei physischer Tabellen oder Views

Kollisionen bei Property-Namen und -Typen

  • Wenn PROPERTIES weggelassen wird, legt SQL/PGQ alle Spalten der Tabelle als Properties offen
  • Wenn sowohl results als auch qualifying eine Spalte number haben, diese aber jeweils double precision und bigint ist, tritt folgender Fehler auf
ERROR: property "number" data type mismatch: double precision vs. bigint  
  • Gleichnamige Properties müssen im gesamten Graphen einen einzigen Typ haben, daher kollidieren gleichnamige Spalten mit unterschiedlichen Typen
  • Wenn nur benötigte Spalten explizit in PROPERTIES angegeben werden, lassen sich kollidierende Spalten aus dem Graphen ausschließen
  • Sowohl das Problem, dass Keys nicht automatisch zu Properties werden, als auch Typkollisionen lassen sich durch eine explizite Allowlist für Properties lösen

Geeignete und ungeeignete Abfragen

  • Feste Beziehungsmuster

    • Property Graphs eignen sich für Fragen wie „Finde die Objekte, die mit X über genau dieses Muster verbunden sind“
    • Im Formula-1-Graphen lassen sich zum Beispiel folgende Abfragen schreiben
      1. Ermitteln, bei welchem constructor ein bestimmter driver gefahren ist
      2. Konkurrenten über ein Muster finden, das vom driver über result und race zu einem anderen result und driver führt
      3. Struktur- und Attributbedingungen gemeinsam filtern, etwa Ergebnisse, bei denen die Startposition außerhalb der Top 10 lag und der driver Italian ist
    • Wenn die Form der Beziehungen vorab bekannt und die Struktur fest und endlich ist, wird die Syntax zum Suchen, Filtern und Aggregieren lesbarer
    • Auch Abfragen, die mehrere Self-Joins erfordern, lassen sich als ein gut lesbares MATCH-Muster ausdrücken
  • Probleme, bei denen der Pfad selbst unbekannt ist

    • Die folgenden Probleme passen nicht zu den Property Graphs in Postgres 19
      • kürzester Pfad zwischen zwei drivern
      • alle innerhalb von N Hops erreichbaren Ziele
      • Verbindungssuche mit vorab unbekannter Tiefe
    • Solche Abfragen erfordern Traversierungen variabler Länge und müssen daher mit einer rekursiven CTE auf den zugrunde liegenden Tabellen umgesetzt werden
    • Auch Graphalgorithmen wie PageRank, Community Detection oder Zentralitätsberechnung sind andere Probleme als Pattern Matching und fallen nicht in den Umfang dieser Funktion
    • Property Graphs finden verbundene Ziele, wenn die Pfadstruktur bekannt ist; sie können aber keine Pfade entdecken, wenn unbekannt ist, über welchen Pfad X und Y verbunden sind, und auch keine strukturelle Bedeutung im Graphen berechnen

Punkte, die vor der Einführung zu prüfen sind

  • SQL/PGQ ist ein deklaratives Overlay auf bestehenden Fremdschlüsselstrukturen und speichert keine zusätzlichen Daten, sofern man dies nicht separat wählt
  • Die wichtigsten Vorteile sind zwei Punkte
    • Traversierungen mit fester Form lassen sich lesbarer schreiben als relationale Joins
    • Es stellt ein benanntes Objekt bereit, das das Schema als Graph dokumentiert
  • Die größte Einschränkung ist das Fehlen von Pfaden variabler Länge, sodass tiefe oder offene Suchen per rekursivem SQL verarbeitet werden müssen
  • Tabellen sind nicht von Natur aus Knoten oder Kanten; in jedem Graphen kann entschieden werden, ob ihre Zeilen als Knoten, Kanten oder beides gelesen werden
  • Wenn man in Postgres 19 feste Graphmuster auf einem bestehenden Schema abfragen möchte, kann man CREATE PROPERTY GRAPH und MATCH prüfen, bevor man Daten in eine separate Graphdatenbank verschiebt
  • Für Suchen mit unbekannter Tiefe ist eine rekursive CTE nötig, aber unabhängig vom gewählten Ansatz müssen die Daten Postgres nicht verlassen
  • Vor einer echten Einführung sollte man selbst testen, ob die benötigte Performance erreicht wird

Noch keine Kommentare.

Noch keine Kommentare.