Provisa-Architektur¶
Überblick¶
Provisa ist eine konfigurationsgesteuerte Datenvirtualisierungsplattform, die speziell dafür entwickelt wurde, einen Semantic Layer anzutreiben — von kleinen Teams bis zu großen Unternehmen. Sie bietet eine einheitliche API über heterogene Datenquellen mit Governance, Sicherheit und Performance-Optimierung. Clients fragen über SQL, GraphQL oder Cypher ab; alle drei sind erstklassige Schnittstellen mit identisch angewendeter Governance. (REQ-002, REQ-038)
Die Unterscheidung des Semantic Layer ist wichtig. Um dem Semantic Layer etwas hinzuzufügen, müssen Sie neue Datenquellen oder Aggregate innerhalb der Datenvirtualisierungsschicht erstellen. Das schafft eine saubere Trennung — keine neuen Ergänzungen der Semantik können außerhalb der Plattform vorgenommen werden, was echte Data Governance ermöglicht. (REQ-136) Die Durchsetzung erfolgt auf Compiler-Ebene: Der genehmigte Beziehungskatalog ist die Source of Truth, unabhängig davon, welche Abfragesprache verwendet wird. (REQ-002)
Provisa ist darauf ausgelegt, für operative Bedürfnisse hochperformant und für analytische Bedürfnisse von Unternehmen hochskalierbar zu sein. Eine einzige Plattform bedient beides, ohne Geschwindigkeit oder Skalierbarkeit zu opfern.
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 eigenständiger Transport. Alle vier wenden dieselbe Sicherheits-Pipeline an (RLS, Maskierung, Sampling, Rollenprüfungen). (REQ-002, REQ-038) Clients sprechen nie direkt mit der Federation-Engine. (REQ-266) Die „Abfragesprache" (SQL / GraphQL / Cypher) ist orthogonal zum Transport — mehrere Sprachen können über denselben Transport ankommen.
| 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 (REQ-001, REQ-267) |
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. (REQ-029, REQ-137)
Arrow Flight (Port 8815)¶
Natives Arrow-Columnar-Transport über gRPC. (REQ-045, REQ-143) 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: (REQ-144)
Das vollständige Ergebnis wird im Provisa-Speicher nie materialisiert — Batches werden weitergeleitet, sobald sie eintreffen. (REQ-145) Das macht Arrow Flight zu einem unbegrenzten Pfad, geeignet für beliebig große Ergebnisse.
Protobuf gRPC (Port 50051)¶
Automatisch generierte .proto-Datei aus dem Datenschema, pro Rolle generiert. (REQ-525) Streaming-Abfragen (eine Nachricht pro Zeile), unäre Mutations. Server Reflection aktiviert. (REQ-526) Rolle über den Metadaten-Key x-provisa-role.
PostgreSQL-Wire-Protokoll / pgwire (konfigurierbarer Port)¶
Implementiert das PostgreSQL-Frontend-/Backend-Wire-Protokoll über die buenavista-Bibliothek. (REQ-527) 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. (REQ-266, REQ-002) Aktiviert durch Setzen von PROVISA_PGWIRE_PORT auf einen Port ungleich null.
Anfrage-Pipeline¶
Drei Abfragesprachen werden akzeptiert. Alle laufen nach ihren jeweiligen Parse-/Kompilierschritten bei der Governance zusammen. (REQ-262, REQ-263) Nur GraphQL unterstützt Schreibvorgänge. (REQ-037) Es gibt kein Capability-Gate für das Abfragen selbst — jede authentifizierte Identität darf in jeder Sprache abfragen, und Daten werden ausschließlich durch Objektsichtbarkeit, RLS und Maskierung geregelt. (REQ-001)
| 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 (REQ-267) |
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 (REQ-865) |
| 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 (REQ-875) |
| 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 | GraphQL-Mutation — immer direkt, nie föderiert |
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. (REQ-863)
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. (REQ-534) 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. (REQ-144)
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 und ermöglicht Arrow-Streaming End-to-End, ohne Ergebnisse zu materialisieren. (REQ-145)
Iceberg-Ergebniskatalog¶
Der CTAS-Redirect verwendet einen Iceberg-Connector (Katalog results), unterstützt durch einen JDBC-Katalog auf der bestehenden PostgreSQL-Instanz. (REQ-169) Iceberg schreibt Parquet-/ORC-Dateien direkt über das native S3-Dateisystem (fs.native-s3.enabled=true) nach MinIO/S3.
Federation-Engines¶
Provisa wählt beim Start eine Federation-Engine über die Umgebungsvariable PROVISA_ENGINE, die persistierte Admin-UI-Konfiguration oder den Standardwert. Ist nichts gesetzt, ist DuckDB der Standard — vollständig in-process, kein externer Dienst (REQ-989). Details zur Auswahl siehe 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. [tool-verified: engine.py _ENGINE_BUILDERS, ENGINE_REGISTRY]
Treiberklassen (REQ-840) [tool-verified: engine.py DriverClass]¶
| 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 [tool-verified: engine.py _ENGINE_BUILDERS]¶
| 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 (REQ-986) | ClickHouse-Zugangsdaten |
snowflake |
Snowflake | Ja | External Stage + External Table (REQ-988) | PROVISA_ENGINE_URL |
databricks |
Databricks SQL | Ja | Unity-Catalog-External-Tables über REST (REQ-987) | 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 (REQ-989) [tool-verified: engine.py build_duckdb_engine, _embedded_duckdb_materialize_default]¶
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.
Da DuckDB pro Datei nur einen einzigen Writer erlaubt, schreibt store_connection.py über die eigene Verbindung der Engine in den eingebetteten Store — nie über eine zweite, unabhängige Verbindung. Das ist der eine Fall, in dem Engine und Materialisierungs-Store per Design einen Datei-Handle teilen. [tool-verified: store_connection.py module docstring]
Arrow-natives Lese-Transport (REQ-986, REQ-987, REQ-988) [tool-verified: engine.py build_*_engine capabilities=]¶
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) [tool-verified: engine.py _warehouse_connectors]¶
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. [tool-verified: connector_base.py Mechanism, engine.py _warehouse_connectors]
Attach stellt alle Voraussetzungen zur Attach-Zeit automatisch bereit:
| Engine | Objekt-/Lake-Formate | Mechanismus | Auto-Provisioning [tool-verified] |
|---|---|---|---|
| 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 (REQ-990) [tool-verified: core/database.py:436, store_connection.py:99]¶
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. (REQ-029)
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 verarbeitet Provisa die Daten nie — die Federation-Engine schreibt Dateien direkt nach MinIO/S3. (REQ-138) Das ist der bevorzugte Pfad für große analytische Exporte.
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. (REQ-137)
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)
(REQ-027, REQ-028, REQ-030, REQ-279)
Federation-Abfrageoptimierung¶
Provisa initialisiert den kostenbasierten Optimizer der Federation-Engine automatisch, sodass quellenübergreifende Abfragepläne auf realer Datenverteilung basieren, nicht auf fest codierten Standardwerten.
Automatische Statistiken (ANALYZE)¶
Bei der Quellenregistrierung führt Provisa für jede veröffentlichte Tabelle ANALYZE catalog.schema.table aus. (REQ-275) 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 Statistikunterstützung variiert je nach Connector. PostgreSQL, MySQL, Hive, Iceberg und Delta Lake unterstützen ANALYZE vollständig. Die Connectors für MongoDB und Cassandra bieten teilweise oder keine Unterstützung. Provisa schluckt ANALYZE-Fehler stillschweigend — die Registrierung wird nie blockiert. (REQ-275)
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. (REQ-280)
Admin: Statistiken aktualisieren¶
Statistikerfassung bei Bedarf über die Admin-API erneut ausführen: (REQ-276)
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 optimieren teure Abfragen transparent, indem sie Ergebnisse vorberechnen und cachen.
Beziehungen als Hinweise für materialisierte Sichten¶
Eine Beziehungsdeklaration ist nicht nur ein Governance-Artefakt — sie ist auch die strukturelle Beschreibung einer Join-Form. Genau diese Form braucht der Optimizer für materialisierte Sichten: die Tabellen, die Spalten, den Join-Typ. Das bedeutet, eine Beziehung kann die Materialisierung direkt antreiben.
Für quellenübergreifende Beziehungen geschieht dies automatisch beim Start: Jede Beziehung mit materialize: true, deren Beine in mehr als einer Quelle landen, erzeugt eine JoinPattern-materialisierte Sicht (auto-mv-<rel_id>). (REQ-158) Keine separate Konfiguration einer materialisierten Sicht ist erforderlich. Sieht der Compiler diesen Join in einer Abfrage, ersetzt der Rewriter transparent das vormaterialisierte Ergebnis. Beziehungen innerhalb derselben Quelle erzeugen nichts — diese JOINs sind über direkte Ausführung bereits schnell. (REQ-159) [tool-verified: provisa/api/app_loaders.py]
Eine junction-gestützte Beziehung materialisiert ihre Traversierung statt eines direkten Joins: Die assoziative Tabelle ist ein drittes Bein, sodass das Muster den Quell-Hop, den Junction-Hop und den Diskriminator trägt, der die Zeilenmenge auf einen Kantentyp festlegt, wobei die eigenen Spalten der Junction neben denen des Ziels in der Sicht landen. (REQ-1586) Da die Junction als Bein zählt, ist eine Kante, deren Junction in einer anderen Quelle sitzt als die beiden Tabellen, die sie verbindet, quellenübergreifend, selbst wenn diese beiden übereinstimmen. Der Rewriter matcht die beiden Hops als Kette — der zweite muss beim Alias beginnen, das der erste eingeführt hat —, sodass eine Abfrage, die dieselben zwei Tabellen ohne Durchlaufen der Junction erreicht, die Basistabellen liest, und eine für einen Diskriminatorwert gebaute Sicht nie eine auf einen anderen gefilterte Traversierung beantwortet.
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: (REQ-158)
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). (REQ-159) Die materialisierte Sicht startet im Status STALE und wird von der Hintergrund-Refresh-Schleife aktualisiert, bevor sie vom Abfrage-Optimizer verwendet wird. (REQ-160)
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. (REQ-160, REQ-234)
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 (REQ-351), 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 (REQ-527, REQ-883, REQ-891) |
vector/ |
Vektorsuche — Modellregistry, Embedding-Provider (openai/ollama/huggingface), cosine_similarity()-Übersetzung, pgvector-Fallback-Cache, deklarative Embedding-Generierung (REQ-419–431) |
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 (REQ-988) |
federation/databricks_connectors.py |
Databricks-UC-External-Table-ATTACH-Connectors (REQ-987) |
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 (REQ-989, REQ-990) |
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, Mutations oder Kafka-Sink-Veröffentlichungen auslösen |
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 |
(REQ-164, REQ-165, REQ-166, REQ-167)
KI-Modell-Konfiguration¶
GET /admin/ai-models und PUT /admin/ai-models konfigurieren die LLM-Pipeline für jede Organisation. (REQ-464, REQ-419, REQ-500, REQ-370, REQ-1349)
Einstellungen sind organisationsbezogen: Die Wahl jeder Organisation legt sich über die Deployment-Konfiguration und wirkt ab der nächsten Anfrage — kein Neustart erforderlich. (REQ-1349) [tool-verified: provisa/api/admin/ai_models_router.py:38-39]
Modellzuweisungen pro Operation. Fünf NL-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 |
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. [tool-verified: provisa/api/admin/ai_models_router.py:29-35, provisa-ui/src/components/admin/AiModelsTab.tsx:43-60]
NL-Rate-Limit. Eine optionale Obergrenze für Anfragen pro Zeitraum, angewendet pro Rolle. Überschüssige Anfragen geben 429 mit Retry-After zurück. [tool-verified: provisa-ui/src/components/admin/AiModelsTab.tsx:306-313]
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. [tool-verified: provisa/api/admin/ai_models_router.py:122-131]
API-Keys. LLM-API-Keys pro Vendor werden verschlüsselt über provisa.core.org_secrets gespeichert (siehe unten). Die GET-Antwort meldet nur, ob für jeden Vendor ein Key gesetzt ist — der Wert wird nie zurückgegeben. Das Senden eines leeren Strings für einen Vendor löscht diesen Key und setzt LLM-Aufrufe für diesen Vendor auf die Umgebungsvariablen-Zugangsdaten des Deployments zurück. (REQ-1395, REQ-1398) [tool-verified: provisa/api/admin/ai_models_router.py:76-78, provisa/api/admin/ai_models_router.py:149-165]
Verschlüsselte Secrets pro Organisation¶
provisa/core/org_secrets.py speichert Zugangsdaten, die niemals im Klartext in der Datenbank erscheinen dürfen. Derzeit beschränkt auf LLM-Vendor-API-Keys ({vendor}_api_key). (REQ-1395, REQ-1398) [tool-verified: provisa/core/org_secrets.py]
Werte werden über den prozessweiten encryption_service aus provisa.encryption.runtime verschlüsselt — derselbe Mechanismus wie api_sources.auth. [tool-verified: provisa/core/org_secrets.py:16-17]
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. [tool-verified: provisa/core/org_secrets.py:33-53]
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. [tool-verified: provisa/core/org_secrets.py:97-117]
Automatisch generierte REST- & JSON:API-Endpunkte¶
Registrierte Tabellen werden neben der GraphQL-Schnittstelle als REST- und JSON:API-Endpunkte offengelegt. (REQ-256, REQ-257)
| 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. (REQ-002, REQ-038)
Subscriptions¶
SSE-Subscriptions werden unter GET /data/subscribe/{table} bereitgestellt. Drei Zustellungsmodi: (REQ-258)
| 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. (REQ-172, REQ-173, REQ-220)
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 |
| Warm-Table-Manager | konfigurierbar | Befördert häufig abgefragte Tabellen in den Iceberg-Local-SSD-Cache |
| 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 |
(REQ-160, REQ-238, REQ-239, REQ-236)
Hot-/Warm-Table-Caching-Stufen¶
| Stufe | Storage | Beförderungskriterium | Zugriffslatenz |
|---|---|---|---|
| Hot | In-Process-Speicher | Zeilenanzahl < Schwellenwert, oder ist Beziehungsziel | <1 ms |
| Warm | Iceberg auf lokaler SSD | Abfragehäufigkeits-Schwellenwert überschritten | ~5–20 ms |
| Cold | Entfernte Quelle | Standard | 50–500 ms |
(REQ-230, REQ-236, REQ-238, REQ-241)
Metadaten-Import (Hasura v2 / DDN)¶
Bestehende Hasura-Deployments können ohne manuelles Umschreiben in eine Provisa-Konfiguration konvertiert werden. (REQ-182, REQ-183)
| 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. (REQ-182, REQ-183)
Apollo Federation¶
compiler/federation.py legt Provisa als Apollo-Federation-v2-Subgraph offen. (REQ-259) 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. (REQ-259)
Cursor-basierte Paginierung¶
Alle Listenabfragen unterstützen Relay-artige Cursor-Paginierung über compiler/cursor.py. (REQ-218) 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). (REQ-196) 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). (REQ-196, REQ-198) Aggregatabfragen sind für das Aggregate-MV-Routing geeignet — siehe mv/aggregate_catalog.py. (REQ-198)
Automatic Persisted Queries (APQ)¶
apq/cache.py implementiert das Apollo-APQ-Wire-Protokoll. (REQ-288) Sendet ein Client nur einen Abfrage-Hash (extensions.persistedQuery), schlägt Provisa ihn in Redis nach. (REQ-289) Bei einem Miss gibt es einen PersistedQueryNotFound-Fehler zurück; der Client wiederholt mit dem vollständigen Abfragetext, den Provisa speichert. (REQ-288) Das ist getrennt vom Result-Caching (cache/).
Vererbte Rollen¶
Eine Rolle benennt über parent_role_id höchstens eine übergeordnete Rolle. (REQ-215) 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. (REQ-1677) Capabilities und Domänenzugriff sind die Vereinigung der Kette. Die visible_to, writable_by und unmasked_to einer Spalte sowie das visible_to einer Metrik, Funktion oder eines Webhooks gelten für eine Rolle, wenn sie diese oder einen Vorfahren nennen. 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. (REQ-1677)
ABAC-Genehmigungs-Hook¶
auth/approval_hook.py ist ein pluggable Autorisierungs-Hook, der vor der Abfrageausführung, nach RLS und Maskierung, aufgerufen wird. (REQ-203) 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 introspiziert native PostgreSQL-Enum-Typen (pg_enum) zur Schema-Generierungszeit. (REQ-221) Spalten, die einen benutzerdefinierten PostgreSQL-Enum-Typ verwenden, werden zu GraphQL-Enum-Typen hochgestuft — ihre Werte werden zu Enum-Membern statt zu String-Skalaren.
Geplante Trigger¶
scheduler/jobs.py verwendet APScheduler, um Hintergrund-Jobs auszuführen, die als Cron- oder Intervall-Trigger definiert sind. (REQ-216) Jeder Job kann an eine Webhook-URL POSTen, eine Mutation gegen den Daten-Endpunkt ausführen oder Abfrageergebnisse an ein Kafka-Topic veröffentlichen. Trigger werden über die Admin-API (scheduledTrigger-Mutations) oder den Schlüssel scheduled_triggers in der YAML-Konfiguration konfiguriert. (REQ-216)
Federation-Performance-Hinweise¶
compiler/hints.py parst Steward-Hinweise, die als Kommentare in Abfragen eingebettet sind, mit Provisas Kommentarsyntax. (REQ-279) 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 |
Column Presets in Mutations¶
compiler/mutation_gen.py unterstützt serverseitige Presets pro Spalte, angewendet bei INSERT oder UPDATE. (REQ-214) Presets sind nicht im generierten GraphQL-Mutation-Input-Typ enthalten — sie werden vom Compiler transparent injiziert. Preset-Typen: static (Literalwert) oder session (Wert aus Request-Session/-Header, z. B. x-hasura-user-id). (REQ-214)
GraphQL-Voyager-Schema-Explorer¶
Die Admin-UI (provisa-ui/src/pages/SchemaExplorer.tsx) bettet GraphQL Voyager als interaktives Schema-Visualisierungstool ein. (REQ-248) Es rendert das rollenbezogene Schema als navigierbares Entity-Relationship-Diagramm — Tabellen als Knoten, Beziehungen als Kanten. Das angezeigte Schema ist immer auf die aktuell gewählte Rolle gefiltert.
Reihenfolge der Sicherheitsdurchsetzung¶
Es gibt kein Capability-Gate für das Abfragen — Governance wird vollständig über Kontrollen auf Datenebene ausgedrückt. (REQ-001) Eine Raw-SQL-Anfrage lehnt jede Tabelle außerhalb des Objekt-Scopes der Rolle ab (HTTP 403), bevor die Governance läuft. (REQ-267)
- Objektsichtbarkeit: Rollenspezifisches Schema verbirgt nicht autorisierte Tabellen/Spalten; Tabellen außerhalb des Scopes in Raw-SQL werden abgelehnt (REQ-039, REQ-267)
- Beziehungsdurchsetzung: Traversierungen müssen im genehmigten Beziehungskatalog existieren, sofern die Rolle nicht
ignore_relationshipshält — unter den vordefinierten Systemrollen nurmodeler(REQ-001, REQ-1297). Im Hochsicherheitsmodus wird die Capability ignoriert, und keine Traversierung entkommt dem Katalog (REQ-693) - RLS: WHERE-Klausel-Injektion pro Tabelle, pro Rolle (REQ-040, REQ-041, REQ-263)
- Spaltenmaskierung: Datentransformation pro Spalte, pro Rolle (REQ-263)
- Zeilenobergrenze (LIMIT): Zeilenanzahl-Obergrenze für Rollen ohne
full_results; zufälliges statistisches Sampling ist ein separates Nutzer-Abfrage-Feature (REQ-263, REQ-478)
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. (REQ-002, REQ-038, REQ-266)
Skalierbarkeitsgrenzen¶
Provisa ist eine dünne Kompilierungs- und Routing-Schicht — sie fügt der Abfragelatenz einstellige Millisekunden hinzu. Pfade, auf denen Provisa Ergebnisdaten serialisiert, sind jedoch durch den Prozessspeicher begrenzt. Zwei Pfade sind wirklich unbegrenzt:
| 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. (REQ-140) 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 (REQ-145)
- Parquet-/ORC-Redirect für dateibasierte Exporte — die Federation-Engine schreibt direkt nach S3, Provisa gibt eine signierte URL zurück (REQ-138, REQ-044)
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 |