Published

🧭 REPASO DEL PARADIGMA — de la ingesta al catálogo

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...

🧭 REPASO DEL PARADIGMA — de la ingesta al catálogo

Qué es esto. Una lectura transversal de las ocho piezas de
docs/piezas/, hecha de una sentada, para responder a dos
preguntas que ninguna pieza puede contestar sola:

¿qué se repite? — los patrones de inmadurez que aparecen en piezas distintas
y por tanto no son de ninguna;
¿dónde encaja cambiar de catálogo? — con Polaris sobre la mesa, por encima
de Gravitino.

No es una pieza (no describe un componente) y no es un approach (no propone
un plan). Es un repaso: qué imagen sale de juntar las ocho, y qué decisiones cambia.

Fecha: 2026-08-12. Todo lo que afirma sale de las piezas, y cada pieza lleva su
medición con el comando que la produjo. §4·3 añade una medición propia: la sonda
de Polaris contra nuestro R2 (scripts/warehouse/p1-polaris-r2-gate.ts).


1 · El recorrido, y por dónde NO pasa

  FUENTE (Postgres del cliente)
     │  Node · streaming real (fetch batch → yield)
  COORDENADA en Index          ← el único punto donde la ingesta toca gobernanza ✅
  warehouse-writer (PyIceberg) ──⛔ NO pasa por la cara ──▶  CATALOG-STORE (Lakekeeper)
     │  llave estática RW de todo el bucket                        │  commit = CAS sobre `table_refs`
     ▼                                                             ▼
  DATA-STORAGE (R2)  ◀───────────────────────────────────── el metadata.json es una SALIDA


  SQL Editor ──▶ JUNCTION ──▶ COMPUTE (Spark) ──▶ ICEBERG-FACE ──▶ CATALOG-STORE
                  ✅ la persona          ✅ el motor        ✅ 1 de 4 carriles

La imagen tiene una asimetría que lo explica casi todo: el carril de lectura del producto está gobernado de punta a punta, y el carril por el que entra el 100 % del dato nuevo no pasa por ninguna puerta.


2 · Los seis patrones que se repiten

No son ocho listas de pendientes: son seis formas de fallar que aparecen en piezas distintas. Ordenados por lo que cuestan.

P1 · Declarado y no aplicado

Lo más frecuente, con diferencia. La decisión está tomada, escrita y visible — y no actúa.

DóndeQué
DATA-STORAGEdedicated_storage armado en 7 organizaciones vacías y desarmado en la única con datos
DATA-STORAGEtarget-file-size = 256 MiB sobre un fichero medio de 17,7 KB (~15.000×)
DATA-STORAGEpolítica de retención declarada en 33 de 188 tablas; layout en 83 de 188
CATALOG-STOREidempotency-key-lifetime: PT30M anunciado · idempotency_record vacía
CATALOG-STOREtask_config vacía: todo el ciclo de vida corre con los defaults, sin decidirlo
ICEBERG-FACEendpoints: [] — la cara no declara ninguna capacidad
INDEX-CATALOGCedar decidido y sin activar · OpenFGA desplegado e inerte en Carbon
DATA-STORAGEcompaction y orphan_cleanup existen como tipos de política con esquema… y sin ejecutor

El coste no es que falte la función: es que el documento y el sistema dicen cosas
distintas, y el documento gana en las reuniones.

P2 · Construido y sin disparador

La mitad cara está hecha. Falta la barata.

DóndeQué
⛔⛔ INGESTIONratifyOpenTransactions — escrito, probado, y sólo lo llama su test. Hay 5 txn open esperando
DATA-STORAGEel retention-scheduler es opt-in y arranca en dry-run
CATALOG-STOREtabular_purge declarada y nunca ejecutada
DATA-STORAGEnadie recoge ficheros sin referencia dentro de una tabla viva
CATALOG-STOREnadie recoge los tags carbon-precomp-… del JOP
DATA-STORAGEcheck:storage-perimeter rojo en main y corriendo en CI

«No es una debilidad de diseño: el diseño está resuelto. Es un barrido sin
disparador — la mitad más barata y la que falta.»
(INGESTION)

P3 · Traducido y no registrado

Se toma una decisión por el usuario y no queda rastro de que se tomó.

  • Un tipo desconocido cae a string sin aviso; Decimal → decimal(38,9) pierde escala en silencio (INGESTION).
  • 17 de 26 fuentes están en main.default sin que nadie lo eligiera, y «lo eligió» y «se lo pusimos» son indistinguibles (INGESTION).
  • El accessKeyId de R2 es el del token padre ⇒ en los logs del bucket todos los inquilinos se ven iguales (DATA-STORAGE).
  • ⭐ Y el caso más caro: el 404 significa tres cosasno existe, no puedes y no está enrutado (CATALOG-STORE capas 4 y 5). Tres causas, un síntoma, y la primera hipótesis de cualquiera será la equivocada.

P4 · El mismo conocimiento en dos sitios, sostenido por un comentario

  • JSON_ENCODED_BASE_TYPES existe en TypeScript y en Python con un # MUST mirror encima y ningún trinquete que lo compare. Si divergen, una columna se escribe codificada y se lee sin decodificar: texto JSON donde el usuario espera un objeto, sin error (INGESTION).
  • tenant_warehouses.key_prefix en Index y key-prefix en Lakekeeper: ya divergieron, en 4 de 8 filas (DATA-STORAGE).

El repo ya sabe cómo se arregla esto —el espejo de namespaces se retiró con
trinquete
— y esa solución no se ha aplicado a los dos casos que quedan.

P5 · Medir una parte y llamarlo el todo

El patrón que más veces ha producido una conclusión falsa, no incompleta:

El censo de DATA-STORAGE recorría un warehouse y lo llamó el catálogovio 174 de 188 tablas y concluyó «ninguna está en el prefijo de su inquilino». Había 14
main.test frente a main.default«medir con el namespace equivocado es medir otro warehouse»
«R2 no soporta vending»se midió una implementación y se concluyó sobre una capacidad
«Perderíamos OpenFGA, Cedar y OPA si dejamos Lakekeeper»eran capacidades del folleto que nuestro despliegue no enciende
El primer diferencial de metadatausó JSON.parse y dio todo igual: estaban mal los dos igual

⚠️ Y uno esperando a repetirse: rest-page-size: 100 con main.default a 99 tablas — cualquier listado que no siga next-page-token está a una tabla de truncar en silencio (arreglado en el censo, no en el resto del repo).

P6 · La puerta es más estrecha que lo que gobierna

  • La cara enruta 13 de 25 endpoints del catálogo, y falla cerrada. Lo que usamos de lo bloqueado —renombrar, registrar— funciona porque va por fuera de ella (CATALOG-STORE capa 5).
  • La cara gobierna 1 de 4 carriles; los otros tres apuntan a Lakekeeper directo (ICEBERG-FACE).
  • Junction pregunta «¿puede esta persona?» sólo en el carril del SQL Editor.

3 · El sustrato legacy que queda, con nombre

QuéDónde
🏁el sink pg con SQL contra dataset_rowsRETIRADO 08-13 (−808 líneas, trinquete 9 → 0), junto al brazo postgres de la puerta que delegaba en éllib/workers/polling/dataset-writer.ts
__row_index / __row_id grabados en 127 de 188 tablasel Parquet, no el código
4 warehouses carbon-test-w-* vivos en Lakekeeper y no registrados en IndexCATALOG-STORE
⚠️cuenta de servicio …-ml-runner fósil: sin concesiones y sin credencialCATALOG-STORE capa 4
⚠️una asignación a oidc~trino-junction, un id que no existeCATALOG-STORE capa 4
⚠️LAKEHOUSE_S3_* de .env.local muerta (401 en un LIST)DATA-STORAGE
⚠️la llave RW en el entorno de duck-server, inerte pero presenteDATA-STORAGE
⚠️políticas Cedar en infra/lakekeeper/policies/el OSS no tiene Cedarmapa §8·1·a

4·1 · Lo primero, porque ordena el resto

De los ~40 pendientes repartidos por las ocho piezas, cambiar de catálogo arregla tres: el /plan, la compactación/expiración programada, y —si además se cambia el modelo— la unidad de tenencia. Todo lo demás sigue igual el día después.

⇒ La pregunta no es «¿es Polaris mejor que Lakekeeper?». Es «¿qué de nuestros problemas es del catálogo?» — y la respuesta medida es: pocos, y ninguno de los dos que más duelen (la ingesta sin puerta, y la recuperación que no recupera).

4·2 · ⭐ El argumento que descartó a Polaris ha CADUCADO

index-catalog-sota.md (2026-08-03), fortaleza F6:

«Vending para R2 — Lakekeeper lo hace; Polaris es AWS-céntrico (STS AssumeRole).
Somos mejores para NUESTRO almacenamiento, y es la razón por la que no
adoptamos Polaris.»

Medido hoy: nadie consume el vending del catálogo.

QuiénCómo obtiene la llave
warehouse-writerllave estática de bucket entero · LAKEHOUSE_REST_VENDING=0
duck-serverllave estática de sólo lectura, DUCK_S3_SCOPE=s3://lakehouse/
Spark, por la carala acuñamos nosotrosr2-local-sign.ts, JWT firmado en proceso, ~1 ms

El warehouse tiene sts-enabled: true y no lo pide nadie.

⭐⭐ La única fortaleza que se citaba como razón para no adoptar Polaris es una
capacidad del catálogo que no encendemos.
Es —exactamente— el error que el propio
mapa corrigió al comparar con Gravitino: «un argumento construido sobre capacidades
que no hemos encendido no compara dos sistemas: compara dos folletos.»

4·3 · 🏁 El argumento que quedaba vivo — MEDIDO: Polaris habla R2

«R1 · ¿Soporta Cloudflare R2? … Es la misma razón por la que se descartó
Polaris.
Si no soporta R2, no hay conversación.» (mapa §8·1·d)

Sonda lanzada el 2026-08-12, misma disciplina que la R1 de Gravitino:

docker run -d --name polaris-probe -p 18181:8181 \
  -e POLARIS_BOOTSTRAP_CREDENTIALS=POLARIS,root,s3cr3t \
  -e polaris.persistence.type=in-memory \
  -e AWS_ACCESS_KEY_ID=$R2_KEY -e AWS_SECRET_ACCESS_KEY=$R2_SECRET -e AWS_REGION=auto \
  apache/polaris:1.7.0
npx tsx scripts/warehouse/p1-polaris-r2-gate.ts --clean
① config: endpoint propio + path-style + region=auto, sin roleArn   ✅ aceptada
③ createNamespace 200 · createTable 200
④ ⭐ LISTANDO EL BUCKET:
     _probe-polaris/probe/r2_gate_…/metadata/00000-….metadata.json    ✅ ESCRITO
⑤ control negativo: fuera de `allowedLocations` → 403, y 0 objetos    ✅
═══ 🏁 POLARIS HABLA R2 ═══

La pieza es stsUnavailable: true, y la dice el propio servidor. Sin ella intenta acuñar por STS y muere con StsException — que es exactamente el fallo que se leyó en su día como «es AWS-céntrico». Su aviso de arranque es literal:

«For S3-compatible storage without STS, set stsUnavailable: true on the storage
config instead.»

El almacenamiento S3-compatible sin STS es un camino de primera clase en Polaris, no un apaño. Y la afirmación que lo descartó era, otra vez, el patrón P5: se midió un error de una configuración y se concluyó sobre una capacidad.

⚠️ Con una consecuencia que sí hay que anotar: Polaris sobre R2 no vende llaves.

loadTable + X-Iceberg-Access-Delegation: vended-credentials
  → 400 «Credential vending was requested … but no credentials are available»
  → remote-signing: 400 «Unsupported access delegation mode»
  → sin delegación: 200

Es el comportamiento honesto —sin STS no hay credencial acotada, así que se niega en vez de entregar la llave ambiente— y para nosotros no cambia nada: la acuñamos en la puerta desde P4-bis. Pero cierra la puerta a que algún día la venda el catálogo.

4·3·bis · ⭐⭐⭐ Y la sonda destapó lo que no íbamos buscando

El endpoints[] de Polaris declara 36, frente a 25 de Lakekeeper y 24 de Gravitino. Los 12 de diferencia no son relleno:

Polaris 1.7.0
⭐⭐⭐ 8 endpoints de POLÍTICASGET/POST/PUT/DELETE …/namespaces/{ns}/policies · …/policies/{n}/mappings · GET /polaris/v1/{prefix}/applicable-policies
4 de tablas no-Iceberggeneric-tables — la «malla» que vende Gravitino, dentro del catálogo Iceberg
scan planningNINGUNO. Polaris 1.7.0 no declara /plan

Y la API de políticas funciona, medida en la misma sonda:

system.data-compaction        → CREADA
system.snapshot-expiry        → CREADA
system.orphan-file-removal    → CREADA
mapping política → tabla      → 204
applicable-policies(tabla)    → [{ "policy-type":"system.data-compaction",
                                   "inheritable":true, "inherited":false }]

⭐⭐⭐ Eso es, literalmente, lo que el atlas tiene como TAGS.md ⛔ no existe y
ROW-POLICY.md ⛔ no existe
— y lo que lib/governance/policy.ts reimplementa a
mano en Index (adjuntar una política a un securable, una por tipo, y resolver la
cadena con override/union). Polaris lo trae en el catálogo, con herencia y
con un endpoint de resolución que cualquier motor puede llamar.

Y los tres tipos que trae de fábrica —compactación, expiración de snapshots,
limpieza de huérfanos— son exactamente los tres que DATA-STORAGE documenta como
«vocabulario sin ejecutor»
.

El eje de la decisión ya no es el almacenamiento: es qué se quiere del catálogo.

Si el criterio es…Gana
/plan — la costura donde poner la política de celdaGravitino (lo declara, autorizado por principal, y está desplegado)
La política EN el catálogo — adjuntar, heredar y resolver⭐⭐⭐ Polaris, y no está cerca
Vending desde el catálogo contra R2Lakekeeper — y no lo usa nadie

4·4 · Lo que CATALOG-STORE añade y no estaba en la mesa el 3 de agosto

El 08-03 el coste de salida se estimó así: «no hay atadura de API; el coste real es el registro». Ahora sabemos cuánto es ese registro, y qué clase de cosa es:

  • el catálogo es la fuente, no un índice: la metadata Iceberg vive normalizada en 44 tablas de su Postgres, y el metadata.json de R2 es una salida (probado con diferencial en frío);
  • el estado vivo son 194 tabulars · 619 snapshots · 851 entradas de metadata-log · 181 refs · 13 warehouses · 2 tags del JOP;
  • migrar de catálogo no es re-apuntar una URI: es reconstruir el estado. Y la operación con la que se reconstruiría —registeres una de las 12 que nuestra cara no enruta.

⚠️ Y hay una prueba que nadie ha hecho, para ninguno de los tres candidatos: registrar una tabla existente en un catálogo nuevo y comprobar que conserva su historia (snapshots, refs, tags). Sin eso, «migrable» es una opinión.

4·5 · La asimetría que de verdad decide, y no es de almacenamiento

Dónde pone cada uno la frontera del inquilino:

Unidad de tenencia
Carbon, hoyel WAREHOUSEcarbon-<env>-w-<workspace> con su key-prefix. Sellado en Index (tenant_warehouses), en la cara (warehouse=w_<ws>) y en el prefijo del bucket
Apache Polarisel CATÁLOGO — múltiples catálogos independientes, cada uno con su propia configuración de almacenamiento, más realms
Gravitino (IRC server)el catálogo, pero el servidor IRC sustituye pieza por pieza sin obligar a mover la frontera

⭐⭐ Absorber Polaris cambia la unidad de tenencia, y eso no toca el catálogo:
toca Index, la cara y el prefijo del bucket — las tres a la vez, y las
tres acaban de quedar documentadas como el sitio donde ya hay una divergencia
abierta (4 filas de tenant_warehouses que no casan).

El coste de Polaris no está en el catálogo. Está en Index.

Eso no es un «no». Es que si el motivo para ir a Polaris es su modelo —multi-catálogo, realms, madurez de vending, comunidad— entonces la decisión que se está tomando no es «qué catálogo» sino «dónde vive la frontera del inquilino», y merece evaluarse como tal.

Si el motivo es /plan, la comparación es otra: Gravitino ya lo tiene medido, desplegado en el cluster, y con el endpoint autorizado por principal leído en la fuente. Polaris tendría que empatar eso desde cero.

4·5·bis · ⛔ ¿Polaris y Gravitino? — no hay sinergia, hay que elegir

① «Gravitino» son dos productos, y el folleto vende el grande. El diagrama del Unified Metadata Lake —SSOT, geo-distribuido, lineage, auditing, optimizing— es el servidor completo (metalake). Lo que medimos y lo que corre en el cluster es el Iceberg REST server: otro artefacto, otro chart, 250m de CPU y 1 Gi. Y su propio conf, leído dentro de la imagen, sólo documenta tres backends:

gravitino.iceberg-rest.catalog-backend = memory     # «it's recommended to change to hive or jdbc»
#                                        jdbc
#                                        hive

El artefacto que tenemos desplegado no puede montarse encima de otro catálogo REST: no documenta ese backend. La federación, si se quiere, exige la bestia entera — que es justo lo que el mapa §8·1·g concluyó que no hace falta.

② Como catálogo de REGISTRO son mutuamente excluyentes, y no por el protocolo —los dos hablan Iceberg REST— sino por dónde vive la atomicidad. CATALOG-STORE capa 2 lo midió: el commit es un CAS sobre table_refs dentro del Postgres del catálogo, y el metadata.json de R2 es una salida.

Dos catálogos sobre las mismas tablas son dos CAS sobre dos punteros distintos.
No es que no se hablen: es que uno de los dos estaría leyendo la última copia de
la verdad del otro
— y se pierde exactamente la propiedad por la que un catálogo
existe.

③ La sinergia que ofrece el diagrama es federación, y está fuera del norte. Decidido el 2026-08-11 (concepto ⑱ del mapa), y §8·1·b lo dice en físico: «no federamos: se copia al bucket». Un bucket, un formato, un inquilino con datos: no hay nada que federar.

④ La única topología coherente sería Gravitino-metalake ENCIMA y Polaris DEBAJO — nunca al revés. Y su precio es el patrón P4 elevado a sistema: dos servidores, dos Postgres, dos modelos de autorización y dos integraciones de identidad sosteniendo el mismo conocimiento.

⑤ Y la asimetría que decide, visible en las propias rutas medidas:

Gravitino:  POST /v1/{prefix}/tables/{table}/plan          ← SPEC Iceberg REST 1.11
Polaris:    GET  /polaris/v1/{prefix}/applicable-policies  ← EXTENSIÓN de proveedor

⭐⭐ El /plan es estándar; la API de políticas no. Elegir Gravitino por el
/plan es apostar por algo que acabará siendo mercancía —lo implementará
cualquiera, incluida nuestra cara, que ya es un IRC—. Elegir Polaris por las
políticas es adoptar una extensión propietaria… que resulta ser la pieza que
el atlas tiene sin escribir
.

Ésa es la elección real: construir lo estándar o adoptar lo que no tenemos.

4·5·ter · 🏁 El servidor COMPLETO de Gravitino, medido — y reencuadra la elección

(Sonda scripts/warehouse/g2-gravitino-federacion-gate.ts, apache/gravitino:1.3.0, 2026-08-12. Todo entre contenedores: no toca producción.)

① Los tipos de objeto que el catálogo modela — y son los del norte

/opt/gravitino/catalogs/  →  fileset · glue · hive · jdbc-doris · jdbc-mysql
                             jdbc-postgresql · jdbc-starrocks · kafka
                             lakehouse-generic · lakehouse-hudi
                             lakehouse-iceberg · lakehouse-paimon · model
RELATIONAL (tablas, vistas) · FILESET ✅ (ficheros/volúmenes) · MODEL ✅ · MESSAGING (colas)

Los proveedores son paquetes en disco (/opt/gravitino/catalogs/<x>/libs), no una lista cerrada. ⇒ El «tablas, volúmenes, colas, conexiones, ficheros, modelos, vistas» existe como modelo, no como diagrama. Y una «conexión» en su vocabulario es un catálogo: montar una fuente es darla de alta.

⚠️ hive y kafka fallaron por falta de sus propiedades obligatorias (metastore, bootstrap servers), no por no existir.

② ⭐⭐⭐ La federación nativa, con el bucle cerrado

montar un Iceberg REST AJENO  (catalog-backend: rest)   → 200
listar SUS schemas                                      → 200
CREAR un schema a través de Gravitino                   → 200
⭐ ¿lo ve el catálogo de ABAJO?  ✅ SÍ
   {"namespaces":[["desde_gravitino"], …]}

⭐⭐⭐ Federar no es copiar: es ponerse DELANTE. Gravitino monta el catálogo
ajeno, le reenvía las operaciones, y el dueño del registro sigue siendo el de
abajo
— la escritura apareció en él, no en Gravitino.

(Verificado como manda la casa: no preguntándole a Gravitino, sino al catálogo
federado. Con Polaris el intento dio Not authorized, que no distingue «no federa»
de «no le pasé el token»; por eso el control se hizo contra un IRC sin auth.)

Y eso demuele la objeción más cara de §4·4. «Migrar de catálogo es reconstruir el estado» sigue siendo cierto para Polaris — que es una sustitución. Para Gravitino no aplica, porque puede adoptarse como superposición:

la cara ──▶ Gravitino (tipos + federación) ──▶ Lakekeeper (sigue siendo el registro Iceberg)

Gravitino es el único de los tres que se adopta sin migrar nada, y por tanto el único reversible.

③ ⛔ Y el contra que hay que decir igual de fuerte: su gobernanza viene apagada

GET /api/metalakes/probe/roles   → 405
  «You should set 'gravitino.authorization.enable' to true in the server side
   gravitino.conf to enable the authorization of the system»

Roles, usuarios y grupos no responden de fábrica. Es el patrón P1 —declarado y no aplicado— pero de serie. Y su ABAC sigue siendo futuro: el mapa §8·1·d ya midió que empuja los permisos hacia Ranger, que es el producto que decidimos no adoptar.

Lo que hoy tenemos —decidePrivilege, access_events, la retícula de la cara— es MÁS de lo que Gravitino trae encendido. Adoptarlo no nos da gobernanza: nos da dónde colgarla, y para más tipos de objeto.

4·5·quater · Pros y contras, con lo medido de cada uno

Gravitino completoPolaris completo
Qué esMetadata lake: un modelo sobre 13 proveedoresUn catálogo Iceberg REST con políticas
Tipos de objeto✅ tablas · vistas · ficheros/volúmenes · modelos · colas · conexiones⛔ tablas · vistas · generic-tables. Ni ficheros, ni modelos, ni colas
Adopción⭐⭐⭐ superposición — federa el catálogo actual, sin migrarsustitución — hay que reconstruir 194 tabulars, 619 snapshots, refs y tags
Reversible✅ se quita y debajo sigue todo⛔ una vez migrado, volver es otra migración
Política en el catálogono — y su ABAC es futuro, empuja a Ranger⭐⭐⭐ : adjuntar, heredar y resolver (medido)
Autorización de serieapagada (405 hasta habilitarla)✅ RBAC activo, con grants por catálogo
Scan planning /plan (spec 1.11, autorizado por principal)⛔ no
R2✅ medido (IRC)✅ medido (stsUnavailable)
Vendingsí (IRC)⛔ sobre R2 se niega — no cambia nada: acuñamos en la puerta
Unidad de tenenciametalake → catálogo (encaja encima del warehouse actual)el catálogo ⇒ cambia la nuestra, y eso toca Index
Coste operativo⛔ servidor JVM + su Postgres + 13 superficies de dependencia. El cluster está al 88 % de CPUservidor JVM + Postgres, superficie más pequeña
Lo que NO resuelvela política (la construimos igual)los tipos que el norte necesita

4·5·quinquies · La lectura, sin diplomacia

Sí merece la pena como proyecto ambicioso — pero no como «cambiar de catálogo».

Los dos resuelven mitades distintas del mismo norte, y la comparación honesta no es Gravitino vs Polaris: es qué mitad falta más.

  • Si el catálogo tiene que albergar volúmenes, colas, conexiones, modelos y ficheros —y el norte dice que sí, porque la ontología se proyecta desde ahí— entonces Polaris no es candidato: no los modela. La conversación se acaba ahí.
  • Y entonces Gravitino no es un catálogo mejor: es la capa que hoy no existe, la que el mapa llama concepto ⑮ —«un solo modelo de tipos y permisos», hoy dos planos que no se hablan—. Eso no es sustituir a Lakekeeper: es ponerle encima lo que le falta, sin migrar, y con la opción de sustituirlo después o nunca.
  • El precio es explícito y hay que aceptarlo con los ojos abiertos: la gobernanza no viene incluida. Seguimos escribiendo la política nosotros — sólo que ahora para seis tipos de objeto en vez de uno.

⭐⭐ La forma correcta de la pregunta no es «¿qué catálogo?». Es «¿cuál de las dos
mitades compramos y cuál construimos?»
— y la única que no se puede construir
barata
es el modelo multi-tipo con sus 13 conectores.

4·6 · ⛔ Y el orden importa más que la elección

Antes de cambiar de catálogo, la ingesta tiene que pasar por la cara.

Tres razones, todas medidas:

  1. Hoy entra por fuera el 100 % del dato nuevo. Cambiar lo que hay debajo de una puerta que la ingesta no cruza no la protege.
  2. La cara es la capa que absorbe el cambio. Ya traduce nombres, resuelve inquilino y sustituye el prefijo. Cuando Trino dejó de hablar con Lakekeeper y pasó a hablar con ella, no hubo que cambiar nada más. Es la prueba de que la abstracción funciona — y de que conviene tenerla delante de todos los carriles antes de mover el sustrato.
  3. Si se cambia primero el catálogo, el trabajo de meter la ingesta por la cara hay que hacerlo igual, después, y encima contra un sustrato recién movido.

5 · Lo que este repaso propone — con su gate

OrdenQuéGate
⛔⛔ La ingesta por la cara (warehouse-writer/api/iceberg)Una escritura de ingesta con asiento en access_events. Hoy no deja ninguno
⛔⛔ Darle disparador al reconciliador — 5 txn open con su reparador escrito y sin llamarLa cola de open con más de 1 h a 0
⛔⛔ Recuperación real: o el borrado blando protege bytes, o se deja de prometer 7 díasDropear, esperar, undrop, y leer las filas
🏁 La sonda de Polaris — hecha, verdescripts/warehouse/p1-polaris-r2-gate.ts
La prueba de mudanza, para el candidato que ganeregister de una tabla existente en el catálogo nuevo conservando snapshots, refs y tags
Reconciliar las 4 filas de tenant_warehouses y dar de alta al writer en los 4 warehouses desiertosel 404 de la capa 4 pasa a 200

5·1 · 🏁 La sonda, y lo que queda después de ella

scripts/warehouse/p1-polaris-r2-gate.ts — sólo lectura salvo dentro de _probe-polaris/, idempotente, y con las cuatro comprobaciones que la hacen concluyente: verificar listando el bucket, leer endpoints[], control negativo fuera del prefijo, y versión fijada (apache/polaris:1.7.0, no :latest).

Lo que sigue sin medirse, para ninguno de los tres candidatos:

QuéPor qué decide
La prueba de mudanza: register de una tabla existente conservando snapshots, refs y tagsCATALOG-STORE capa 2: el catálogo es la fuente ⇒ migrar es reconstruir. Sin esta prueba, «migrable» es una opinión
Polaris con persistencia real (relational-jdbc sobre Postgres), no in-memoryLa sonda usó el metastore de pruebas — el propio servidor lo avisa. Prueba la capacidad, no la operación
Multi-inquilino: un catálogo por organización con su allowedLocationsEs el cambio de unidad de tenencia de §4·5, y es donde está el coste real

6 · Las dos frases que resumen el repaso

① El catálogo no es donde duele. Duele que el dato entre sin puerta, que el
reparador no tenga quien lo llame, y que la recuperación que prometemos no exista.
Cambiar de catálogo no arregla ninguno de los tres, y hacerlo antes que ellos
multiplica el trabajo en vez de dividirlo.

② Pero la sonda encontró algo que sí cambia el paradigma, y no era lo que
buscábamos.
No es el almacenamiento —eso sólo era el peaje— sino que Polaris
trae en el catálogo la pieza que el atlas tiene como inexistente
: adjuntar una
política a un objeto, heredarla y resolverla, con un endpoint que cualquier motor
puede llamar. TAGS y ROW-POLICY dejarían de ser dos documentos por escribir
para pasar a ser una integración.

Y el precio está igual de medido: se pierde el /plan —que Gravitino sí
declara y que era la costura donde íbamos a poner la política de celda— y se
pierde que el catálogo venda llaves
, que hoy no usa nadie.

⇒ La decisión ya no es «qué catálogo aguanta R2». Es «¿la política vive en el
catálogo (Polaris) o en el plan (Gravitino)?»
— y ésa sí es una decisión de
paradigma, con las dos opciones medidas y ninguna descartada por un folleto.


Relacionado

docs/piezas/ (las ocho piezas) · index-gobernanza-mapa.md §8·1 (la evaluación de Gravitino, con R1 y R2 medidos) · index-catalog-sota.md (el análisis de Polaris del 2026-08-03, cuya F6 este repaso caduca) · f2-catalog-substrate-approach.md (los modelos de tenencia).