Published

🏛️ Warehouse — Reorganización a modelo Unity-Catalog (Visión + Approach)

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

🏛️ Warehouse — Reorganización a modelo Unity-Catalog (Visión + Approach)

Documento vivo. Se irá modificando conforme madure el concepto. Fija el norte y el approach de fases; el detalle de cada fase se refina cuando se aborde.

La tesis: separar la app en dos planosProject & Files (autoría) y Warehouse (datos + gobernanza estilo Unity Catalog) — y darle al Warehouse una jerarquía real (Catalog › Schema › Dataset/Volume) en vez de re-proyectar el árbol de carpetas de project_files.

Complementa a dataspace-router.md (la facade de acceso — capa 4) y port-migration.md (el tracker de puertos). Este doc es la capa de organización y gobernanza que se monta ENCIMA del tridente ya construido. Grounded en el substrato verificado (2026-07-04).


1. La visión en una frase

El dato vive en el Warehouse; el documento vive en Project & Files.

Todo lo tabular (datasets) y no-estructurado gobernado (media → volumes), más credentials, connections, permisos, linaje y gobernanza, se organiza en un catálogo real tipo Unity Catalog. Las definiciones y documentos que autoran las building blocks (notebooks, notepads, dashboards, recetas de pipeline/workflow, ontología, definición de modelo) se quedan en Project & Files.


2. Los dos planos

PlanoQué contieneSharing / gobernanza
Project & FilesautoríaNotebooks (.ipynb), notepads, dashboards, definiciones de pipeline/workflow, object-types/ontología, definición de modelofile_access_grants (20261121), page_shares (20260616), workspace groups
Warehousedatos + gobernanzaDataset (tabular), Volume (media), Credentials, Connections, Permisos, Lineage, Governance, SharesModelo GRANT (a construir), cascada Catalog→Schema→Dataset

El criterio del split: DATO vs. DOCUMENTO — no "quién lo produjo"

Trampa a evitar: "todos los outputs de las building blocks van a Files" es incorrecto. El output tabular de un pipeline / notebook / modelo es un dato → va al Warehouse como Dataset. Su receta/definición → Files.

Casi toda building block tiene dos artefactos: la definición (→ Files) y el output (→ Warehouse). Un pipeline = receta (file) + dataset resultante (tabla). No es "la app X vive aquí"; es "la receta a Files, el dato al Warehouse".


3. La jerarquía (DECIDIDA)

Workspace            = Metastore   (frontera de tenant — ya existe)
  └─ Catalog         CREABLE, varios por workspace (equipo / dominio / entorno)   ◄─ modelo Multi-Catalog
       └─ Schema     ← un Project actual mapea aquí   (= namespace Iceberg)
            ├─ Dataset   ← tabla / datos (reutiliza el vocabulario actual)
            └─ Volume    ← media sets / items de media (no-estructurado gobernado)

  Governance / Credentials / Connections / Permisos / Lineage  ─ anclados a nivel de Catalog (cascada ↓)
  Shares received (EDC / Delta-sharing-like)                    ─ hermano del árbol

Por qué "Catalog" y no un nombre inventado: no es un préstamo de Databricks — es la verdad técnica del substrato. Lakekeeper ES un Iceberg REST catalog, y el modelo Iceberg ya es Catalog → Namespace → Table. Nombrar el contenedor top "Catalog" le pone a la capa de producto el nombre que la capa de almacenamiento ya tiene. Mapea 1:1:

Nivel CarbonDatabricks UCApache IcebergHoy en Carbon
WorkspaceMetastore(account)workspaces (existe)
CatalogCatalogCatalog (Lakekeeper)○ nuevo (hoy 1 namespace fijo datasets)
SchemaSchemaNamespace○ nuevo (hoy = Project/carpeta)
DatasetTableTable✅ storage listo (tridente Iceberg)
VolumeVolume○ nuevo (hoy media_set en project_files)

4. Estado actual — la "chapuza" (grounded)

Hoy un dataset ES un file, y el "catálogo" del Warehouse es el árbol de carpetas re-proyectado:

  • Derivación del árbol: app/api/dataspace/catalog/route.ts:44GROUP BY project_id. Los "proyectos" y "datasets" del Warehouse son literalmente la estructura de project_files, agrupada por la FK datasets.project_id. La posición la dicta la carpeta, no un schema.
  • Namespace: hardcodeado a 'datasets' (WarehouseTab.tsx:210 default; checksum-client.ts:49; contrato en consumption-contract.ts:154). No hay jerarquía de namespace.
  • Landing de dataset synceado: el usuario elige un output folder en Data Connection (dataset-writer.ts:746 lee output_folder de la metadata de credencial), y el ingest crea dos filas atómicas (app/api/datasets/ingest/route.ts:115-150): un project_files type='dataset' bajo esa carpeta + una fila datasets colgada por file_id.
  • Media sets: patrón idéntico (lib/projects/fileBackedDatasetUpload.ts:121-172): project_files type='media_set' + fila datasets service='media_set' + los ficheros como manifest en dataset_rows.

Veredicto: un dataset = artefacto project_files en una carpeta + fila de metadata. Su lugar en el catálogo es folder-driven, no schema-driven. Eso es lo contrario del modelo UC de las capturas de Databricks (Catalog → Schema → Table/Volume, con Credentials/Connections/Permissions de primera clase).


5. Gap vs. Unity Catalog (~29% del modelo hoy)

Primitivo UCEstadoDónde / nota
Metastore○ ausenteworkspaces es app-level, no storage-level
Catalog○ ausentetodo plano bajo datasets
Schema (first-class)○ ausentehoy = Project/carpeta
Namespace Iceberg◐ parcialfijo a datasets, no jerárquico
Table (governada)✅ existetridente: iceberg_sync_log, identity cols, read/write routers
View / Function○ ausentesin registro de objetos
Model (ML)✅ existeml_model_registry (20260607) — subsistema aparte
Volume○ ausentestorage.buckets no catalogado
External Location○ ausentepaths en env vars, sin registro
Storage Credentials○ ausentesecretos inline / config
Connections◐ parcialintegrations por-workspace, sin registro central
GRANT (objeto-nivel)○ ausentehoy es RLS (20260416, 20260417): user_id + workspace_id, acoplado a tenancy
File Access Grants✅ existe20261121 — solo project_files (Files plane), no datos
Governance metadata (tags/clasif./lineage)◐ parcialprovenance estampada por el tridente; falta tags/clasificación/lineage
Shares (Delta Sharing)○ ausenteexiste EDC (dataspace_assets/dataspace_transfers, 20261230), orthogonal a UC

Lo bueno: el plano de datos ya está catalog-ready — el tridente le dio a cada dataset identidad Iceberg real (namespace + snapshot) independiente del file. Lo que falta es el control-plane de organización y gobernanza encima, que vive en PG (PG = plano de control; R2+Lakekeeper = plano de datos).


6. Modelo de datos objetivo (BORRADOR — se refina por fase)

Tablas nuevas en PG (control plane). Nombres/columnas provisionales.

-- Contenedor top, creable, múltiple por workspace
catalogs (
  id, workspace_id, name, comment, owner_id,
  created_at, updated_at,
  UNIQUE (workspace_id, name)
)

-- Agrupación; un Project actual migra a un Schema
schemas (
  id, catalog_id → catalogs.id, name, comment, owner_id,
  legacy_project_id,            -- puente al project_files actual
  created_at, updated_at,
  UNIQUE (catalog_id, name)
)

-- El dataset gana su posición de catálogo (bridge: nullable al principio)
datasets.schema_id → schemas.id          -- backfill desde project_id
-- project_files/file_id se mantiene como PUNTERO LEGACY detrás de flag

-- Media governado
volumes (
  id, schema_id → schemas.id, name, kind ('media'|'file'),
  storage_ref, comment, created_at,
  UNIQUE (schema_id, name)
)

-- Registro central (sustituye secretos dispersos / env vars)
storage_credentials (id, workspace_id, name, provider, secret_ref, ...)
external_locations  (id, credential_id → storage_credentials.id, url_prefix, ...)
connections         (id, workspace_id, name, type, credential_id, config, ...)

-- El modelo GRANT (fase tardía — reemplaza el enforcement RLS-tenancy)
grants (id, principal_type, principal_id, object_type, object_id, privilege, ...)

Namespace Iceberg objetivo: de datasets.ds_{id} (fijo) → {catalog}.{schema}.ds_{id} (Lakekeeper soporta namespaces multi-nivel). El namespace pasa a ser derivado de la jerarquía, no una constante.


7. El linchpin: desacoplar el Dataset del project_files

Es LA pieza. Hoy el dato es un file; el Warehouse debe volverse autoritativo: el catálogo (catalog.schema.dataset) posiciona el dato, y la carpeta deja de ser su identidad.

Estrategia = seam / bridge detrás de flags (mismo método que el tridente, source-of-truth.md):

  1. Se crean catalogs/schemas y se siembra 1 catalog default (main) por workspace.
  2. Cada project existente se migra a un schema dentro de main (schemas.legacy_project_id).
  3. datasets.schema_id se backfillea desde project_id.
  4. El árbol del Warehouse gana el nivel Catalog, leyendo del nuevo modelo (derivado del legacy al principio).
  5. El project_files queda como puntero legacy detrás de flag; se drena cuando el catálogo es fuente única.
  6. El picker "output folder" de Data Connection se sustituye por "catalog.schema".

Multi-Catalog no implica big-bang: se empieza con el main sembrado y se crean más catalogs a futuro.


8. Approach por fases (BORRADOR)

FaseObjetivoRompe / riesgo
W0 — Modelo + siembra(migración escrita)Tablas catalogs/schemas; seed main por workspace; projects→schemas; backfill datasets.schema_id. Inert (árbol sigue leyendo legacy). → supabase/migrations/20261231_warehouse_catalog_hierarchy.sql (aditiva, idempotente, type-agnostic, sin aplicar).Bajo (aditivo)
W1 — Árbol re-enraizado + Catalog creable(implementada) 📄 approach/api/…/catalog devuelve catalogs→schemas→datasets/volumes del nuevo modelo; UI "Create catalog"/"Create schema"; invariantes por trigger (20261232). Re-enraizado LÓGICO; el namespace Iceberg FÍSICO (datasets.*catalog.schema.*) se difiere a W1.5. (pendiente: aplicar 20261232 + verificar)Medio (UI)
W1.5 — Namespace físico Iceberg 📄 approach · W1.5-a ✅ · W1.5-b ✅ · W1.5-c 📐Born-in-place (write nativo nace en catalog.schema) + barrido metadata-only del datasets.* existente. Persiste datasets.iceberg_namespace (cutover por-dataset).Medio (metadata-only; el riesgo es coordinación, no datos)
W2 — VolumesMedia sets → volumes gobernados; separar del data-path de project_files.Medio
W3 — Credentials & ConnectionsRegistro central; Data Connection lee/escribe de ahí; picker catalog.schema.Medio
W4 — Governance metadataTags, clasificación, lineage (aprovechar la provenance del tridente).Bajo-medio
W5 — RLS → GRANT ⚠️Introducir grants; migrar enforcement de RLS-tenancy a principal→objeto→rol con herencia catalog→schema→dataset.Alto — toca auth. El hueso. Aislada a propósito.
W6 — SharesFormalizar "Shares received" (EDC) hacia share/recipient (Delta-sharing-like).Medio

Cada fase: seam-first, detrás de flags, project_files como puntero legacy hasta el drenaje.

Coordinación: hay una migración del tridente en curso (pipeline.output nativo, fase 2d — ver port-migration.md). W0-W1 son ortogonales (organización, no I/O), pero el cambio de namespace de W1 debe coordinarse con el write-path nativo.


9. Decisiones abiertas (a madurar)

  • Frontera exacta Workspace = Metastore: ¿un metastore por org o por workspace? ¿catalogs compartibles entre workspaces?
  • RLS → GRANT: cómo migrar el enforcement sin downtime; convivencia temporal RLS+GRANT.
  • Views / Functions como objetos de catálogo (UC los tiene; Carbon no) — ¿en scope?
  • Delta Sharing propio vs. seguir en EDC para el share cross-org.
  • Credential vending para motores externos (del dataspace-router.md) — ¿converge aquí?
  • Zero-copy EDC (Fase 5 del track dataspace) vs. copia actual.
  • Modelos: en UC son objeto de catálogo (catalog.schema.model). ¿Se unifica ml_model_registry bajo el Warehouse o se queda en Files como "definición"?

10. Registro de decisiones

FechaDecisión
2026-07-04Rename "Data Space" → "Warehouse" (UI + identificadores: WarehouseTab, view-type/app-id 'warehouse', icono cloud-server). Capa B (rutas /api/dataspace/*, lib/dataspaces, edc-service, DB, término Gaia-X/EDC) intacta.
2026-07-04Split por dato vs. documento (no por productor).
2026-07-04Unidad contenedora top = Catalog (1:1 con Lakekeeper/Iceberg).
2026-07-04Mapeo Multi-Catalog (fiel a Databricks): Workspace=metastore, Catalogs creables, Project→Schema.
2026-07-04Naming hoja: Dataset (tabular, reutiliza vocabulario actual) · Volume (media). Nivel medio = Schema.
2026-07-04W0 APLICADA en Supabase (migración 20261231): catalogs/schemas/datasets.schema_id + siembra.
2026-07-04Approach W1 redactado (warehouse-w1-approach.md): re-enraizado lógico (invariantes por trigger); el namespace Iceberg físico se difiere a W1.5.
2026-07-04W1 implementada: migración 20261232 (triggers T1/T2/T3), read-path v2 (app/api/dataspace/catalog), CRUD (catalogs/schemas), WarehouseTab árbol 3 niveles + create inline. Pendiente: aplicar 20261232 + verificar en preview.
2026-07-04W0+W1 aplicadas en Supabase (20261231+20261232). W1.5 recon + approach (warehouse-w1_5-approach.md): move metadata-only; estrategia born-in-place + barrido; persistir datasets.iceberg_namespace; namespace 2-nivel. W1.5-a escrita (20261233 slugs + columna).