Provisa-Architektur¶
Überblick¶
Provisa ist eine konfigurationsgesteuerte Datenvirtualisierungsplattform: Föderation, Metriken, Ontologie und Governance aus einem einzigen Modell, für kleine Teams und große Unternehmen. Sie bietet eine einheitliche API über heterogene Datenquellen mit Governance, Sicherheit und Performance-Optimierung. Clients fragen über SQL, GraphQL oder Cypher ab; für alle drei gilt dieselbe Governance.
Das Modell ist der einzige Ort, an dem Bedeutung hinzukommt. Um es zu erweitern, registrieren Sie neue Datenquellen oder Aggregate in der Datenvirtualisierungsschicht. Das hält eine saubere Trennung aufrecht, weil nichts außerhalb der Plattform Semantik hinzufügen kann, und erst das macht echte Data Governance möglich. Der Compiler setzt es durch: Der Katalog genehmigter Beziehungen ist die maßgebliche Quelle, gleich in welcher Abfragesprache.
Eine Plattform bedient operative und analytische Lesezugriffe. Eine Abfrage auf eine einzige Quelle geht über deren nativen Treiber direkt zu dieser Quelle und ist meist in unter 100 ms zurück, und eine Abfrage, die Quellen verknüpft, läuft in der Federation-Engine, die Sie für analytische Last gewählt haben.
Config YAML → PG Metadata → Federation Catalogs
↓
Federation engine metadata → Schema Generator → SDL / SQL catalog / Cypher labels / gRPC proto (per role)
↓
Query → Parser → SQL Compiler → Transpiler
↓
Router (Smart Dispatch)
/ | \
Federation Direct PG Direct MySQL/etc.
\ | /
Executor Pool
↓
┌───── Inline ─────┐ ┌──── Redirect ────┐
│ JSON (HTTP) │ │ CTAS → S3 │
│ Arrow (Flight) │ │ (Parquet, ORC) │
│ Protobuf (gRPC) │ │ Provisa → S3 │
└─────────────────-┘ │ (JSON, CSV, …) │
└─────────────────-┘
Abfrageschnittstellen¶
Jede Schnittstelle ist ein eigener Transport, und alle vier wenden dieselbe Sicherheits-Pipeline an (RLS, Maskierung, Stichproben, Rollenprüfungen). Clients sprechen nie direkt mit der Federation-Engine. Die Abfragesprache (SQL, GraphQL oder Cypher) ist vom Transport unabhängig, und mehrere Sprachen können über denselben Transport kommen.
| Port | Transport | Akzeptierte Abfragesprachen | Anwendungsfall |
|---|---|---|---|
| 8001 | HTTP | GraphQL, SQL, Cypher | Web-Clients, BI-Tools, curl, REST-Konsumenten |
| 8815 | Arrow Flight (gRPC) | SQL (über Arrow Flight SQL) | Daten-Tools (Pandas, DuckDB, Spark, ADBC) |
| 50051 | Protobuf gRPC | Pro Rolle generierte Proto-RPCs | Service-zu-Service mit typisierten Verträgen |
| konfigurierbar¹ | PostgreSQL-Wire-Protokoll (pgwire) | SQL | psql, DBeaver, SQLAlchemy, jeder PG-kompatible Client |
¹ Setzen Sie PROVISA_PGWIRE_PORT (z. B. 5433). Deaktiviert, wenn nicht gesetzt oder 0.
HTTP (Port 8001)¶
Mehrere Endpunkte unter demselben Port, unterschieden nach Pfad:
| Pfad | Sprache | Hinweise |
|---|---|---|
POST /data/graphql |
GraphQL | Reads und Mutations; APQ-Hash wird über extensions.persistedQuery akzeptiert |
POST /data/sql |
SQL | Nur lesend; kein Capability-Gate — geregelt durch Objektsichtbarkeit + RLS + Maskierung |
POST /data/query |
Cypher | Nur lesend; Standardrolle |
GET /data/nl |
Natürliche Sprache | Übersetzt basierend auf dem Quelltyp in SQL/GraphQL/Cypher |
GET /data/subscribe/{table} |
GraphQL | SSE-Subscription-Stream |
GET /neo4j/... |
Cypher (Neo4j-kompatibel) | Neo4j-HTTP-API-Kompatibilitäts-Shim |
POST /admin/graphql |
GraphQL | Admin-API (Superuser-/Admin-Rolle erforderlich) |
Alle Pfade geben standardmäßig JSON zurück. Accept: text/csv, application/vnd.apache.parquet, application/vnd.apache.arrow.stream und application/octet-stream (Rohbinär) werden per Content Negotiation unterstützt. Ergebnisse, die den konfigurierten Größenschwellenwert überschreiten, werden automatisch zu einer signierten S3-URL umgeleitet.
Arrow Flight (Port 8815)¶
Natives Arrow-Columnar-Transport über gRPC. Clients senden ein JSON-Ticket:
und empfangen Arrow-RecordBatches als Lazy-Stream. Wenn der Zaychik-Flight-SQL-Proxy verfügbar ist, fließen Daten End-to-End als Strom von Arrow-Record-Batches:
Das vollständige Ergebnis wird nie im Speicher von Provisa gehalten: Batches werden weitergereicht, sobald sie ankommen. Damit ist Arrow Flight ein unbegrenzter Weg, geeignet für Ergebnisse beliebiger Größe.
Protobuf gRPC (Port 50051)¶
Automatisch generierte .proto-Datei aus dem Datenschema, pro Rolle generiert. Streaming-Abfragen (eine Nachricht pro Zeile), unäre Mutations. Server Reflection aktiviert. Rolle über den Metadaten-Key x-provisa-role. Reflection beschreibt jedem Aufrufer den Service seiner eigenen Rolle.
PostgreSQL-Wire-Protokoll / pgwire (konfigurierbarer Port)¶
Implementiert das PostgreSQL-Frontend-/Backend-Wire-Protokoll über die buenavista-Bibliothek. Jeder PostgreSQL-kompatible Client (psql, DBeaver, SQLAlchemy mit psycopg2, JDBC) kann sich ohne Änderung verbinden. Akzeptiert nur SQL. Die vollständige Governance-Pipeline (RLS, Maskierung, Domänenberechtigungen) gilt identisch für pgwire-Verbindungen. Aktiviert durch Setzen von PROVISA_PGWIRE_PORT auf einen Port ungleich null.
Anfrage-Pipeline¶
Drei Abfragesprachen werden angenommen, und alle drei treffen nach ihren eigenen Parse- und Compile-Schritten bei der Governance zusammen. Alle drei können auch schreiben: GraphQL-Mutationen, SQL INSERT, UPDATE, DELETE, MERGE und TRUNCATE sowie Cypher CREATE, SET und DELETE werden zu SQL und durchlaufen dieselbe Schreibzulassung. Für das Abfragen selbst gibt es keine Fähigkeitsschranke. Jede authentifizierte Identität darf in jeder Sprache abfragen, und die Daten werden allein durch Objektsichtbarkeit, RLS und Maskierung gesteuert.
| Schnittstelle | Reads | Writes | Abfrage-Gate |
|---|---|---|---|
GraphQL (/data/graphql) |
Ja | Ja (Mutations) | Keines — nur Governance auf Datenebene |
SQL (/data/sql) |
Ja | Nein | Keines — nur Governance auf Datenebene |
Cypher (/data/query) |
Ja | Nein | Keines — nur Governance auf Datenebene |
flowchart TD
A[GraphQL Request] --> B[Auth / Role Resolution]
A2[SQL Request] --> B
A3[Cypher Request] --> B
B --> E[APQ Hash Check]
E --> F[Parse & Validate]
F --> G[Extract Directives / Hints]
G --> H{Cache Hit?}
H -- yes --> R
H -- no --> I{Input Type}
I -- GraphQL --> I1[Compile → Semantic SQL]
I -- SQL --> I2[Parse & Validate SQL\nApply Namespace / Source Binding]
I -- Cypher --> I3[Translate Cypher → SQL\nResolve Node / Rel Mappings]
I1 --> J[Governance: RLS + Masking + Visibility + Sampling]
I2 --> J
I3 --> J
J --> K[MV Rewrite]
K --> L{Route}
L -- Direct --> M[Transpile → Source Dialect\nExecute via Driver]
L -- Federation --> N[Transpile → Federation SQL\nInject Session Hints\nExecute via Federation Engine / Flight]
L -- Materialize --> O[Fetch from REST / GraphQL / gRPC\nMaterialize → S3 Parquet\nPost-filter via Federation Engine]
L -- Mutation --> P[RLS Injection\nTranspile → Source Dialect\nExecute via Driver\nInvalidate Cache + MV\nEmit Change Event]
M --> Q{Redirect?}
N --> Q
O --> Q
Q -- yes --> S[Upload to S3\nReturn Signed URL]
Q -- no --> R[Serialize: JSON / CSV / Parquet / Arrow]
R --> T[Store in Cache]
T --> U[Return to Client]
P --> U
Routing-Entscheidungen:
| Route | Wann |
|---|---|
| Cache | Result-Cache-Treffer — wird zuerst ausgewertet, liefert das gespeicherte Ergebnis ohne Ausführung |
| Cheap-Count | count(*)-förmige Abfrage über einer nicht materialisierten Quelle, die einen exakten nativen Zähler anbietet — wird zum nativen Count-Aufruf geroutet, statt zum Zählen zu materialisieren |
| Direct | Einzelne Quelle + hat nativen Treiber + hat Federation-Connector |
| Federation | Mehrquellen-Federation, oder Quelle hat Connector, aber keinen Treiber |
| Materialize | Quelle hat keinen Federation-Connector — zuerst nach S3/PG abrufen und cachen |
| Mutation | Ein Schreibvorgang: der eigene Treiber der Quelle, wenn sie einen hat; sonst die Engine, über einen Connector, der die Quelle schreibt |
Das Routing verbraucht die Ausgabe der Post-Governance-Optimierungsstufe, nie das vor der Optimierung geregelte SQL. Governance kann Quellen HINZUFÜGEN (RLS-Subquery-Prädikate); die Optimierungsstufe kann sie ENTFERNEN (Hot-Table-VALUES-CTE-Inlining, API-Cache-Rewrites, Union-Branch-Pruning). Eine föderierte Abfrage, die nach dem Inlining zu einer einzigen Live-Quelle kollabiert, wird daher als direkt neu geroutet.
Multi-Root-Abfragen¶
GraphQL-Abfragen mit mehreren Root-Feldern (z. B. { orders { id } customers { name } }) werden zu separaten SQL-Abfragen kompiliert und unabhängig ausgeführt. SQL- und Cypher-Anfragen sind per Definition Single-Root. Ergebnisse werden zu einer einzigen Antwort zusammengeführt:
- Felder unterhalb des Redirect-Schwellenwerts werden inline in
datazurückgegeben - Felder oberhalb des Schwellenwerts werden umgeleitet, mit Einträgen pro Feld in
redirects - Binärformate (Parquet, Arrow) werden nur für Single-Root-Abfragen unterstützt
Federation-Ausführungspfade¶
| Pfad | Transport | Über | Wann verwendet |
|---|---|---|---|
| REST | Federation-Engine-Client (HTTP :8080) | Direkte Abfrage | Standard, immer verfügbar |
| Flight SQL | adbc-driver-flightsql (gRPC :8480) |
Zaychik-Proxy → JDBC | Wenn Zaychik läuft |
| CTAS | Federation-Engine-Client (HTTP :8080) | Direktes Schreiben, Iceberg nach S3 | Parquet-/ORC-Redirect |
Zaychik-Arrow-Flight-SQL-Proxy¶
Die Federation-Engine unterstützt das Arrow-Flight-SQL-Protokoll nicht nativ. Zaychik ist ein Java-Proxy, der die Arrow-Flight-SQL-gRPC-Schnittstelle implementiert, Anfragen in JDBC-Abfragen übersetzt und Ergebnisse als Arrow-Record-Batches zurückstreamt.
ADBC client → gRPC :8480 → Zaychik → JDBC :8080 → Federation Engine → results → Arrow batches → client
Der Provisa-Flight-Server (Port 8815) verbindet sich als ADBC-Client mit Zaychik sodass Arrow durchgehend gestreamt und kein Ergebnis materialisiert wird.
Iceberg-Ergebniskatalog¶
Der CTAS-Redirect verwendet einen Iceberg-Connector (Katalog results), unterstützt durch einen JDBC-Katalog auf der bestehenden PostgreSQL-Instanz. Iceberg schreibt Parquet-/ORC-Dateien direkt über das native S3-Dateisystem (fs.native-s3.enabled=true) nach MinIO/S3.
Federation-Engines¶
Provisa wählt die Federation-Engine beim Start aus der Umgebungsvariablen PROVISA_ENGINE, der in der Admin-UI gespeicherten Konfiguration oder dem Standard. Ist nichts gesetzt, ist DuckDB der Standard, vollständig im Prozess und ohne externen Dienst. Wie die Auswahl funktioniert, steht unter Konfiguration.
Jede Engine ist eine FederationEngine-Instanz, definiert in provisa/federation/engine.py. Die Instanz besitzt eine Connector-Sammlung, die bestimmt, welche Quelltypen die Engine live lesen kann (ATTACH) versus welche zuerst in den Materialisierungs-Store der Engine landen müssen.
Treiberklassen¶
| Klasse | Bedeutung | Beispiele |
|---|---|---|
BROAD |
Erreicht viele externe Quelltypen über native Connectors | Trino |
PARTIAL |
Erreicht eine Teilmenge (relational, Dateien, Cloud-Objekt/-Lake) und landet alles andere | DuckDB, PostgreSQL, ClickHouse, Databricks, Snowflake, BigQuery, Fabric, Synapse |
SELF_ONLY |
Erreicht nur seinen eigenen Store; jede andere Quelle landet dort | SQLAlchemy |
Verfügbare Engines¶
| Engine-Key | Dialekt | MPP | Externer-Link-Mechanismus | Auth |
|---|---|---|---|---|
trino / trino-byo |
Trino SQL | Ja | Trino-Kataloge (breite Connector-Menge) | JDBC-Zugangsdaten |
pg |
PostgreSQL | Nein | FDW / pg_duckdb | PostgreSQL-Zugangsdaten |
duckdb |
DuckDB | Nein | Extension-natives ATTACH | Keine (in-process) |
clickhouse / clickhouse-server |
ClickHouse | Ja (Shards) | S3-/IcebergS3-/DeltaLake-Table-Engines | ClickHouse-Zugangsdaten |
snowflake |
Snowflake | Ja | External Stage + External Table | PROVISA_ENGINE_URL |
databricks |
Databricks SQL | Ja | Unity-Catalog-External-Tables über REST | Bearer-Token (http_path in federation_hints) |
bigquery |
BigQuery | Ja (Dremel) | BigQuery External / BigLake Tables | GOOGLE_APPLICATION_CREDENTIALS-Service-Account-Key |
fabric |
T-SQL | Ja | OneLake-Shortcuts → OPENROWSET | Azure AD (az login / Managed Identity) |
synapse |
T-SQL | Ja | ADLS OPENROWSET / External Tables | Azure AD |
sqlalchemy |
Beliebiger SQLAlchemy-Dialekt | Nein | Keiner (nur Landen) | Zugangsdaten pro Dialekt |
Zero-Config-Standard: DuckDB¶
Ist PROVISA_ENGINE nicht gesetzt, verwendet Provisa die vollständig eingebettete In-Process-DuckDB-Engine. Der Materialisierungs-Store von DuckDB ist eine eingebettete DuckDB-Datei unter $PROVISA_DATA_DIR/materialize.duckdb (Standard ~/.provisa/materialize.duckdb). Keine externe Datenbank oder Dienst erforderlich.
DuckDB erlaubt pro Datei nur einen Schreiber, daher schreibt store_connection.py über die eigene Verbindung der Engine in den eingebetteten Store und öffnet nie eine zweite, unabhängige. Das ist der einzige Fall, in dem Engine und Materialisierungs-Store bewusst ein Datei-Handle teilen.
Arrow-natives Lese-Transport¶
ClickHouse, DuckDB, Snowflake, Databricks, BigQuery, Fabric und Synapse bewerben alle EngineCapability.ARROW und EngineCapability.ARROW_STREAM. Abfragen gegen diese Engines geben Arrow-RecordBatches direkt zurück; der Zeilen-Serialisierungspfad wird vollständig umgangen. Der Flight-Server streamt diese Batches an Clients, ohne das vollständige Ergebnis im Prozessspeicher von Provisa zu materialisieren. Für Trino stützt sich Arrow-Streaming auf den Zaychik-Proxy; für die Warehouse-Engines speist die eigene Arrow-native API der Engine (Cloud Fetch für Databricks, Storage Read API für BigQuery, fetch_arrow_table für DuckDB und Snowflake) den Flight-Stream.
Externe Datenverknüpfungen (ATTACH)¶
Jede Warehouse-Engine kann Cloud-Objekt-/Lake-Daten vor Ort scannen, ohne eine Kopie zu landen. Parquet-, CSV-, Iceberg- und Delta-Lake-Dateien auf S3, GCS oder OneLake werden der Engine direkt angehängt, als wären sie native Tabellen. Die Strategie (ATTACH (vor Ort scannen) oder LAND (in den Store kopieren)) wird durch den deklarierten Mechanism des Connectors bestimmt; im Planner existiert kein Engine-spezifisches Branching. Ein Mechanism.ATTACH_R-Connector löst einen Zero-Copy-Scan aus; ein Mechanism.DIRECT- oder fehlender Connector löst ein Landen aus.
Attach stellt alle Voraussetzungen zur Attach-Zeit automatisch bereit:
| Engine | Objekt-/Lake-Formate | Mechanismus | Auto-Provisioning |
|---|---|---|---|
| Databricks | parquet, csv, iceberg, delta_lake | UC External Table (ATTACH_R) |
REST installiert Unity-Catalog-Storage-Credential + External Location, dann CREATE TABLE … USING <format> LOCATION … — live-verifiziert über Cloudflare R2 |
| BigQuery | parquet, csv, json, iceberg, delta_lake | BigQuery External / BigLake Table (ATTACH_R) |
CREATE OR REPLACE EXTERNAL TABLE … OPTIONS(format=…, uris=[…]) — live-verifiziert |
| ClickHouse | csv, parquet, iceberg, delta_lake | S3-/IcebergS3-/DeltaLake-Table-Engine (ATTACH_R) |
Validierungssonde bei der Attach-Zeit ausgeführt — live-verifiziert über Cloudflare R2 |
| Fabric | parquet, csv, iceberg, delta_lake | OneLake-Shortcut → OPENROWSET (ATTACH_R) |
REST erstellt eine AmazonS3Compatible-Verbindung + Lakehouse + Shortcut; gibt den OneLake-BULK-Pfad zurück — live-verifiziert beim Lesen von R2 über Fabric |
| Snowflake | parquet, csv, json, iceberg, delta_lake | External Stage + External Table (ATTACH_R) |
CREATE STAGE … URL=… CREDENTIALS=…, dann CREATE OR REPLACE EXTERNAL TABLE … LOCATION=@stage FILE_FORMAT=(TYPE=…) — implementiert; nicht live getestet (kein Konto verfügbar) |
Zugangsdaten für Cloud-Storage reisen in den federation_hints der Quelle (siehe Sources). Jeder Quelltyp, der nicht ATTACH kann, landet zuerst im Materialisierungs-Store der Engine.
Columnar-Materialisierungs-Writes¶
Connection.bulk_copy in provisa/core/database.py wählt den schnellsten Bulk-Ingest-Pfad pro Store-Dialekt: binäres COPY (asyncpg copy_records_to_table) für PostgreSQL-Stores und ein einzelnes vorbereitetes executemany-Statement für alle anderen relationalen Stores. Der eingebettete DuckDB-Store landet über land_duckdb_native in store_connection.py: ein executemany-Aufruf für den gesamten Batch, nie eine Pro-Zeile-Schleife.
Große-Ergebnis-Umleitung¶
Ergebnisse, die einen Zeilenschwellenwert überschreiten, werden statt inline zurückgegeben zu S3-kompatiblem Storage (MinIO) umgeleitet.
Redirect-Modi¶
| Modus | Funktionsweise | Berühren Daten Provisa? |
|---|---|---|
| CTAS (Parquet, ORC) | Federation-Engine schreibt direkt über CREATE TABLE AS SELECT nach S3 |
Nein |
| Provisa-Upload (JSON, NDJSON, CSV, Arrow IPC) | Provisa serialisiert und lädt via boto3 hoch | Ja |
Bei CTAS-nativen Formaten berührt Provisa die Daten nie: Die Federation-Engine schreibt Dateien direkt nach MinIO/S3. Das ist der bevorzugte Weg für große analytische Exporte.
Ohne Objektspeicher¶
Ein Ergebnis geht in den Objektspeicher, wenn einer konfiguriert ist (ein Endpunkt oder Anmeldedaten für AWS S3) und die Engine das angeforderte Format selbst dorthin schreibt. Andernfalls wird es in eine Datei im Ordner der Org geschrieben, <data directory>/orgs/<org>/results/. Ein konfigurierter Speicher, in den die Engine schreibt, den sie aber nicht erreicht, lässt die Anfrage namentlich fehlschlagen. Das Ergebnis wird dann nicht stattdessen in den Ordner geschrieben.
| Engine | Schreibt die Datei selbst | Formate |
|---|---|---|
| DuckDB | COPY (query) TO '<file>' |
parquet, csv, json, ndjson |
| PostgreSQL | COPY (query) TO STDOUT, in die Datei auf dem Host von Provisa gestreamt |
csv |
| Jedes andere Format oder jede andere Engine | Provisa schreibt sie aus den Zeilen | parquet, orc, csv, arrow, json, ndjson |
Das Handle hat dieselben Schlüssel wie das des Objektspeichers, mit "sink": "local" und einer redirect_url von /data/redirect-file/<name>, einem Pfad auf dem Server, der geantwortet hat. Nur der Benutzer, der das Ergebnis angefordert hat, darf es abrufen. Jeder Abruf vergleicht die eigene Identität der Anfrage mit dem neben der Datei vermerkten Eigentümer und antwortet jedem anderen mit 403 und einer Anfrage ohne Identität mit 401.
Eine Datei, die älter als redirect.ttl ist, wird mit 404 beantwortet und gelöscht. Ein Timer löscht sie bei ihrem Ablauf, so wie die Ergebnisse des Objektspeichers gelöscht werden. Jedes neue Ergebnis räumt außerdem den Ergebnisordner der Org auf, wodurch Dateien entfernt werden, deren Timer durch einen Neustart beendet wurde.
Redirect-Header¶
| Header | Effekt |
|---|---|
X-Provisa-Redirect-Format: <mime> |
Umleitung in diesem Format (impliziert Erzwingung, sofern kein Schwellenwert gesetzt) |
X-Provisa-Redirect-Threshold: N |
Nur umleiten, wenn das Ergebnis N Zeilen überschreitet |
X-Provisa-Redirect: true |
Umleitung mit Standardformat erzwingen |
Diese Header implementieren clientgesteuerte Umleitung.
Antwort:
{
"data": {"orders": null},
"redirect": {
"redirect_url": "https://minio:9000/provisa-results/results/abc.parquet?...",
"row_count": 50000,
"expires_in": 3600,
"content_type": "application/vnd.apache.parquet"
}
}
Serverkonfiguration¶
| Env-Var | Standard | Zweck |
|---|---|---|
PROVISA_REDIRECT_ENABLED |
false |
Serverseitige Schwellenwert-Umleitung aktivieren |
PROVISA_REDIRECT_THRESHOLD |
1000 |
Standard-Zeilenanzahl-Schwellenwert |
PROVISA_REDIRECT_FORMAT |
parquet |
Standard-Redirect-Format |
PROVISA_REDIRECT_BUCKET |
provisa-results |
S3-Bucket-Name |
PROVISA_REDIRECT_ENDPOINT |
S3-kompatible Endpunkt-URL | |
PROVISA_REDIRECT_TTL |
3600 |
TTL der signierten URL (Sekunden) |
Routing-Entscheidungsbaum¶
Multi-source query? → Federation engine
NoSQL source (MongoDB, Cassandra)? → Federation engine
Uses path columns on non-PG source? → Federation engine
Single RDBMS with driver? → Direct (sub-100ms target)
Single RDBMS without driver? → Federation engine
Steward hint "federated"? → Federation engine (override)
Steward hint "direct"? → Direct (if possible)
Redirect to Parquet/ORC? → Federation engine (CTAS, regardless of source count)
Federation-Abfrageoptimierung¶
Provisa versorgt den kostenbasierten Optimierer der Federation-Engine automatisch mit Daten, sodass quellenübergreifende Abfragepläne auf der tatsächlichen Verteilung der Daten beruhen und nicht auf fest eingebauten Standardwerten.
Automatische Statistiken (ANALYZE)¶
Bei der Quellenregistrierung führt Provisa für jede veröffentlichte Tabelle ANALYZE catalog.schema.table aus. Dies erfasst:
- Zeilenanzahl
- Pro Spalte: Null-Anteil, Anzahl eindeutiger Werte, Min/Max, Histogramme (connectorabhängig)
Der Optimizer nutzt diese, um die Selektivität für gefilterte Abfragen zu schätzen. Ohne Statistiken greift er auf feste Standardwerte zurück (z. B. 10 % Selektivität für Gleichheitsprädikate), was bei schiefen oder hochkardinalen Daten schlechte Join-Pläne erzeugt. Mit Statistiken sind die Schätzungen genau genug, um für die meisten Workloads korrekte Broadcast- vs. Partitioned-Join-Entscheidungen zu treffen.
Abdeckung: Die Unterstützung für Statistiken hängt vom Connector ab. PostgreSQL, MySQL, Hive, Iceberg und Delta Lake unterstützen ANALYZE vollständig; MongoDB und Cassandra teilweise oder gar nicht. Ein Katalog, dessen Connector keine Statistiken sammelt, wird übersprungen, und ein fehlschlagendes ANALYZE wird protokolliert und übergangen, sodass die Registrierung nie blockiert wird.
Selektivitätsgrenzen: Statistiken liefern Schätzungen pro Spalte. Bei korrelierten Prädikaten (WHERE region = 'US' AND city = 'Seattle') nimmt der Optimizer Spaltenunabhängigkeit an, was Zeilenanzahlen unterschätzen kann. Das ist eine bekannte Einschränkung spaltenbasierter Statistiken in allen kostenbasierten Optimizern.
API-Quellen: api_cache_{table_name}-Tabellen in PostgreSQL werden nach jedem Cache-Refresh-Zyklus automatisch analysiert, sodass der Optimizer aktuelle Zeilenschätzungen hat, wenn API-gestützte Quellen mit relationalen Quellen gejoint werden.
Admin: Statistiken aktualisieren¶
Statistikerfassung bei Bedarf über die Admin-API erneut ausführen:
mutation {
refreshSourceStatistics(sourceId: "sales-pg") {
tablesAnalyzed
failures { table message }
}
}
Nützlich, wenn eine Quelle seit der Registrierung erhebliche neue Daten erhalten hat.
Materialisierte Sichten¶
Materialisierte Sichten beschleunigen teure Abfragen, indem sie deren Ergebnisse vorab berechnen. Abfragen nutzen sie, ohne geändert zu werden.
Beziehungen als Hinweise für materialisierte Sichten¶
Eine Beziehungsdeklaration ist ein Governance-Artefakt und zugleich die strukturelle Beschreibung einer Join-Form. Genau diese Form (die Tabellen, die Spalten, der Join-Typ) braucht der MV-Optimierer, also kann eine Beziehung die Materialisierung direkt steuern.
Bei quellenübergreifenden Beziehungen geschieht das automatisch beim Start: Jede Beziehung mit materialize: true, deren Beine in mehr als einer Quelle liegen, erzeugt eine JoinPattern-MV (auto-mv-<rel_id>), ohne eigene MV-Konfiguration. Sieht der Compiler diesen Join in einer Abfrage, setzt der Rewriter das vormaterialisierte Ergebnis ein, ohne dass der Aufrufer es merkt. Beziehungen innerhalb einer Quelle erzeugen nichts, weil diese JOINs durch direkte Ausführung schon schnell sind.
Eine Junction-Beziehung materialisiert ihre ganze Traversierung. Die assoziative Tabelle ist ein drittes Bein, daher trägt das Muster den Quell-Hop, den Junction-Hop und den Diskriminator, der die Zeilen auf einen Kantentyp festlegt, wobei die eigenen Spalten der Junction neben denen des Ziels in der Sicht landen. Weil die Junction als Bein zählt, ist eine Kante, deren Junction in einer anderen Quelle liegt als die beiden verbundenen Tabellen, quellenübergreifend, auch wenn diese beiden in derselben Quelle liegen. Der Rewriter erkennt die beiden Hops als Kette, bei der der zweite vom Alias ausgehen muss, den der erste eingeführt hat. Eine Abfrage, die dieselben beiden Tabellen ohne die Junction erreicht, liest also die Basistabellen, und eine Sicht für einen Diskriminatorwert beantwortet nie eine Traversierung, die auf einen anderen gefiltert ist.
Die praktische Konsequenz: Stewards, die eine Beziehung genehmigen, entscheiden implizit auch, ob der Join ein guter Kandidat für die Materialisierung ist. Der Governance-Akt und der Optimierungshinweis sind dieselbe Deklaration.
Modi¶
| Modus | Konfiguration | Verhalten |
|---|---|---|
| Join-Pattern | join_pattern in der MV-Konfiguration |
Schreibt passende JOINs so um, dass sie aus der MV-Tabelle lesen |
| Custom SQL | sql in der MV-Konfiguration |
Beliebiges SELECT, optional in der SDL offengelegt |
| Automatisch materialisierte Beziehung | quellenübergreifende Beziehung (automatisch) | Generiert automatisch eine Join-Pattern-MV; keine Konfiguration erforderlich |
| Junction-gestützte Beziehung | materialize: true auf einer Junction-Beziehung |
Materialisiert die Zwei-Hop-Traversierung, Diskriminator und Kantenattribute inklusive |
Auto-Materialisierung¶
Quellenübergreifende JOINs sind die teuersten Abfragen (immer föderiert). Quellenübergreifende Beziehungen generieren beim Start automatisch MV-Definitionen:
relationships:
- id: orders-to-reviews
source_table_id: orders # sales-pg
target_table_id: product_reviews # reviews-mongo
source_column: product_id
target_column: product_id
cardinality: one-to-many
materialize: true # auto-create MV
refresh_interval: 600 # refresh every 10 minutes
Nur quellenübergreifende Beziehungen erzeugen materialisierte Sichten (JOINs innerhalb derselben Quelle sind über direkte Ausführung bereits schnell). Die materialisierte Sicht startet im Status STALE und wird von der Hintergrund-Refresh-Schleife aktualisiert, bevor sie vom Abfrage-Optimizer verwendet wird.
Refresh-Lebenszyklus¶
STALE → (refresh loop picks up) → REFRESHING → FRESH
↑ |
└──── mutation hits source table ────────────────┘
Die Refresh-Schleife läuft alle 30 Sekunden, prüft get_due_for_refresh() und führt CREATE TABLE AS SELECT (erster Lauf) oder DELETE + INSERT (nachfolgend) gegen die MV-Zieltabelle über die Federation-Engine aus.
Modulübersicht¶
| Modul | Zweck |
|---|---|
api/ |
FastAPI-App, Router, Middleware, Lifespan-Management |
api/flight/ |
Arrow-Flight-Server (gRPC, Port 8815) |
api/admin/ |
Strawberry-GraphQL-Admin-API — Konfiguration, Discovery, Views |
api/rest/ |
Automatisch generierte REST-Endpunkte aus registrierten Tabellen |
api/jsonapi/ |
Automatisch generierte JSON:API-Endpunkte mit Paginierung und Fehlerbehandlung |
api/data/subscribe.py |
SSE-Subscriptions — LISTEN/NOTIFY, Polling, Debezium-CDC |
compiler/ |
GraphQL-/SQL-Parser, Semantic-SQL-Generator, RLS, Maskierung, Sampling, zweistufige Governance (stage2.py) |
cypher/ |
Cypher-→-SQL-Übersetzer, Parser, Label-Map, Write-Übersetzer für Cypher-Mutations |
pgwire/ |
PostgreSQL-Wire-Protokoll-Server; catalog.py fängt pg_catalog/information_schema für die Objektsichtbarkeit pro Rolle ab |
vector/ |
Vektorsuche — Modellregistry, Embedding-Provider (openai/ollama/huggingface), cosine_similarity()-Übersetzung, HNSW-Index auf der Embedding-Spalte eines Replikats, deklarative Embedding-Generierung |
compiler/federation.py |
Unterstützung für Apollo-Federation-v2-Subgraph |
transpiler/ |
Dialekt-Transpilation, Routing-Logik |
executor/ |
Föderierte/direkte Ausführung, Serialisierung, Ausgabeformate |
executor/drivers/ |
Direkte Quelltreiber (PostgreSQL, MySQL, DuckDB, Snowflake, Databricks, ClickHouse, …) |
executor/trino_flight.py |
ADBC-Flight-SQL-Client für die Federation-Engine |
executor/ctas_write.py |
CTAS-basierter Redirect (Federation-Engine schreibt nach S3) |
executor/redirect.py |
S3-Redirect-Logik, Provisa-seitiger Upload |
federation/engine.py |
FederationEngine, DriverClass, _ENGINE_BUILDERS, ENGINE_REGISTRY, build_engine |
federation/connector.py |
Connector-Abstraktionen — Trino, ClickHouse; Mechanism, WarehouseNativeConnector |
federation/connector_duckdb.py |
DuckDB- und PostgreSQL-FDW-Connector-Definitionen |
federation/snowflake_connectors.py |
Snowflake-External-Stage-+-External-Table-ATTACH-Connectors |
federation/databricks_connectors.py |
Databricks-UC-External-Table-ATTACH-Connectors |
federation/bigquery_connectors.py |
BigQuery-External-/BigLake-ATTACH-Connectors |
federation/databricks_uc.py |
Auto-Provisioning von Unity-Catalog-Credential + External Location |
federation/databricks_backend.py |
Databricks-SQL-Warehouse-Ausführungs-Backend |
federation/snowflake_backend.py |
Snowflake-Ausführungs-Backend |
federation/bigquery_backend.py |
BigQuery-Ausführungs-Backend (Storage-Read-API-Arrow-Transport) |
federation/mssql_warehouse_backend.py |
Fabric-Warehouse- + Synapse-Ausführungs-Backends (T-SQL über ODBC) |
federation/mssql_warehouse_connectors.py |
OPENROWSET-ATTACH-Connectors für Fabric / Synapse |
federation/fabric_shortcuts.py |
Auto-Provisioning von OneLake-Shortcuts (Verbindung → Lakehouse → Shortcut) |
federation/clickhouse_backend.py |
ClickHouse-Ausführungs-Backend |
federation/duckdb_backend.py |
DuckDB-In-Process-Ausführungs-Backend |
federation/pg_backend.py |
PostgreSQL-Ausführungs-Backend |
federation/store_connection.py |
DuckDB-natives Materialisierungs-Store-Write-Interface |
registry/ |
Persistierte Query-Registry, Governance |
security/ |
Sichtbarkeit, Rechte, Spaltenmaskierung |
cache/ |
Redis-gestütztes Query-Result-Caching (Hot-Tier) |
mv/ |
Registry, Refresh und SQL-Rewriter für materialisierte Sichten |
events/ |
Dataset-Change-Events und Trigger-Dispatch |
webhooks/ |
Ausgehende Webhook-Ausführung für Mutations und Events |
scheduler/ |
APScheduler-basiertes Hintergrund-Job-Management — Cron- und Intervall-Trigger, die Webhooks und Mutations auslösen, und die Abfragen der Live-Kafka-Ausgaben |
apq/ |
Apollo-APQ-Wire-Protokoll — Redis-gestützter Query-Hash-Cache; getrennt vom Result-Caching |
compiler/cursor.py |
Relay-artige Cursor-Paginierung — first/after/last/before-Argumente und pageInfo-Generierung für alle Listenabfragen |
compiler/aggregate_gen.py |
Automatisch generierte {table}_aggregate-Query-Typen mit count, sum, avg, min, max-Unterfeldern und gefiltertem nodes-Zugriff |
compiler/enum_detect.py |
Automatische Enum-Typ-Erkennung — native PostgreSQL-Enum-Typen (pg_enum) als GraphQL-Enum-Typen statt String-Skalare offengelegt |
compiler/hints.py |
Federation-Performance-Hinweise — abfrageweite Routing-Direktiven, als SQL-Kommentare eingebettet (/* @provisa route=federated */), die automatisches Routing überschreiben |
compiler/mutation_gen.py |
Mutation-Compiler; Column Presets — serverseitig statische oder Session-Variable-Werte, angewendet bei Insert/Update, nicht im Mutation-Input-Typ offengelegt |
auth/approval_hook.py |
ABAC-Genehmigungs-Hook — pluggable externe Autorisierung, vor der Abfrageausführung aufgerufen; Webhook-, gRPC- und unix_socket-Transporte; Scope pro Tabelle/Quelle/global; konfigurierbare Fallback-Richtlinie |
subscriptions/ |
SSE-Subscription-Zustand und -Zustellung |
discovery/ |
LLM-Beziehungserkennung (Claude-API) |
grpc/ |
Proto-Generierung, gRPC-Server, Reflection |
api_source/ |
REST-/GraphQL-/gRPC-API-Quellen mit PG-Cache |
kafka/ |
Kafka-Topic-Quellen, Sink, Schema Registry |
auth/ |
Pluggable Auth-Provider, Middleware, Rollenzuordnung |
core/ |
Konfiguration, Modelle, DB, Repositories, Secrets; Rollenmodell unterstützt parent_role_id und flatten_roles() für rekursive Rollenvererbung |
hasura_v2/ |
Hasura-v2-Metadaten-→-Provisa-Konfigurationskonverter |
ddn/ |
Hasura-DDN-Supergraph-→-Provisa-Konfigurationskonverter |
mongodb/ |
MongoDB-Quell-Connector |
elasticsearch/ |
Elasticsearch-Quell-Connector |
cassandra/ |
Cassandra-Quell-Connector |
prometheus/ |
Prometheus-Metriken-Quell-Connector |
source_adapters/ |
Generische Adapterschicht für Quellverbindungen |
Admin-API¶
Die Admin-Strawberry-GraphQL-API ist unter /admin/graphql (HTTP-Port 8001) eingehängt. Sie ist vom Daten-GraphQL-Endpunkt getrennt und erfordert die Rolle Superuser oder Admin.
| Capability | Beschreibung |
|---|---|
| Konfigurations-Download/-Upload | Vollständige Provisa-YAML-Konfiguration exportieren oder ersetzen |
| Beziehungs-Editor | Beziehungsdefinitionen erstellen, aktualisieren, löschen |
| KI-FK-Erkennung | Von Claude angetriebene FK-Kandidatenanalyse auslösen |
| Schema-Introspektion | Veröffentlichte Tabellen, Spalten und Rollen durchsuchen |
| View-Management | Definitionen materialisierter Sichten registrieren und verwalten |
KI-Modell-Konfiguration¶
GET /admin/ai-models und PUT /admin/ai-models konfigurieren die LLM-Pipeline für jede Organisation.
Die Einstellungen gelten pro Organisation: Die Wahl jeder Organisation liegt über der Deployment-Konfiguration und gilt ab der nächsten Anfrage, ohne Neustart.
Modellzuweisungen pro Operation. Sechs Operationen haben jeweils einen konfigurierbaren Vendor und Model-String:
| Operation | Was sie antreibt |
|---|---|
table_description |
LLM-generierte Tabellenbeschreibungen |
column_description |
LLM-generierte Spaltenbeschreibungen |
relationship_inference |
FK-Kandidatenerkennung |
sql_generation |
NL-→-SQL-Generierung |
table_selection |
Auswahl der Tabellen für den NL-Prompt |
mcp_chat |
Die Tool-Use-Schleife des Chat-Assistenten-Panels |
Das Vendor-Feld akzeptiert jeden aisuite-kompatiblen Vendor (anthropic, openai, groq, mistral, cohere und andere) oder einen lokalen Endpunkt (ollama, lmstudio). Ein leerer Model-String entfernt den Override der Organisation und kehrt zum Deployment-Standard zurück.
NL-Rate-Limit. Eine optionale Obergrenze für Anfragen pro Zeitraum, angewendet pro Rolle. Überschüssige Anfragen geben 429 mit Retry-After zurück.
Vektormodell-Registry. Eine Liste von Embedding-Modellen (Felder: id, provider, dimensions, optional api_key_env und base_url, Flag enabled). Vollständiger Listenersatz: Jeder Eintrag muss id, provider und dimensions haben, sonst wird der Schreibvorgang mit 400 abgelehnt.
API-Schlüssel. Der LLM-API-Schlüssel jedes Anbieters wird verschlüsselt über provisa.core.org_secrets gespeichert (siehe unten). Die Antwort auf GET sagt nur, ob für jeden Anbieter ein Schlüssel gesetzt ist, und gibt den Wert nie zurück. Ein leerer String für einen Anbieter löscht seinen Schlüssel, sodass LLM-Aufrufe für diesen Anbieter wieder die Anmeldung aus der Umgebungsvariablen des Deployments nutzen.
Verschlüsselte Secrets pro Organisation¶
provisa/core/org_secrets.py speichert Zugangsdaten, die niemals im Klartext in der Datenbank erscheinen dürfen. Er enthält die API-Keys der KI-Anbieter ({vendor}_api_key) und den Jev-Key (jev_api_key).
Werte werden mit dem prozessweiten encryption_service aus provisa.encryption.runtime verschlüsselt, demselben Mechanismus wie bei api_sources.auth.
Zwölf aisuite-kompatible Vendors werden unterstützt: anthropic, openai, cohere, groq, mistral, xai, deepseek, together, fireworks, nebius, sambanova und inception. Google, AWS und Azure sind ausgeschlossen, da sie Konfiguration über einen reinen API-Key hinaus erfordern (Projekt-IDs, IAM-Rollen, Region). Vendors mit lokalem Endpunkt (ollama, lmstudio) haben keinen Key und sind aus demselben Grund ausgeschlossen.
Die Übergabe von value=None an write_org_secret löscht die Zeile. Aufrufer, die ein Secret lesen, verbrauchen es sofort (z. B. um einen LLM-Client zu erstellen) und dürfen es in keiner API-Antwort widerspiegeln.
Automatisch generierte REST- & JSON:API-Endpunkte¶
Registrierte Tabellen werden neben der GraphQL-Schnittstelle als REST- und JSON:API-Endpunkte offengelegt.
| Schnittstelle | Mount-Pfad | Spezifikation |
|---|---|---|
| REST | /rest/<table-id> |
Einfaches GET/POST mit Query-Parametern |
| JSON:API | /jsonapi/<table-id> |
konform mit jsonapi.org — Paginierung, Beziehungen, Fehlerobjekte |
Diese Endpunkte wenden dieselbe Sicherheits-Pipeline (RLS, Maskierung, Rollenprüfungen) an wie der GraphQL-Endpunkt.
Subscriptions¶
SSE-Subscriptions werden unter GET /data/subscribe/{table} bereitgestellt. Drei Zustellungsmodi:
| Modus | Mechanismus | Wann verwendet |
|---|---|---|
| LISTEN/NOTIFY | PostgreSQL-LISTEN auf einem Kanal |
PG-Quellen mit Mutationsaktivität |
| Polling | Abfrage im Intervall erneut ausführen | Nicht-PG-Quellen, oder wenn CDC nicht verfügbar ist |
| Debezium-CDC | Kafka-Topic von Debezium-Connector | Hochfrequente Change-Streams |
Der Client empfängt text/event-stream mit einem JSON-Event pro geänderter Zeile oder Diff.
Event- & Webhook-System¶
Datenbankmutationen (INSERT/UPDATE/DELETE) können über die Module events/ und webhooks/ ausgehende Events auslösen.
Mutation executed → EventDispatcher → match event trigger rules
↓
WebhookExecutor → HTTP POST to configured URL
Event-Trigger werden in der Konfiguration definiert und nach Tabelle, Operationstyp und optionalem Zeilenfilter gematcht. Webhook-Payloads enthalten den Operationstyp, die geänderte Zeile und den Rollenkontext.
Hintergrunddienste¶
Vier Hintergrundschleifen starten während der App-Lifespan (api/app.py):
| Dienst | Intervall | Zweck |
|---|---|---|
| MV-Refresh-Schleife | 30 s | Pollt get_due_for_refresh(), führt CTAS oder DELETE+INSERT auf veralteten materialisierten Sichten aus |
| Hot-Replikations-Auswertung | replication.hot_interval |
Befördert eine Tabelle, deren Anweisungszahl ihren Schwellenwert überschritten hat, und fordert ihr Replikat an; stuft eine herab, die unter die Hälfte gefallen ist. Läuft in dem einen Prozess, der die Scheduler-Sperre hält |
| Hot-Table-Loader | konfigurierbar | Lädt kleine Referenztabellen in den In-Memory-Cache für Zugriff unter einer Millisekunde |
| API-Source-Poller | Intervall pro Quelle | Ruft entfernte REST-/GraphQL-/gRPC-Quellen erneut ab und cacht sie neu |
Hot-Tabellen und bei Last replizierte Tabellen¶
| Stufe | Storage | Beförderungskriterium | Zugriffslatenz |
|---|---|---|---|
| Hot | In-Process-Speicher | Zeilenanzahl < Schwellenwert, oder ist Beziehungsziel | <1 ms |
| Bei Last repliziert | Replikat im Store der Engine | replicate ist Default oder Hot-N und die regulierten Anweisungen, die die Tabelle lesen, haben ihren Schwellenwert überschritten |
Der eigene Lesezugriff der Engine auf ihren Store |
| Cold | Entfernte Quelle | Standard | 50–500 ms |
Eine Tabelle liegt in höchstens einer der beiden: Eine Tabelle, die die Hot-Stufe verwaltet, wird nicht wegen Last repliziert.
Metadaten-Import (Hasura v2 / DDN)¶
Bestehende Hasura-Deployments können ohne manuelles Umschreiben in eine Provisa-Konfiguration konvertiert werden.
| Modul | Eingabe | Ausgabe |
|---|---|---|
hasura_v2/ |
Hasura-v2-metadata.yaml |
Provisa-config.yaml |
ddn/ |
Hasura-DDN-Supergraph-JSON | Provisa-config.yaml |
Beide Konverter bilden getrackte Tabellen, Beziehungen, Berechtigungen und Remote-Schemas ab. Das Ergebnis ist eine vollständige Provisa-Konfiguration, bereit für das Deployment.
Apollo Federation¶
compiler/federation.py legt Provisa als Apollo-Federation-v2-Subgraph offen. Die Subgraph-SDL wird automatisch aus dem veröffentlichten Schema generiert, mit @key-Direktiven auf Primärschlüsselspalten und @external-/@provides-Annotationen auf quellenübergreifenden Beziehungen. Provisa beantwortet die vom Federation-Gateway benötigten _entities- und _service-Abfragen.
Cursor-basierte Paginierung¶
Alle Listenabfragen unterstützen Relay-artige Cursor-Paginierung über compiler/cursor.py. Clients übergeben first/after (vorwärts) oder last/before (rückwärts). Der Compiler kodiert die Zeilenposition als opaken Base64-Cursor und injiziert die passenden WHERE-/LIMIT-Klauseln. Jede Listenabfrage gibt ein pageInfo-Objekt zurück:
| Feld | Typ | Beschreibung |
|---|---|---|
hasNextPage |
Boolean | Wahr, wenn nach dieser Seite weitere Ergebnisse existieren |
hasPreviousPage |
Boolean | Wahr, wenn vor dieser Seite Ergebnisse existieren |
startCursor |
String | Cursor des ersten Knotens auf dieser Seite |
endCursor |
String | Cursor des letzten Knotens auf dieser Seite |
Aggregatabfragen¶
Jede registrierte Tabelle erhält ein automatisch generiertes {table}_aggregate-Root-Feld (compiler/aggregate_gen.py). Der Aggregat-Typ legt count, sum, avg, min, max pro numerischer Spalte offen sowie nodes für gefilterten Zeilenzugriff mit voller Feldauswahl (dieselbe RLS/Maskierung wie die Basisabfrage). Ein Aggregat über eine Tabelle, von jeder Oberfläche (SQL oder kompiliert), wird ohne jede Deklaration aus einer frischen materialisierten Sicht beantwortet, die es vorab aggregiert: Eine Sicht, deren SQL ein GROUP BY über eine Tabelle mit benannten SUM, COUNT, MIN und MAX ist, bedient die Aggregate, die nach einer Teilmenge ihrer Gruppierungsspalten gruppieren und nur auf ihnen filtern. Jedes Aggregat wird über die gespeicherten neu aggregiert (COUNT als Summe der gespeicherten Zählungen, AVG aus einer gespeicherten Summe und Zählung derselben Spalte); was die Sicht nicht exakt beantworten kann, bleibt der Tabelle überlassen. Die Umschreibung läuft nach der Governance, sodass ein Zeilenfilter auf einer der Gruppierungsspalten der Sicht auf die Sicht übertragen wird, und ein Zeilenfilter auf einer anderen Spalte oder eine Maske über einer summierten Spalte überlässt das Lesen der Tabelle.
Automatic Persisted Queries (APQ)¶
apq/cache.py implementiert das Apollo-APQ-Wire-Protokoll. Sendet ein Client nur einen Abfrage-Hash (extensions.persistedQuery), schlägt Provisa ihn in Redis nach. Bei einem Miss gibt es einen PersistedQueryNotFound-Fehler zurück; der Client wiederholt mit dem vollständigen Abfragetext, den Provisa speichert. Das ist getrennt vom Result-Caching (cache/).
Vererbte Rollen¶
Eine Rolle benennt über parent_role_id höchstens eine übergeordnete Rolle. Beim Runtime-Build durchläuft security/inheritance.py die Kette jeder Rolle vom Kind aufwärts und faltet sie in die eigenen Kopien der geladenen Daten des Builds, sodass jeder spätere Lookup weiterhin nach der handelnden Rollen-ID indiziert bleibt. Capabilities und Domänenzugriff sind die Vereinigung der Kette. Das visible_to einer Spalte, jeder Eintrag ihrer Maskenliste sowie das visible_to einer Metrik, Funktion oder eines Webhooks gelten für eine Rolle, wenn sie diese oder einen Vorfahren nennen. Die Schreibberechtigungen einer Tabelle werden je Operation auf dieselbe Weise aufgelöst: Eine Rolle ohne Berechtigung für eine Operation übernimmt die des nächstgelegenen Vorfahren, ihre eigene zuerst. RLS wird pro Tabelle aufgelöst, wobei das Kind Vorrang hat: die nächstgelegene Rolle in der Kette, die eine Regel für die Tabelle hat, ihre eigene Tabellenregel vor ihrer Domänenregel, liefert das eine Prädikat, das läuft; die Regel eines Vorfahren wird nie damit kombiniert. Eine Rolle erbt das Rate-Limit ihrer übergeordneten Rolle, wenn sie keines setzt. Eine übergeordnete Rolle muss existieren, darf nicht die Rolle selbst sein und darf keinen Zyklus schließen; eine Rolle, von der andere Rollen erben, kann nicht gelöscht werden, bis sie umgehängt wurden.
ABAC-Genehmigungs-Hook¶
auth/approval_hook.py ist ein pluggable Autorisierungs-Hook, der vor der Abfrageausführung, nach RLS und Maskierung, aufgerufen wird. Er integriert sich mit externen Policy Engines (OPA, benutzerdefinierte ABAC-Dienste).
| Einstellung | Beschreibung |
|---|---|
| Transport | webhook (HTTP POST), grpc oder unix_socket |
| Scope | Pro Tabelle, pro Quelle oder global |
| Fallback-Richtlinie | allow oder deny, wenn der Hook-Endpunkt nicht erreichbar ist |
Automatische Enum-Typ-Erkennung¶
compiler/enum_detect.py untersucht beim Erzeugen des Schemas die nativen Enum-Typen von PostgreSQL (pg_enum). Eine Spalte mit einem benutzerdefinierten PostgreSQL-Enum-Typ wird zu einem GraphQL-Enum-Typ, und ihre Werte werden zu Enum-Mitgliedern statt String-Skalaren.
Geplante Trigger¶
scheduler/jobs.py verwendet APScheduler, um Hintergrund-Jobs auszuführen, die als Cron- oder Intervall-Trigger definiert sind. Jeder Job kann an eine Webhook-URL POSTen oder eine Mutation gegen den Daten-Endpunkt ausführen; die Live-Kafka-Ausgabe einer Tabelle (live.outputs) wird auf demselben Scheduler abgefragt. Trigger werden über die Admin-API (scheduledTrigger-Mutations) oder den Schlüssel scheduled_triggers in der YAML-Konfiguration konfiguriert.
Federation-Performance-Hinweise¶
compiler/hints.py parst Steward-Hinweise, die als Kommentare in Abfragen eingebettet sind, mit Provisas Kommentarsyntax. Das Hinweisformat variiert je nach Abfragesprache:
| Hinweis | Effekt |
|---|---|
route=federated |
Federation über die Federation-Engine erzwingen, direktes Treiber-Routing umgehen |
route=direct |
Direkte Treiberausführung erzwingen |
cache=true |
Diese Abfrage für den Antwort-Cache anmelden (SQL-Kommentarform: -- @provisa cache=true) |
cache_ttl=N |
Anmelden und das gecachte Ergebnis N Sekunden behalten (-- @provisa cache_ttl=N) |
Der Antwort-Cache ist pro Anfrage opt-in: GraphQL nutzt die Direktive @cached(ttl: Int), SQL und Cypher den Kommentar cache/cache_ttl, gRPC die Aufruf-Metadaten x-provisa-cache / x-provisa-cache-ttl. Der Hinweis gilt auf jedem Transport, der die Sprache trägt (Cypher über HTTP, Bolt und Arrow Flight; GraphQL über HTTP und Arrow Flight). Eine Anfrage ohne Hinweis liest und schreibt den Cache nie, und keine Form liest aktuellere Daten, als die Quellen- und Tabelleneinstellungen des Betreibers erlauben. Siehe Cache.
Schreibberechtigungen und Presets in Mutationen¶
Die write_grants einer Tabelle legen je Rolle und Operation fest, welche Spalten ein Schreibvorgang nennen darf, welche Zeilen er berühren darf und welche Werte die Berechtigung selbst setzt. compiler/write_admission.py lässt jeden Schreibvorgang in der Governance-Stufe zu, sodass GraphQL, SQL, Cypher und die übrigen Oberflächen dieselbe Antwort erhalten: das Recht write, die Berechtigung der Rolle für die Operation, die Spalten, die der Anweisung hinzugefügten Presets, das per AND in das WHERE aufgenommene Prädikat rows und das auf die neuen Zeilen ausgewertete check, bevor etwas eine Quelle erreicht. Presets stammen aus einer Sitzungsvariable, einem Literal oder dem Zeitpunkt des Schreibvorgangs. Die Anweisung darf eine Preset-Spalte nicht liefern, daher stehen Presets nicht im generierten GraphQL-Mutations-Eingabetyp.
GraphQL-Voyager-Schema-Explorer¶
Die Admin-UI (provisa-ui/src/pages/SchemaExplorer.tsx) bettet GraphQL Voyager als interaktive Schema-Visualisierung ein. Es zeichnet das rollenbezogene Schema als Entity-Relationship-Diagramm, das sich verschieben, zoomen und anklicken lässt, mit Tabellen als Knoten und Beziehungen als Kanten. Das gezeigte Schema ist immer auf die gerade gewählte Rolle gefiltert.
Reihenfolge der Sicherheitsdurchsetzung¶
Für das Abfragen gibt es keine Fähigkeitsschranke; die Governance kommt ganz aus Kontrollen auf der Datenebene. Eine Raw-SQL-Anfrage wird für jede Tabelle außerhalb des Objektbereichs der Rolle abgelehnt (HTTP 403), bevor die Governance läuft.
- Objektsichtbarkeit: Rollenspezifisches Schema verbirgt nicht autorisierte Tabellen/Spalten; Tabellen außerhalb des Scopes in Raw-SQL werden abgelehnt
- Beziehungsdurchsetzung: Traversierungen müssen im genehmigten Beziehungskatalog existieren, sofern die Rolle nicht
ignore_relationshipshält. Unter den vordefinierten Systemrollen hält nurmodeleres. Im Hochsicherheitsmodus wird die Capability ignoriert, und keine Traversierung entkommt dem Katalog - RLS: WHERE-Klausel-Injektion pro Tabelle, pro Rolle
- Spaltenmaskierung: Datentransformation pro Spalte, pro Rolle
- Zeilenobergrenze (LIMIT): Zeilenanzahl-Obergrenze für Rollen ohne
full_results; zufälliges statistisches Sampling ist ein separates Nutzer-Abfrage-Feature
Alle vier Abfrageschnittstellen (HTTP, Flight, gRPC, pgwire) setzen dieselbe Stage-2-Governance-Pipeline durch; kein Client-Pfad kann sie umgehen, ohne den Server zu umgehen.
Skalierbarkeitsgrenzen¶
Provisa ist eine dünne Schicht zum Kompilieren und Routen und fügt der Abfragelatenz einstellige Millisekunden hinzu. Wege, auf denen Provisa die Ergebnisdaten serialisiert, sind allerdings durch den Prozessspeicher begrenzt. Zwei Wege haben gar keine Grenze:
| Pfad | Speicherbegrenzt? | Geeignet für |
|---|---|---|
| JSON inline (HTTP) | Ja | Kleine bis mittlere Ergebnisse |
| Arrow-Flight-Streaming (gRPC :8815) | Nein | Unbegrenzt — Streaming über Zaychik oder Warehouse-Arrow-API |
| Protobuf gRPC inline (:50051) | Ja | Mittlere Ergebnisse, Service-zu-Service |
| Redirect: Provisa-Upload (JSON, CSV, NDJSON, Arrow IPC) | Ja | Mittlere Ergebnisse, Datei-Download |
| Redirect: CTAS (Parquet, ORC) | Nein | Unbegrenzt — Federation-Engine schreibt nach S3 |
Schwellenwert-Sondierung¶
Für schwellenwertbasierten Redirect injiziert Provisa LIMIT threshold + 1 als Sonde in die Abfrage. Hat das Ergebnis weniger Zeilen, wird es inline zurückgegeben (vollständiges Ergebnis, keine verschwendete Arbeit). Erreicht das Ergebnis das Limit, wird die Sonde verworfen und die vollständige Abfrage über CTAS oder Provisa-Upload erneut ausgeführt. Das vermeidet SELECT COUNT(*) (das manche Quellen nicht optimieren) und funktioniert bei jeder Quelle.
Für große analytische Workloads verwenden Sie entweder:
- Arrow Flight (Port 8815) für Streaming zu Daten-Tools; Batches fließen durch Provisa, ohne materialisiert zu werden
- Parquet-/ORC-Redirect für dateibasierte Exporte: Die Federation-Engine schreibt direkt nach S3, und Provisa gibt eine signierte URL zurück
Infrastruktur¶
| Dienst | Image | Port | Zweck |
|---|---|---|---|
| Provisa API | (Host-Prozess) | 8001 | HTTP-/REST-Endpunkt |
| Provisa Flight | (Host-Prozess) | 8815 | Arrow-Flight-gRPC-Server |
| Provisa gRPC | (Host-Prozess) | 50051 | Protobuf-gRPC-Server |
| Federation Engine | trinodb/trino (Standard) oder externes Warehouse |
8080 / variiert | Query-Federation-Engine — Trino für den eingebetteten Stack; Snowflake/Databricks/BigQuery/Fabric/Synapse/DuckDB für Warehouse-Ziele |
| Zaychik | provisa-zaychik (aus dem Quellcode gebaut) |
8480 | Arrow-Flight-SQL-Proxy für Trino; für Warehouse-Engines nicht erforderlich |
| PostgreSQL | postgres:16 |
5432 | Konfigurationsmetadaten + Iceberg-Katalog |
| MongoDB | mongo:7 |
27017 | Demo-NoSQL-Datenquelle |
| MinIO | minio/minio |
9000/9001 | S3-kompatibler Objektspeicher |
| Redis | redis:7-alpine |
6379 | Query-Result-Cache |
| PgBouncer | edoburu/pgbouncer |
6432 | Connection Pooling für PG |
| Kafka | confluentinc/cp-kafka:7.6.0 |
9092 | Streaming-Datenquellen |
| Schema Registry | confluentinc/cp-schema-registry:7.6.0 |
8081 | Avro-/Protobuf-Schema-Management |