Provisa-Architektur¶
Überblick¶
Provisa ist eine konfigurationsgetriebene Data-Virtualization-Plattform, speziell dafür entworfen, eine semantische Schicht von kleinen Teams bis zu großen Unternehmen zu betreiben. 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 vollwertige Schnittstellen mit identisch angewendeter Governance. (REQ-002, REQ-038)
Die Unterscheidung der semantischen Schicht ist wichtig. Um die semantische Schicht zu erweitern, müssen neue Datenquellen oder Aggregate innerhalb der Data-Virtualization-Schicht angelegt werden. Das schafft eine saubere Trennung — keine Erweiterungen 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 Anforderungen hochperformant und für analytische Enterprise-Anforderungen 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 eigener Transport. Alle vier wenden dieselbe Sicherheits-Pipeline an (RLS, Masking, Sampling, Rollenprüfungen). (REQ-002, REQ-038) Clients sprechen nie direkt mit der Föderations-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 (via Arrow Flight SQL) | Datentools (Pandas, DuckDB, Spark, ADBC) |
| 50051 | Protobuf gRPC | Pro Rolle generierte Proto-RPCs | Service-zu-Service mit typisierten Contracts |
| konfigurierbar¹ | PostgreSQL-Wire-Protokoll (pgwire) | SQL | psql, DBeaver, SQLAlchemy, jeder PG-kompatible Client |
¹ PROVISA_PGWIRE_PORT setzen (z. B. 5433). Deaktiviert, wenn nicht gesetzt oder 0.
HTTP (Port 8001)¶
Mehrere Endpunkte unter demselben Port, unterschieden nach Pfad:
| Pfad | Sprache | Anmerkungen |
|---|---|---|
POST /data/graphql |
GraphQL | Lese- und Mutationsoperationen; APQ-Hash wird via extensions.persistedQuery akzeptiert |
POST /data/sql |
SQL | Nur lesend; kein Capability-Gate — geregelt durch Objektsichtbarkeit + RLS + Masking (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-Kompatibilität) | Neo4j-HTTP-API-Kompatibilitäts-Shim |
POST /admin/graphql |
GraphQL | Admin-API (Superuser-/Admin-Rolle erforderlich) |
Alle Pfade liefern standardmäßig JSON zurück. Accept: text/csv, application/vnd.apache.parquet, application/vnd.apache.arrow.stream und application/octet-stream (rohe Binärdaten) werden über 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)¶
Nativer Arrow-Columnar-Transport über gRPC. (REQ-045, REQ-143) Clients senden ein JSON-Ticket:
und erhalten Arrow-RecordBatches lazy gestreamt. Wenn der Zaychik-Flight-SQL-Proxy verfügbar ist, fließen die Daten Ende-zu-Ende als Stream von Arrow-Record-Batches: (REQ-144)
Das vollständige Ergebnis wird niemals im Provisa-Speicher 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 generiertes .proto, pro Rolle erzeugt aus dem Datenschema. (REQ-525) Streaming-Abfragen (eine Nachricht pro Zeile), unäre Mutationen. Server Reflection aktiviert. (REQ-526) Rolle via x-provisa-role-Metadaten-Schlüssel.
PostgreSQL-Wire-Protokoll / pgwire (konfigurierbarer Port)¶
Implementiert das PostgreSQL-Frontend-/Backend-Wire-Protokoll mittels der buenavista-Bibliothek. (REQ-527) Jeder PostgreSQL-kompatible Client — psql, DBeaver, SQLAlchemy mit psycopg2, JDBC — kann sich ohne Anpassung verbinden. Akzeptiert nur SQL. Die vollständige Governance-Pipeline (RLS, Masking, Domänenberechtigungen) gilt identisch für pgwire-Verbindungen. (REQ-266, REQ-002) Aktiviert, indem PROVISA_PGWIRE_PORT auf einen Port ungleich null gesetzt wird.
Anfrage-Pipeline¶
Drei Abfragesprachen werden akzeptiert. Alle laufen nach ihren jeweiligen Parse-/Compile-Schritten bei der Governance zusammen. (REQ-262, REQ-263) Nur GraphQL unterstützt Schreiboperationen. (REQ-037) Es gibt kein Capability-Gate auf das Abfragen selbst — jede authentifizierte Identität darf in jeder Sprache abfragen, und Daten werden ausschließlich durch Objektsichtbarkeit, RLS und Masking geregelt. (REQ-001)
| Schnittstelle | Lesen | Schreiben | Abfrage-Gate |
|---|---|---|---|
GraphQL (/data/graphql) |
Ja | Ja (Mutationen) | 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 | Cache-Treffer beim Ergebnis — wird zuerst geprüft, liefert das gespeicherte Ergebnis ohne Ausführung (REQ-865) |
| Cheap-Count | Abfrage der Form count(*) über eine nicht materialisierte Quelle, die einen exakten nativen Count exponiert — wird zum nativen Count-Aufruf statt zur Materialisierung geroutet (REQ-875) |
| Direct | Einzelquelle + hat nativen Treiber + hat Föderations-Connector |
| Federation | Mehrquellen-Föderation, oder Quelle hat Connector, aber keinen Treiber |
| Materialize | Quelle hat keinen Föderations-Connector — zunächst abrufen und in S3/PG cachen |
| Mutation | GraphQL-Mutation — immer direkt, nie föderiert |
Das Routing verwendet die Ausgabe der Post-Governance-Optimierungsstufe, niemals das ungeprüfte, governance-vorbearbeitete SQL vor der Optimierung. 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 auf eine einzige Live-Quelle zusammenfällt, wird daher als direkt neu geroutet. (REQ-863)
Multi-Root-Abfragen¶
GraphQL-Abfragen mit mehreren Root-Feldern (z. B. { orders { id } customers { name } }) werden in separate 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 Pro-Feld-Einträgen in
redirects - Binärformate (Parquet, Arrow) werden nur für Single-Root-Abfragen unterstützt
Föderations-Ausführungspfade¶
| Pfad | Transport | Über | Wann verwendet |
|---|---|---|---|
| REST | Föderations-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 | Föderations-Engine-Client (HTTP :8080) | Direktes Schreiben, Iceberg nach S3 | Parquet-/ORC-Redirect |
Zaychik-Arrow-Flight-SQL-Proxy¶
Die Föderations-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, was Ende-zu-Ende-Arrow-Streaming ohne Materialisierung der Ergebnisse ermöglicht. (REQ-145)
Iceberg-Ergebniskatalog¶
CTAS-Redirect nutzt einen Iceberg-Connector (results-Katalog), gestützt von einem JDBC-Katalog auf der bestehenden PostgreSQL-Instanz. (REQ-169) Iceberg schreibt Parquet-/ORC-Dateien direkt nach MinIO/S3 über das native S3-Dateisystem (fs.native-s3.enabled=true).
Föderations-Engines¶
Provisa wählt beim Start eine Föderations-Engine über die Umgebungsvariable PROVISA_ENGINE, die persistierte Admin-UI-Konfiguration oder den Standard aus. Wenn nichts gesetzt ist, ist DuckDB der Standard — vollständig in-process, kein externer Dienst (REQ-989). Siehe Konfiguration für Details zur Auswahl.
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 im Materialisierungsspeicher 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 Übrige | DuckDB, PostgreSQL, ClickHouse, Databricks, Snowflake, BigQuery, Fabric, Synapse |
SELF_ONLY |
Erreicht nur den eigenen Speicher; jede andere Quelle landet ein | 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-Anmeldedaten |
pg |
PostgreSQL | Nein | FDW / pg_duckdb | PostgreSQL-Anmeldedaten |
duckdb |
DuckDB | Nein | Extension-natives ATTACH | Keine (in-process) |
clickhouse / clickhouse-server |
ClickHouse | Ja (Shards) | S3-/IcebergS3-/DeltaLake-Table-Engines (REQ-986) | ClickHouse-Anmeldedaten |
snowflake |
Snowflake | Ja | External Stage + External Table (REQ-988) | PROVISA_ENGINE_URL |
databricks |
Databricks SQL | Ja | Unity-Catalog-External-Tables via REST (REQ-987) | Bearer-Token (http_path in federation_hints) |
bigquery |
BigQuery | Ja (Dremel) | BigQuery External/BigLake-Tables | GOOGLE_APPLICATION_CREDENTIALS Service-Account-Schlüssel |
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 | Keine (nur Landung) | Pro-Dialekt-Anmeldedaten |
Zero-Config-Standard: DuckDB (REQ-989) [tool-verified: engine.py build_duckdb_engine, _embedded_duckdb_materialize_default]¶
Wenn PROVISA_ENGINE nicht gesetzt ist, verwendet Provisa die vollständig eingebettete In-Process-DuckDB-Engine. Der Materialisierungsspeicher von DuckDB ist eine eingebettete DuckDB-Datei unter $PROVISA_DATA_DIR/materialize.duckdb (standardmäßig ~/.provisa/materialize.duckdb). Keine externe Datenbank oder Dienst wird benötigt.
Da DuckDB nur einen Writer pro Datei erzwingt, schreibt store_connection.py in den eingebetteten Speicher über die eigene Verbindung der Engine — niemals eine zweite unabhängige Verbindung. Dies ist der einzige Fall, in dem sich Engine und Materialisierungsspeicher bewusst ein Datei-Handle teilen. [tool-verified: store_connection.py Modul-Docstring]
Arrow-nativer Lese-Transport (REQ-986, REQ-987, REQ-988) [tool-verified: engine.py build_*_engine capabilities=]¶
ClickHouse, DuckDB, Snowflake, Databricks, BigQuery, Fabric und Synapse werben alle mit 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 beruht das Arrow-Streaming auf dem Zaychik-Proxy; für die Warehouse-Engines speist die eigene Arrow-native API der jeweiligen Engine den Flight-Stream (Cloud Fetch für Databricks, Storage Read API für BigQuery, fetch_arrow_table für DuckDB und Snowflake).
Externe Datenverknüpfungen (ATTACH) [tool-verified: engine.py _warehouse_connectors]¶
Jede Warehouse-Engine kann Cloud-Objekt-/Lake-Daten in place scannen, ohne eine Kopie zu landen. Parquet-, CSV-, Iceberg- und Delta-Lake-Dateien auf S3, GCS oder OneLake hängen direkt an die Engine an, als wären sie native Tabellen. Die Strategie — ATTACH (in place scannen) oder LAND (in den Speicher kopieren) — wird durch den deklarierten Mechanism des Connectors bestimmt; es gibt kein engine-spezifisches Branching im Planer. Ein Mechanism.ATTACH_R-Connector löst einen Zero-Copy-Scan aus; ein Mechanism.DIRECT- oder fehlender Connector löst eine Landung aus. [tool-verified: connector_base.py Mechanism, engine.py _warehouse_connectors]
Attach stellt zur Attach-Zeit automatisch alle Voraussetzungen 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) |
Validierungsprobe zur 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; liefert den OneLake-BULK-Pfad zurück — live-verifiziert beim Lesen von R2 durch 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 Account verfügbar) |
Anmeldedaten für Cloud-Storage reisen in federation_hints der Quelle (siehe Quellen). Jeder Quelltyp, der nicht ATTACHen kann, landet zunächst im Materialisierungsspeicher der Engine.
Columnar-Materialisierungs-Schreibvorgänge (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 Speicherdialekt: binäres COPY (asyncpg copy_records_to_table) für PostgreSQL-Speicher, und ein einzelnes vorbereitetes executemany-Statement für alle anderen relationalen Speicher. Der eingebettete DuckDB-Speicher landet über land_duckdb_native in store_connection.py — ein einziger executemany-Aufruf für den gesamten Batch, niemals eine Pro-Zeile-Schleife.
Große-Ergebnisse-Redirect¶
Ergebnisse, die einen Zeilen-Schwellenwert überschreiten, werden statt inline zurückgegeben an S3-kompatiblen Speicher (MinIO) umgeleitet. (REQ-029)
Redirect-Modi¶
| Modus | Funktionsweise | Daten berühren Provisa? |
|---|---|---|
| CTAS (Parquet, ORC) | Föderations-Engine schreibt direkt nach S3 via CREATE TABLE AS SELECT |
Nein |
| Provisa-Upload (JSON, NDJSON, CSV, Arrow IPC) | Provisa serialisiert und lädt via boto3 hoch | Ja |
Für CTAS-native Formate verarbeitet Provisa die Daten nie selbst — die Föderations-Engine schreibt Dateien direkt nach MinIO/S3. (REQ-138) Dies ist der bevorzugte Pfad für große analytische Exporte.
Redirect-Header¶
| Header | Wirkung |
|---|---|
X-Provisa-Redirect-Format: <mime> |
In diesem Format umleiten (impliziert Erzwingung, sofern kein Schwellenwert gesetzt ist) |
X-Provisa-Redirect-Threshold: N |
Nur umleiten, wenn das Ergebnis N Zeilen überschreitet |
X-Provisa-Redirect: true |
Umleitung mit Standardformat erzwingen |
Diese Header implementieren client-gesteuerte 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¶
| Umgebungsvariable | Standard | Zweck |
|---|---|---|
PROVISA_REDIRECT_ENABLED |
false |
Server-seitige 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 für die vorsignierte 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)
Föderations-Abfrageoptimierung¶
Provisa grundiert den kostenbasierten Optimizer der Föderations-Engine automatisch, sodass quellübergreifende Abfragepläne auf realer Datenverteilung basieren, nicht auf fest codierten Standardwerten.
Automatische Statistiken (ANALYZE)¶
Bei der Quellregistrierung führt Provisa ANALYZE catalog.schema.table für jede veröffentlichte Tabelle aus. (REQ-275) Dabei werden erfasst:
- Zeilenanzahl
- Pro Spalte: Null-Anteil, Anzahl distinkter Werte, Min/Max, Histogramme (connector-abhä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 zu schlechten Join-Plänen bei verzerrten oder hochkardinalen Daten führt. Mit Statistiken sind die Schätzungen für die meisten Workloads präzise genug, um korrekte Broadcast- versus partitionierte Join-Entscheidungen zu treffen.
Abdeckung: Die Statistik-Unterstützung variiert je Connector. PostgreSQL, MySQL, Hive, Iceberg und Delta Lake unterstützen ANALYZE vollständig. Die MongoDB- und Cassandra-Connectors haben teilweise oder keine Unterstützung. Provisa schluckt ANALYZE-Fehler stillschweigend — die Registrierung wird nie blockiert. (REQ-275)
Selektivitätsgrenzen: Statistiken liefern Pro-Spalte-Schätzungen. Bei korrelierten Prädikaten (WHERE region = 'US' AND city = 'Seattle') geht der Optimizer von Spaltenunabhängigkeit aus, was die Zeilenanzahl unterschätzen kann. Dies 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 erneut ausführen, über die Admin-API: (REQ-276)
mutation {
refreshSourceStatistics(sourceId: "sales-pg") {
tablesAnalyzed
failures { table message }
}
}
Nützlich, wenn eine Quelle seit der Registrierung erheblich neue Daten erhalten hat.
Materialisierte Sichten¶
MVs optimieren teure Abfragen transparent, indem sie Ergebnisse vorberechnen und cachen.
Beziehungen als MV-Hinweise¶
Eine Beziehungsdeklaration ist nicht nur ein Governance-Artefakt — sie ist auch die strukturelle Beschreibung einer Join-Form. Genau diese Form benötigt der MV-Optimizer: zwei Tabellen, zwei Spalten, ein Join-Typ. Das bedeutet, eine Beziehung kann direkt die Materialisierung steuern.
Für quellübergreifende Beziehungen geschieht dies automatisch beim Start: Jede genehmigte quellübergreifende Beziehung erzeugt eine JoinPattern-MV (auto-mv-<rel_id>). (REQ-158) Keine separate MV-Konfiguration ist erforderlich. Wenn der Compiler diesen Join in einer Abfrage erkennt, ersetzt der Rewriter das vormaterialisierte Ergebnis transparent.
Für quellinterne Beziehungen können Data Stewards explizit über materialize: true opt-in gehen. Quellinterne JOINs sind bereits über direkte Ausführung schnell, daher lohnt sich die Materialisierung nur für sehr heiße Join-Pfade. (REQ-159)
Die praktische Konsequenz: Data Stewards, die eine Beziehung genehmigen, entscheiden implizit auch, ob der Join ein guter Kandidat für 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 exponiert |
| Auto-materialisierte Beziehung | quellübergreifende Beziehung (automatisch) | Erzeugt automatisch eine Join-Pattern-MV; keine Konfiguration erforderlich |
| Steward-materialisierte Beziehung | materialize: true auf quellinterner Beziehung |
Explizites Opt-in für heiße quellinterne Join-Pfade |
Auto-Materialisierung¶
Quellübergreifende JOINs sind die teuersten Abfragen (immer föderiert). Quellübergreifende Beziehungen erzeugen 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 quellübergreifende Beziehungen erzeugen MVs (quellinterne JOINs sind über direkte Ausführung bereits schnell). (REQ-159) Die MV startet im Status STALE und wird von der Hintergrund-Refresh-Schleife aktualisiert, bevor sie vom Query-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 (nachfolgende Läufe) gegen die MV-Zieltabelle über die Föderations-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, Sichten |
api/rest/ |
Automatisch generierte REST-Endpunkte aus registrierten Tabellen |
api/jsonapi/ |
Automatisch generierte JSON:API-Endpunkte mit Pagination und Fehlerbehandlung |
api/data/subscribe.py |
SSE-Subscriptions — LISTEN/NOTIFY, Polling, Debezium CDC |
compiler/ |
GraphQL-/SQL-Parser, semantischer SQL-Generator, RLS, Masking, Sampling, zweistufige Governance (stage2.py) |
cypher/ |
Cypher-→SQL-Übersetzer, Parser, Label-Map (REQ-351), Write-Übersetzer für Cypher-Mutationen |
pgwire/ |
PostgreSQL-Wire-Protokoll-Server; catalog.py fängt pg_catalog/information_schema für rollenspezifische Objektsichtbarkeit 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 |
Apollo-Federation-v2-Subgraph-Unterstützung |
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 Föderations-Engine |
executor/ctas_write.py |
CTAS-basierter Redirect (Föderations-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 |
Unity-Catalog-Credential- + External-Location-Auto-Provisioning |
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 |
OneLake-Shortcut-Auto-Provisioning (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-native Materialisierungsspeicher-Schreibfläche (REQ-989, REQ-990) |
registry/ |
Persisted-Query-Registry, Governance |
security/ |
Sichtbarkeit, Rechte, Spalten-Masking |
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 Mutationen und Events |
scheduler/ |
APScheduler-basiertes Hintergrund-Job-Management — Cron- und Interval-Trigger, die Webhooks, Mutationen oder Kafka-Sink-Publishes auslösen |
apq/ |
Apollo-APQ-Wire-Protokoll — Redis-gestützter Query-Hash-Cache; getrennt vom Result-Caching |
compiler/cursor.py |
Relay-artige Cursor-Pagination — first/after/last/before-Argumente und pageInfo-Generierung für alle List-Queries |
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), die als GraphQL-Enum-Typen statt als String-Skalare exponiert werden |
compiler/hints.py |
Föderations-Performance-Hinweise — abfrageseitige Routing-Direktiven, eingebettet als SQL-Kommentare (/* @provisa route=federated */), die automatisches Routing überschreiben |
compiler/mutation_gen.py |
Mutations-Compiler; Spalten-Presets — server-seitige statische oder Session-Variable-Werte, angewendet beim Insert/Update, nicht im Mutations-Eingabetyp exponiert |
auth/approval_hook.py |
ABAC-Genehmigungs-Hook — pluggable externe Autorisierung, vor der Abfrageausführung aufgerufen; Webhook-, gRPC- und Unix-Socket-Transporte; Pro-Tabelle-/Quelle-/global-Scope; konfigurierbare Fallback-Policy |
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) eingebunden. Sie ist vom Daten-GraphQL-Endpunkt getrennt und erfordert die Superuser- oder Admin-Rolle.
| Fähigkeit | Beschreibung |
|---|---|
| Konfiguration herunterladen/hochladen | Vollständige Provisa-YAML-Konfiguration exportieren oder ersetzen |
| Beziehungs-Editor | Beziehungsdefinitionen erstellen, aktualisieren, löschen |
| KI-FK-Erkennung | Claude-gestützte FK-Kandidatenanalyse auslösen |
| Schema-Introspektion | Veröffentlichte Tabellen, Spalten und Rollen durchsuchen |
| Sichten-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 org-spezifisch: Die Auswahl jeder Organisation legt sich über die Deployment-Konfiguration und wirkt beim nächsten Request — kein Neustart erforderlich. (REQ-1349) [tool-verified: provisa/api/admin/ai_models_router.py:38-39]
Pro-Operation-Modellzuweisungen. Fünf NL-Operationen haben jeweils einen konfigurierbaren Vendor und Modell-String:
| Operation | Was sie steuert |
|---|---|
table_description |
LLM-generierte Tabellenbeschreibungen |
column_description |
LLM-generierte Spaltenbeschreibungen |
relationship_inference |
FK-Kandidaten-Erkennung |
sql_generation |
NL-→-SQL-Generierung |
table_selection |
Auswahl, welche Tabellen in den NL-Prompt aufgenommen werden |
Das Vendor-Feld akzeptiert jeden aisuite-kompatiblen Vendor (anthropic, openai, groq, mistral, cohere und weitere) oder einen lokalen Endpunkt (ollama, lmstudio). Ein leerer Modell-String entfernt den Org-Override und stellt den Deployment-Standard wieder her. [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, pro Rolle angewendete Obergrenze für Anfragen pro Zeitraum. Überschüssige Anfragen liefern 429 mit Retry-After. [tool-verified: provisa-ui/src/components/admin/AiModelsTab.tsx:306-313]
Vektor-Modell-Registry. Eine Liste von Embedding-Modellen (Felder: id, provider, dimensions, optional api_key_env und base_url, enabled-Flag). 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-Schlüssel. Pro-Vendor-LLM-API-Schlüssel werden verschlüsselt über provisa.core.org_secrets gespeichert (siehe unten). Die GET-Antwort meldet nur, ob für jeden Vendor ein Schlüssel gesetzt ist — der Wert wird nie zurückgegeben. Das Senden eines leeren Strings für einen Vendor löscht diesen Schlüssel und stellt für LLM-Aufrufe dieses Vendors die Umgebungsvariable-Anmeldedaten des Deployments wieder her. (REQ-1395, REQ-1398) [tool-verified: provisa/api/admin/ai_models_router.py:76-78, provisa/api/admin/ai_models_router.py:149-165]
Pro-Organisation Verschlüsselte Secrets¶
provisa/core/org_secrets.py speichert Anmeldedaten, die niemals als Klartext in der Datenbank erscheinen dürfen. Derzeit beschränkt auf LLM-Vendor-API-Schlüssel ({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, weil sie Konfiguration über einen einfachen API-Schlüssel hinaus benötigen (Projekt-IDs, IAM-Rollen, Region). Vendors mit lokalem Endpunkt (ollama, lmstudio) haben keinen Schlüssel 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 konstruieren) 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 exponiert. (REQ-256, REQ-257)
| Schnittstelle | Mount-Pfad | Spezifikation |
|---|---|---|
| REST | /rest/<table-id> |
Einfaches GET/POST mit Query-Parametern |
| JSON:API | /jsonapi/<table-id> |
jsonapi.org-konform — Pagination, Beziehungen, Fehlerobjekte |
Diese Endpunkte wenden dieselbe Sicherheits-Pipeline (RLS, Masking, Rollenprüfungen) an wie der GraphQL-Endpunkt. (REQ-002, REQ-038)
Subscriptions¶
SSE-Subscriptions werden unter GET /data/subscribe/{table} bedient. Drei Zustellmodi: (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 einem Debezium-Connector | Hochfrequente Change-Streams |
Der Client erhält text/event-stream mit einem JSON-Event pro geänderter Zeile oder Diff.
Event- & Webhook-System¶
Datenbankmutationen (INSERT/UPDATE/DELETE) können ausgehende Events über die Module events/ und webhooks/ 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 anhand von Tabelle, Operationstyp und optionalem Zeilenfilter abgeglichen. Webhook-Payloads enthalten den Operationstyp, die geänderte Zeile und den Rollenkontext.
Hintergrunddienste¶
Vier Hintergrundschleifen starten während des App-Lifespans (api/app.py):
| Dienst | Intervall | Zweck |
|---|---|---|
| MV-Refresh-Schleife | 30 s | Prüft get_due_for_refresh(), führt CTAS oder DELETE+INSERT auf veralteten MVs 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 Sub-Millisekunden-Zugriff |
| API-Source-Poller | Pro-Quelle-Intervall | Ruft entfernte REST-/GraphQL-/gRPC-Quellen erneut ab und cacht sie |
(REQ-160, REQ-238, REQ-239, REQ-236)
Hot-/Warm-Tabellen-Caching-Ebenen¶
| Ebene | Speicherung | Beförderungskriterium | Zugriffslatenz |
|---|---|---|---|
| Hot | In-Process-Speicher | Zeilenanzahl < Schwellenwert, oder ist ein Beziehungsziel | <1 ms |
| Warm | Iceberg auf lokaler SSD | Abfragehäufigkeit-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 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 exponiert Provisa als Apollo-Federation-v2-Subgraph. (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 subgraph-übergreifenden Beziehungen. Provisa beantwortet _entities- und _service-Abfragen, die vom Federation-Gateway benötigt werden. (REQ-259)
Cursor-basierte Pagination¶
Alle List-Queries unterstützen Relay-artige Cursor-Pagination ü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 fügt die passenden WHERE-/LIMIT-Klauseln ein. Jede List-Query liefert ein pageInfo-Objekt:
| Feld | Typ | Beschreibung |
|---|---|---|
hasNextPage |
Boolean | True, wenn nach dieser Seite weitere Ergebnisse existieren |
hasPreviousPage |
Boolean | True, wenn vor dieser Seite Ergebnisse existieren |
startCursor |
String | Cursor des ersten Knotens auf dieser Seite |
endCursor |
String | Cursor des letzten Knotens auf dieser Seite |
Aggregat-Abfragen¶
Jede registrierte Tabelle erhält ein automatisch generiertes {table}_aggregate-Root-Feld (compiler/aggregate_gen.py). (REQ-196) Der Aggregat-Typ exponiert count, sum, avg, min, max pro numerischer Spalte sowie nodes für gefilterten Zeilenzugriff mit vollständiger Feldauswahl (gleiches RLS/Masking wie die Basisabfrage). (REQ-196, REQ-198) Aggregat-Abfragen sind für Aggregat-MV-Routing berechtigt — siehe mv/aggregate_catalog.py. (REQ-198)
Automatisch persistierte Abfragen (APQ)¶
apq/cache.py implementiert das Apollo-APQ-Wire-Protokoll. (REQ-288) Wenn ein Client nur einen Query-Hash sendet (extensions.persistedQuery), schlägt Provisa ihn in Redis nach. (REQ-289) Bei einem Miss liefert es einen PersistedQueryNotFound-Fehler zurück; der Client wiederholt mit dem vollständigen Query-Body, den Provisa speichert. (REQ-288) Dies ist getrennt vom Result-Caching (cache/).
Vererbte Rollen¶
Rollen in core/models.py können auf eine parent_role_id verweisen. (REQ-215) flatten_roles() löst die Vererbungskette rekursiv auf und mischt RLS-WHERE-Klauseln (UND-verknüpft), Spaltensichtbarkeit (Vereinigung, restriktivste gewinnt) und Masking-Policies (Kind überschreibt Elternteil pro Spalte). Dies vermeidet die Duplizierung von Berechtigungssätzen über ähnliche Rollen hinweg (z. B. analyst, die von reader erbt). (REQ-215)
ABAC-Genehmigungs-Hook¶
auth/approval_hook.py ist ein pluggable Autorisierungs-Hook, der vor der Abfrageausführung aufgerufen wird, nach RLS und Masking. (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-Policy | allow oder deny, wenn der Hook-Endpunkt unerreichbar 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 befördert — ihre Werte werden zu Enum-Mitgliedern statt zu String-Skalaren.
Geplante Trigger¶
scheduler/jobs.py verwendet APScheduler, um als Cron- oder Interval-Trigger definierte Hintergrundjobs auszuführen. (REQ-216) Jeder Job kann an eine Webhook-URL POSTen, eine Mutation gegen den Daten-Endpunkt ausführen, oder Abfrageergebnisse an ein Kafka-Topic publizieren. Trigger werden über die Admin-API (scheduledTrigger-Mutationen) oder den Schlüssel scheduled_triggers in der YAML-Konfiguration konfiguriert. (REQ-216)
Föderations-Performance-Hinweise¶
compiler/hints.py parst Steward-Hinweise, die in Abfragen als Kommentare mit Provisas Kommentarsyntax eingebettet sind. (REQ-279) Das Hinweisformat variiert je nach Abfragesprache:
| Hinweis | Wirkung |
|---|---|
route=federated |
Erzwingt Föderation über die Föderations-Engine, umgeht direktes Treiber-Routing |
route=direct |
Erzwingt direkte Treiberausführung |
Spalten-Presets bei Mutationen¶
compiler/mutation_gen.py unterstützt Pro-Spalte-server-seitige Presets, angewendet bei INSERT oder UPDATE. (REQ-214) Presets werden nicht im generierten GraphQL-Mutations-Eingabetyp aufgeführt — sie werden vom Compiler transparent eingefügt. 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 rollenspezifische Schema als navigierbares Entity-Relationship-Diagramm — Tabellen als Knoten, Beziehungen als Kanten. Das angezeigte Schema ist immer auf die aktuell gewählte Rolle gefiltert.
Sicherheits-Durchsetzungsreihenfolge¶
Es gibt kein Capability-Gate für 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 Governance ausgeführt wird. (REQ-267)
- Objektsichtbarkeit: Pro-Rolle-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, es sei denn, die Rolle besitzt
ignore_relationships— unter den vorinstallierten Systemrollen nurmodeler(REQ-001, REQ-1297). Im High-Security-Modus wird die Capability ignoriert, und keine Traversierung entkommt dem Katalog (REQ-693) - RLS: Pro-Tabelle-pro-Rolle-WHERE-Klausel-Injektion (REQ-040, REQ-041, REQ-263)
- Spalten-Masking: Pro-Spalte-pro-Rolle-Datentransformation (REQ-263)
- Zeilenobergrenze (LIMIT): Zeilenanzahl-Obergrenze für Rollen ohne
full_results; zufälliges statistisches Sampling ist eine separate Benutzerabfrage-Funktion (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 schlanke Kompilierungs- und Routing-Schicht — sie fügt der Abfragelatenz einstellige Millisekunden hinzu. Pfade jedoch, auf denen Provisa Ergebnisdaten serialisiert, sind durch den Prozessspeicher begrenzt. Zwei Pfade sind wirklich unbegrenzt:
| Pfad | Speichergrenze? | Geeignet für |
|---|---|---|
| JSON inline (HTTP) | Ja | Kleine bis mittlere Ergebnisse |
| Arrow-Flight-Streaming (gRPC :8815) | Nein | Unbegrenzt — Streaming via 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 — Föderations-Engine schreibt nach S3 |
Schwellenwert-Probing¶
Für schwellenwertbasiertes Redirect fügt Provisa LIMIT threshold + 1 als Probe in die Abfrage ein. (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 Probe verworfen und die vollständige Abfrage erneut via CTAS oder Provisa-Upload 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 Datentools — Batches fließen durch Provisa, ohne materialisiert zu werden (REQ-145)
- Parquet-/ORC-Redirect für dateibasierte Exporte — die Föderations-Engine schreibt direkt nach S3, Provisa liefert eine vorsignierte 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 | Abfrage-Föderations-Engine — Trino für den eingebetteten Stack; Snowflake/Databricks/BigQuery/Fabric/Synapse/DuckDB für Warehouse-Ziele |
| Zaychik | provisa-zaychik (aus 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 |