🧭 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ónde | Qué |
|---|---|
| DATA-STORAGE | dedicated_storage armado en 7 organizaciones vacías y desarmado en la única con datos |
| DATA-STORAGE | target-file-size = 256 MiB sobre un fichero medio de 17,7 KB (~15.000×) |
| DATA-STORAGE | política de retención declarada en 33 de 188 tablas; layout en 83 de 188 |
| CATALOG-STORE | idempotency-key-lifetime: PT30M anunciado · idempotency_record vacía |
| CATALOG-STORE | task_config vacía: todo el ciclo de vida corre con los defaults, sin decidirlo |
| ICEBERG-FACE | endpoints: [] — la cara no declara ninguna capacidad |
| INDEX-CATALOG | Cedar decidido y sin activar · OpenFGA desplegado e inerte en Carbon |
| DATA-STORAGE | compaction 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ónde | Qué |
|---|---|
| ⛔⛔ INGESTION | ratifyOpenTransactions — escrito, probado, y sólo lo llama su test. Hay 5 txn open esperando |
| DATA-STORAGE | el retention-scheduler es opt-in y arranca en dry-run |
| CATALOG-STORE | tabular_purge declarada y nunca ejecutada |
| DATA-STORAGE | nadie recoge ficheros sin referencia dentro de una tabla viva |
| CATALOG-STORE | nadie recoge los tags carbon-precomp-… del JOP |
| DATA-STORAGE | check: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
stringsin aviso;Decimal → decimal(38,9)pierde escala en silencio (INGESTION). - 17 de 26 fuentes están en
main.defaultsin que nadie lo eligiera, y «lo eligió» y «se lo pusimos» son indistinguibles (INGESTION). - El
accessKeyIdde 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
404significa tres cosas — no 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_TYPESexiste en TypeScript y en Python con un# MUST mirrorencima 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_prefixen Index ykey-prefixen 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álogo | vio 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 metadata | usó 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 | |
|---|---|---|
| 🏁 | pg con SQL contra dataset_rowspostgres de la puerta que delegaba en él | lib/workers/polling/dataset-writer.ts |
| ⛔ | __row_index / __row_id grabados en 127 de 188 tablas | el Parquet, no el código |
| ⛔ | 4 warehouses carbon-test-w-* vivos en Lakekeeper y no registrados en Index | CATALOG-STORE |
| ⚠️ | cuenta de servicio …-ml-runner fósil: sin concesiones y sin credencial | CATALOG-STORE capa 4 |
| ⚠️ | una asignación a oidc~trino-junction, un id que no existe | CATALOG-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 presente | DATA-STORAGE |
| ⚠️ | políticas Cedar en infra/lakekeeper/policies/ — el OSS no tiene Cedar | mapa §8·1·a |
4 · Y entonces: ¿Polaris como sustrato de catálogo?
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én | Cómo obtiene la llave |
|---|---|
warehouse-writer | llave estática de bucket entero · LAKEHOUSE_REST_VENDING=0 |
duck-server | llave estática de sólo lectura, DUCK_S3_SCOPE=s3://lakehouse/ |
| Spark, por la cara | la acuñamos nosotros — r2-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: trueon 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ÍTICAS | GET/POST/PUT/DELETE …/namespaces/{ns}/policies · …/policies/{n}/mappings · GET /polaris/v1/{prefix}/applicable-policies |
| ⭐ 4 de tablas no-Iceberg | generic-tables — la «malla» que vende Gravitino, dentro del catálogo Iceberg |
| ⛔ scan planning | NINGUNO. 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 existey
ROW-POLICY.md ⛔ no existe— y lo quelib/governance/policy.tsreimplementa a
mano en Index (adjuntar una política a un securable, una por tipo, y resolver la
cadena conoverride/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 celda | ⭐ Gravitino (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 R2 | Lakekeeper — 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.jsonde 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 —
register— es 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, hoy | ⭐ el WAREHOUSE — carbon-<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 Polaris | el 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 detenant_warehousesque 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
/planes estándar; la API de políticas no. Elegir Gravitino por el
/planes 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 dioNot 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 completo | ⭐ Polaris completo | |
|---|---|---|
| Qué es | Metadata lake: un modelo sobre 13 proveedores | Un 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 migrar | ⛔ sustitució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álogo | ⛔ no — y su ABAC es futuro, empuja a Ranger | ⭐⭐⭐ sí: adjuntar, heredar y resolver (medido) |
| Autorización de serie | ⛔ apagada (405 hasta habilitarla) | ✅ RBAC activo, con grants por catálogo |
Scan planning /plan | ⭐ sí (spec 1.11, autorizado por principal) | ⛔ no |
| R2 | ✅ medido (IRC) | ✅ medido (stsUnavailable) |
| Vending | sí (IRC) | ⛔ sobre R2 se niega — no cambia nada: acuñamos en la puerta |
| Unidad de tenencia | metalake → 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 CPU | servidor JVM + Postgres, superficie más pequeña |
| Lo que NO resuelve | la 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:
- 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.
- 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.
- 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
| Orden | Qué | 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 llamar | La 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ías | Dropear, esperar, undrop, y leer las filas |
| ④ | 🏁 La sonda de Polaris — hecha, verde | scripts/warehouse/p1-polaris-r2-gate.ts |
| ⑤ | La prueba de mudanza, para el candidato que gane | register 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 desiertos | el 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 tags | CATALOG-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-memory | La 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 allowedLocations | Es 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.TAGSyROW-POLICYdejarí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).