Levantar la frontera — Index como la única superficie del Warehouse
Entregable de arquitectura (2026-07-29). La frontera entre Warehouse (los hechos: bytes en R2 + los punteros de Lakekeeper que dicen dónde están) e Index (el gobierno: linaje, calidad, metadata, desde la ingesta) ya está cartografiada — warehouse-index.md dice de qué lado cae cada columna. Este documento responde a la pregunta siguiente: cómo se levanta.
La tesis, en una frase: no se levanta moviendo columnas. Se levanta partiendo el camino de control del camino de datos, y poniendo Index en el primero — donde es barato, raro y autoritativo— y fuera del segundo, donde sería un cuello de botella. Lo que hoy son copias que alguien tiene que acordarse de escribir se convierten en proyecciones de un log de eventos autoritativo.
Cruza con: warehouse-index.md (qué es Index) · warehouse-scaffolding-retirement.md (qué sale) · warehouse-as-narrow-waist.md (por qué la cintura no es un motor) · junction.md (la puerta).
1 · Dos hallazgos que cambian el diseño
Antes de proponer nada, dos hechos que reordenan el espacio de soluciones. Uno está en nuestro código; el otro, en el catálogo que ya corremos.
1.1 · El código ya descubrió la respuesta, a medias
Hoy el nombre físico de una tabla se deriva de la jerarquía lógica: {catalog.slug}.{schema.slug}.ds_<uuid>. Esa fórmula está duplicada en dos lenguajes (fqn.ts ≡ writer.py) y ya se rompió una vez (F3: fqn.ts ignoraba LAKEHOUSE_NAMESPACE que 11 sitios respetan ⇒ DuckDB y ml-runner podían apuntar a tablas físicas distintas, en silencio).
Pero mira lo que hace el escritor de verdad. iceberg-native-write.ts:123-140 persiste el namespace físico en datasets.iceberg_namespace al commitear, y junction.ts:183-186 lleva este comentario:
«Resolver PURO — NO derivar de schema_id aquí (rompería tablas existentes); la derivación es born-in-place (write).»
Eso es el diseño correcto, escrito como si fuera un parche. El código ya aprendió que la ubicación física no puede derivarse del nombre lógico —porque entonces renombrar mueve datos— y lo resolvió guardándola. Lo que falta no es inventar nada: es terminar ese movimiento y borrar la derivación que todavía convive con él.
1.2 · Lakekeeper ya nos cuenta lo que pasa
Lakekeeper emite CloudEvents en cada cambio de tabla, y su 0.12.0 añadió un manejador de eventos de auditoría con garantía exactly-once. Corremos 0.13.0.
⚠️ No lo hemos tocado: un barrido del repo por CloudEvent da cero. No lo estamos usando, ni sabemos si está configurado en nuestro despliegue.
Y sin embargo es exactamente la pieza que falta. Hoy ratify existe —lo dice el propio encuadre— porque el control-plane está al lado: es un detector de «¿quién se olvidó?». Si el catálogo cuenta lo que ha pasado, exactly-once, nadie tiene que acordarse de nada. La pregunta «¿quién se olvidó?» deja de poder formularse, que es la definición de haber resuelto el problema en vez de vigilarlo.
2 · El espacio de soluciones, y por qué la respuesta es un híbrido
Hay exactamente tres formas de poner Index «en el camino». Vale la pena descartarlas por escrito, porque las dos puras son tentadoras y las dos están mal.
| Dónde está Index | Qué gana | Por qué NO basta | |
|---|---|---|---|
| A · Delante (Index implementa Iceberg REST y proxea Lakekeeper) | En el camino de toda operación | Puede negar antes de que el motor vea nada; el vending de credenciales es el punto de aplicación | Mete a Index en el camino caliente de las lecturas. Contradice la decisión ya tomada de que el planning es client-side, y convierte cada scan en una dependencia de disponibilidad |
| B · Detrás (Index proyecta el stream de eventos del catálogo) | Fuera del camino | Las copias dejan de ser copias: son proyecciones de un log autoritativo. Cero coste en el camino caliente | La gobernanza llega tarde. No puedes negar una lectura con un evento que llega después de leerla |
| C · Híbrido | Delante del control, detrás de los hechos | Ambas | — |
La respuesta es C, y no es una componenda: es la separación correcta. Son dos caminos con propiedades opuestas y hay que tratarlos como tales.
┌─── CAMINO DE CONTROL ── raro, caro por operación, DEBE ser autoritativo ───┐
│ crear objeto · resolver coordenada → ubicación + credencial · DDL · política │
│ ▼ pasa POR Index │
└───────────────────────────────────────────────────────────────────────────────┘
│ devuelve: ubicación opaca + credencial acotada
▼
┌─── CAMINO DE DATOS ── caliente, masivo, Index NO puede estar aquí ────────────┐
│ el motor lee/escribe Parquet en R2 y commitea el CAS contra Lakekeeper │
└───────────────────────────────────────────────────────────────────────────────┘
│ Lakekeeper emite el evento (exactly-once)
▼
┌─── CAMINO DE HECHOS ── masivo, asíncrono, NADIE tiene que acordarse ──────────┐
│ Index PROYECTA: snapshot, filas, esquema, linaje, verdicto de calidad │
└───────────────────────────────────────────────────────────────────────────────┘
La regla que lo resume: Index decide antes; el catálogo ejecuta y cuenta; Index proyecta después. Ningún escritor reporta nunca.
3 · El movimiento clave: romper el espejo de nombres
Esto es lo que convierte «el namespacing es literalmente Index» de aspiración en mecánica.
Hoy hay dos dueños del namespace. Index tiene catalogs › schemas › datasets; Lakekeeper tiene sus namespaces; y el físico espeja al lógico vía una fórmula. Ese espejo es la duplicación — no es una consecuencia de ella. De él salen tres problemas que hoy se tratan por separado como si no fueran el mismo:
- La fórmula tiene que existir en dos lenguajes y mantenerse a mano (ya se rompió).
- Renombrar un schema movería datos, así que no se puede — de ahí el pin defensivo.
- Dos sistemas afirman ser la autoridad sobre el mismo nombre.
La solución no es sincronizar mejor el espejo. Es romperlo.
El namespace de Lakekeeper deja de significar nada. Un objeto del Warehouse tiene dos identidades y sólo una es humana:
· Coordenada —
catalog.schema.objeto. Legible, renombrable, gobernada. Vive sólo en Index.
· Identidad física — un identificador opaco e inmutable que Index asigna al crear el objeto y guarda. Nadie la deriva; se consulta.
Lo que cae solo cuando se rompe el espejo:
- Renombrar es un
UPDATEen Index. Cero bytes se mueven. Hoy es imposible por construcción. - La fórmula del FQN desaparece, y con ella su test de conformidad cross-language: no hay nada que mantener sincronizado si no hay dos derivaciones.
- Mover un objeto entre schemas pasa a ser gobernanza pura, no una migración.
- Un motor nuevo no necesita conocer la convención de nombres — pide y recibe. Es la misma sustracción que warehouse-scaffolding-retirement.md aplica a
row_count, sobre otra columna del mismo contrato invisible.
Y lo mejor: la migración es casi ninguna. datasets.iceberg_namespace es NULLABLE y ya se pinea al nacer. Los objetos nuevos nacen opacos; los viejos ya llevan su ubicación guardada. No hay big-bang — hay que borrar la derivación, no reescribir el almacenamiento.
4 · Los cinco invariantes
Un diseño se define por lo que prohíbe. Estos son los que hay que poder comprobar en CI:
- Ninguna ubicación física se deriva. Sólo hay un resolvedor, y lee lo guardado. Comprobable: cero apariciones de la fórmula fuera del resolvedor.
- Ningún objeto existe sin coordenada gobernada. Crear es un acto de Index; escribir viene después. Comprobable: no hay
create_tablefuera del chokepoint. - Ningún escritor reporta hechos.
row_count, snapshot, esquema y frescura se proyectan del stream. Comprobable: cero escrituras a las columnas-estadística fuera del proyector. - El CAS sigue siendo de Lakekeeper. Index nunca serializa escrituras: si lo hiciera, sería un segundo punto de linearización y una fuente de split-brain. Es la regla que ya existe («NO un router delante de otro router»), aplicada al catálogo.
- La superficie de Index es una SPEC ABIERTA, nunca una API propietaria. Esto no es purismo: warehouse-as-narrow-waist.md §6.2 ya avisó de que «el peligro de sobre-centralización se traslada del motor al catálogo». Index puede ser el punto único porque habla Iceberg REST; el día que hable sólo lo suyo, es lock-in con otro nombre.
El invariante 5 es la respuesta a la objeción obvia. Sí, esto centraliza. Centralizar el control detrás de una interfaz abierta es exactamente lo que hacen Unity Catalog, Polaris y BigLake. Centralizar detrás de una interfaz propietaria es lo que hacía el data warehouse de los 90.
5 · El plan — strangler fig, cinco fases, cada una reversible
⚙️ El approach técnico del primer tramo (N·-1 → N·2) está en index-n0-n2-approach.md, con los call-sites exactos. Dos correcciones que salieron de ese reconocimiento y que afectan a lo que se lee aquí abajo:
· El espejo está sólo en el NAMESPACE. El nombre de tabla (ds_<uuid>) ya es opaco y se queda — igual que su test de conformidad. N·0/N·1 son bastante más pequeñas de lo que sugiere este documento: el espejo entero es una función de 12 líneas con un solo llamante.
· Hoy dev/prod se aíslan POR namespace (main.testvsmain.default,INFRA.md). Romper el espejo tiene que preservar esa separación a propósito.
Ninguna fase requiere la siguiente. Cada una tiene valor sola y se puede apagar.
N·0 · Un solo resolvedor (coste: bajo · riesgo: ninguno · reversible)
Sustituir las dos derivaciones (fqn.ts, writer.py:native_table_identifier) por un resolvedor que lee la ubicación guardada. Sin cambio de comportamiento: hoy lo guardado y lo derivado coinciden. Es la fase que hace posibles todas las demás, y borra de golpe la clase de bug de F3.
N·1 · Identidad física opaca (coste: bajo · riesgo: bajo)
Los objetos nuevos nacen con identidad opaca asignada por Index. Los existentes se quedan donde están — su ubicación ya está guardada. A partir de aquí, renombrar y mover son operaciones de metadata. datasets.iceberg_namespace ya modela esto; es terminar lo empezado.
⭐ N·2 · La suscripción — en SOMBRA primero (coste: medio · riesgo: bajo · el mejor ratio del plan)
Index se suscribe a los CloudEvents de Lakekeeper y compara lo que el catálogo cuenta contra lo que los escritores reportaron. No cambia nada todavía: mide la deriva.
Es la fase que hay que hacer aunque no se haga ninguna otra, por tres razones:
- Mide si la premisa central es cierta antes de apostar por ella.
- Convierte
ratifyde detector-que-adivina en verificador con fuente. - Cuando se enciende, retira el reporte obligatorio de los 28 escritores de
row_county de la fila deiceberg_sync_logde una vez — que es la mitad del andamio del otro documento.
N·3 · La cara Iceberg REST de Index (coste: alto · riesgo: medio)
Index implementa la spec y proxea a Lakekeeper. Los motores repuntan a Index. El vending de credenciales acotadas pasa a ser el punto de aplicación — y es lo que hace la gobernanza engine-agnostic de verdad: no importa qué motor sea, no recibe llave sin pasar por la política. (Nota: hoy la SECRET de R2 es una llave compartida sin scope. Esta fase es lo que la retira de verdad; DUCK_S3_SCOPE es un parche interino.)
N·4 · Create-first (coste: bajo, DESPUÉS de N·3 · riesgo: bajo)
Ningún objeto puede existir sin que Index lo cree. Se cierra el DDL directo contra Lakekeeper. Es lo que hace verdad «gobernanza desde la ingesta»: hoy un CTAS podía crear una tabla fuera del chokepoint, sin identidad ni layout.
N·5 · Calidad y linaje en el seam (coste: medio)
Con N·2 y N·3 en pie, ambos caen solos y dejan de ser proyectos aparte:
- Linaje: toda resolución-para-escritura declara sus entradas ⇒ la arista se emite en el acto, no se reconstruye después. Eso disuelve los cinco almacenes desconectados de hoy, y es la única forma de tener linaje desde la ingesta sin un scraper de DAGs.
- Calidad: un veredicto es un hecho sobre
(objeto, snapshot, política), y llega por el mismo stream que el commit. Encaja limpio con el tier medallón: el tier es una afirmación; el veredicto es la evidencia. Hoy el tier se afirma y nadie lo comprueba.
6 · Costes y riesgos, sin adornos
- Disponibilidad. N·3 hace de Index una dependencia dura de la resolución. Mitigación real: las ubicaciones de metadata son inmutables, así que la resolución es cacheable agresivamente. Pero hay que diseñar la degradación antes, no descubrirla.
- Dos sistemas de autorización. Lakekeeper trae OpenFGA. Si Index también decide, hay dos. Decisión necesaria (§7·3). Recomendación: Index posee la política (es quien conoce proyectos, tiers y fronteras de dataspace); Lakekeeper aplica en la frontera de almacenamiento como defensa en profundidad. Nunca dos fuentes de la misma regla.
- Implementar una spec cuesta. N·3 es la fase cara. La alternativa —una API propietaria— es más barata y viola el invariante 5.
- CloudEvents no está verificado en NUESTRO despliegue. Es una capacidad documentada del producto que corremos, no una medición. N·2 empieza por comprobarlo.
- Criterio de parada, dicho por adelantado: si la sombra de N·2 muestra que el stream pierde o duplica eventos, la tesis «proyección, no copia» se cae y hay que replegarse al plan W (derivar del snapshot + reconciliar). Sería una respuesta válida, y barata de obtener: por eso N·2 va primero.
7 · Decisiones para el owner
- ¿Se acepta que Index sea la superficie única, hablando Iceberg REST? Es la decisión estructural. El sí implica que Lakekeeper pasa a ser un detalle de implementación detrás de Index, no un peer. Recomendación: sí — es el modelo de Unity Catalog, y el invariante 5 es lo que impide que se vuelva lock-in.
- ¿Se rompe el espejo de nombres? Recomendación: sí, y ya — N·0+N·1 son baratas, no requieren migración de datos, y desbloquean renombrar/mover, que hoy es imposible por construcción.
- ¿Quién autoriza: Index u OpenFGA? La única que no puede quedar abierta, porque dos fuentes para la misma regla es peor que cualquiera de las dos. Recomendación: política en Index, aplicación en ambos.
- ¿Se hace N·2 sola primero? Recomendación: sí. Es la de mejor ratio del plan, mide la premisa central antes de apostar, y su resultado puede invalidar el resto — que es exactamente lo que se le pide a un primer paso.
Referencias externas: Unity Catalog OSS · Open-sourcing Unity Catalog · Iceberg REST Catalog Spec · UC credential vending / acceso externo · Credential vending en catálogos Iceberg · Lakekeeper — concepts · Lakekeeper — autorización OpenFGA · El estado de los catálogos Iceberg, junio 2026.