Linaje a nivel de columna¶
Provisa sigue el linaje de datos a nivel de columna de forma estática. Lo calcula a partir de las definiciones SQL y los contratos de los comandos, así que no hace falta ejecutar nada. Tiene dos vistas: un DAG por sentencia y un grafo de procedencia de toda la federación que abarca cada vista y vista materializada (MV) registrada.
El explorador de linaje¶
Abra Lineage en la interfaz (/lineage). Pegue una sentencia SQL y haga clic en Build
statement graph para ver su DAG a nivel de columna. Haga clic en Federation graph para cargar
el grafo de procedencia sobre cada MV del registro.
DAG a nivel de sentencia¶
Cada columna de salida nombrada en su SQL se convierte en un nodo. El constructor la rastrea hacia atrás a través de cada CTE, subconsulta, join y llamada de comando en línea hasta sus columnas de origen, construyendo un grafo dirigido desde las entradas de origen hasta las salidas finales.
Ejemplo resuelto¶
SELECT o.id, e.embedding, upper(e.geo) AS geo_u
FROM orders o
JOIN enrich_grpc_set('main.public.orders') e ON o.id = e.id
Esta sentencia produce tres columnas de salida. El grafo para geo_u se ve así:
orders.geo ──[enrich_grpc_set(...)]──► e.geo ──[UPPER]──► geo_u
orders.id ─╮ (taint closure)
orders.region ─╯
orders.id,orders.regionyorders.geoson nodos origen (el contrato de entrada estrecho deenrich_grpc_setdeclaraidyregion; el cierre de contaminación (taint closure) completo conecta todas las entradas declaradas con todas las salidas).e.embeddingye.geoson nodos command en el límite deenrich_grpc_set.geo_ues un nodo derived producido por la función SQLUPPER.
El límite del comando no es opaco. Dado que enrich_grpc_set declara sus columnas de entrada
(id, region) y columnas de salida (id, embedding, geo), el motor de linaje empalma el
cierre de contaminación de forma continua desde las columnas declaradas de la relación de origen
hasta cada salida.
Tipos de nodo y señales visuales¶
| Tipo de nodo | Color | Significado |
|---|---|---|
source |
Verde | Una columna de una tabla base |
derived |
Azul | Producida por una expresión SQL (función, operador, CTE) |
command |
Púrpura | Una columna de salida de un comando registrado |
Anillos adicionales en un nodo:
- Anillo naranja — una columna de salida final de la sentencia.
- Borde doble — la relación de la columna es una vista materializada (instantánea de MV/CTAS).
- Anillo rojo — miembro de un ciclo clasificado como error.
- Anillo amarillo — miembro de un ciclo clasificado como bucle de retroalimentación.
Transformaciones nombradas en las aristas¶
Cada arista lleva la expresión SQL cruda que produce la columna de destino, además de una lista de
operaciones nombradas: funciones SQL (sql_function), operadores aritméticos/lógicos
(operator), comandos registrados (command), referencias de columna simples (identity) y
literales (constant).
Una arista proveniente de una llamada de comando se representa como una línea púrpura discontinua en la interfaz.
Grafo a escala de la federación¶
El grafo de la federación fusiona el linaje por sentencia de cada MV registrada en un único grafo de procedencia. La identidad de un nodo es relation.column, así que la columna de salida de una vista y la referencia de entrada de otra vista a esa columna se funden en un nodo. El resultado es un único DAG desde las columnas de las fuentes base hasta cada conjunto de datos derivado de la plataforma.
Use focus, direction y depth para acotar la vista a escala de federación sin recalcular el
grafo.
Ciclos¶
El motor de linaje detecta cada ciclo dirigido y lo clasifica. Un ciclo se describe y nunca se rechaza.
| Clasificación | Color de borde | Significado |
|---|---|---|
feedback |
Amarillo | El ciclo atraviesa un nodo materializado — un bucle de retroalimentación legítimo y desfasado en el tiempo. La instantánea de la MV es el límite de versión que lo hace bien definido. |
error |
Rojo | No hay límite de materialización en el bucle — una definición circular sin un orden de evaluación estable. Probablemente un error de diseño. |
Un ciclo feedback es un patrón válido. Una MV de enriquecimiento que devuelve una columna derivada a su propia relación de origen funciona mientras un nodo del bucle esté materializado, porque la instantánea separa las dos mitades en el tiempo. Un ciclo error necesita el criterio de un operador. Suele significar que dos vistas se referencian entre sí sin una instantánea en medio.
API¶
Ambos endpoints son estáticos. Leen definiciones y contratos, nunca datos.
POST /admin/lineage/graph¶
Devuelve el DAG a nivel de columna para una única sentencia SQL.
POST /admin/lineage/graph
Content-Type: application/json
{
"sql": "SELECT o.id, e.embedding FROM orders o JOIN enrich_grpc_set('main.public.orders') e ON o.id = e.id",
"dialect": "postgres"
}
Forma de la respuesta:
{
"nodes": [
{"id": "orders.id", "column": "id", "relation": "orders", "kind": "source", "materialized": false}
],
"edges": [
{
"source": "orders.id",
"target": "e.id",
"transform": "enrich_grpc_set(...)",
"ops": [{"name": "enrich_grpc_set", "kind": "command"}]
}
],
"outputs": ["id", "embedding"]
}
Devuelve HTTP 422 cuando el SQL no se puede analizar (parsear).
GET /admin/lineage/federation¶
Devuelve el grafo de procedencia combinado sobre todas las MV del registro.
GET /admin/lineage/federation
GET /admin/lineage/federation?focus=orders.id&direction=downstream&depth=3
Parámetros de consulta:
| Parámetro | Valores | Predeterminado | Efecto |
|---|---|---|---|
focus |
Un id de nodo | — | Acota la respuesta al subgrafo alrededor de este nodo |
direction |
upstream | downstream | both |
both |
Dirección de recorrido a partir de focus |
depth |
entero | sin límite | Distancia máxima de saltos desde focus |
La respuesta tiene la misma forma que el grafo de sentencia, con un campo cycles añadido
:
{
"nodes": [...],
"edges": [...],
"outputs": [...],
"cycles": [
{
"nodes": ["orders.region", "enriched_orders.region"],
"has_materialization_boundary": true,
"classification": "feedback"
}
]
}
De qué se construyó cada refresco¶
Cada refresco de una vista materializada registra de qué se construyó: la versión de la definición de la vista, la versión de sus entradas y cómo se estableció esa versión, y las columnas de origen y la expresión de cada columna de salida. La vista operativa "ops"."mv_refresh_lineage" devuelve una fila por refresco:
| Columna | Contiene |
|---|---|
mv_id |
La vista materializada refrescada |
target_table |
La tabla que escribió el refresco |
definition_version |
La versión de la definición de la vista |
input_version, input_version_kind |
La versión de sus entradas: un snapshot de Iceberg, una marca de agua, un token de frescura o la hora del refresco |
trace_id |
La traza del refresco en "ops"."traces" |
refreshed_at |
Cuándo se ejecutó |
column_derivations |
Cada columna de salida con sus columnas de origen y su expresión, como JSON |
SELECT refreshed_at, definition_version, input_version, input_version_kind
FROM "ops"."mv_refresh_lineage"
WHERE mv_id = 'view-view_exposure'
ORDER BY refreshed_at
La vista lee los registros de refresco de "ops"."traces" y responde en todo motor que los conserve. En Trino son las tablas Iceberg del catálogo otel. En DuckDB el servidor escribe sus propios registros de traza como archivos Parquet en su carpeta de telemetría (PROVISA_TELEMETRY_DIR, ~/.provisa/telemetry por defecto), un archivo por lote exportado en una carpeta por día. La compactación programada de telemetría (otel.compact_cron) une los archivos pequeños de cada día en archivos de hasta 64 MB. Cada organización lee solo sus propios refrescos, y los registros se conservan indefinidamente. Los spans solo se registran mientras el SDK de OpenTelemetry está activado (OTEL_SDK_DISABLED sin definir o false).
Una vista tal como estaba y tal como se registró¶
Una vista declarada bitemporal conserva cada versión que ha tenido: cada refresco añade, y ninguna versión se reescribe, en cualquier almacén. Una lectura simple responde con el estado actual. Dos cabeceras de la petición leen su pasado:
X-Provisa-As-Oflee la vista a una fecha, o sobre un rango, según su reloj: el tiempo de cambio propio de la fuente cuando la vista declarabitemporal_source_time, el tiempo de carga de Provisa cuando no.X-Provisa-As-Recordedla lee tal como Provisa la tenía registrada en un momento. Las versiones registradas después quedan fuera, así que una corrección registrada más tarde no cambia la reproducción del pasado.
A 30 de septiembre y registrada el 1 de octubre da la cifra original; registrada el 5 de octubre da la corrección. El registro de auditoría de cada sentencia guarda ambos tiempos (as_of, as_recorded), de modo que un informe se puede reproducir tal como se produjo por primera vez. Vistas con historial explica cómo declarar una vista bitemporal y cada superficie que acepta los dos valores.
Qué rompería un renombrado o una eliminación de columna¶
Una columna lleva dos nombres, y cada uno lo almacena un conjunto distinto de artefactos.
El nombre expuesto es lo que muestran las superficies SQL y GraphQL: table_columns.alias, con
respaldo en el valor predeterminado snake_case cuando no hay alias definido. Las vistas, vistas materializadas, expresiones de
métricas, predicados de RLS, contratos de DQ, granularidades de vistas de métricas y claves de fila
de MV se escriben todos contra ese nombre, así que renombrar un alias los rompe con la misma
certeza que eliminar la columna.
El nombre físico es table_columns.column_name, la identidad que sobrevive al reemplazo
completo de columnas del upsert de la tabla. Las relaciones, los enlaces del glosario, las
asignaciones de etiquetas, la columna de marca de agua (watermark) y los ajustes preestablecidos de
columna almacenan este, así que solo se rompen cuando la columna se elimina.
columnDependents reporta ambos casos. Las vistas y MV descendientes provienen de recortar el
grafo de federación en el nombre expuesto de la columna; los artefactos que ese grafo no cubre
provienen de un escaneo directo del registro.
query {
columnDependents(tableId: "42", renamed: ["order_total"], removed: ["legacy_code"]) {
columnName
dependents { kind name detail breaksOn }
}
}
breaksOn es rename para una referencia por nombre expuesto y remove para una por nombre
físico, de modo que quien llama pueda saber a cuál de las dos mitades de la edición está
reaccionando cada artefacto.
Consulte esto antes de guardar. Una columna renombrada se localiza por el nombre expuesto que aún conserva en el registro; una vez que el alias se ha aplicado, el nombre anterior desaparece y la consulta no encuentra nada.
La página de tablas ejecuta la consulta automáticamente cuando una edición pendiente cambia un alias o reduce el conjunto de columnas, y enumera lo que encuentra. El aviso es orientativo. Nombra los artefactos afectados, y el administrador decide. Nunca bloquea el guardado, porque no se puede llegar a todos los consumidores del patrimonio: un panel externo o una aplicación cliente que consulta la columna por su nombre queda fuera del conocimiento del registro. Por el mismo motivo, los análisis sobre texto SQL libre buscan la columna como token identificador sin resolver el ámbito, así que pueden nombrar un artefacto que al final no usa la columna. En un aviso, informar de más es la dirección segura.
Usar el linaje para gobernar contratos de comandos¶
Dado que el cierre de contaminación conecta cada columna de entrada declarada con cada columna de salida declarada, la amplitud de ese cierre depende enteramente de lo que usted declare.
Piense en un comando que recibe una tabla de pedidos completa (id, region, amount, customer_id, discount, notes, ...) y devuelve un embedding. Si el contrato de entrada enumera todas esas columnas, cada columna posterior que use el embedding muestra linaje desde todas ellas. Es exacto, pero no ayuda, porque no puede saber qué importó de verdad.
Declare solo id y text (las columnas que el modelo de embedding realmente lee), y el cono de
linaje se reduce a esas dos columnas de origen. La derivación es a la vez sólida y precisa.
Consulte Commands para conocer la mecánica de declarar un contrato de entrada estrecho.